Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Harden Docs spaces to a fixed {Technical, Product} vocabulary - #33

Merged
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces
Jul 10, 2026
Merged

Harden Docs spaces to a fixed {Technical, Product} vocabulary#33
AndresL230 merged 2 commits into
mainfrom
feat/harden-docs-spaces

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

The Docs page showed a stray Sapling tab alongside Technical and Product. Root cause: two disconnected vocabularies.

  • Display side derived tabs from the data — render.ts took whatever distinct space values existed and made a tab per value. Any value became a tab.
  • Write side constrained space to sapling | canopy and defaulted new docs to canopy — a vocabulary that never matched the Technical | Product model the UI was rebuilt around. (Side effect: the next agent-written doc would have spawned a Canopy tab.)

Nothing pinned the tab set. The Sapling tab was simply the one doc still carrying space='sapling' (sapling-frontend-local-dev, an engineering reference).

What

Make space a real controlled vocabulary of exactly {technical, product}, enforced on every surface:

  • Hard enum{technical, product} on the write contract (DocProposal, QueryRequest), the query + propose_doc_update MCP tools, the gate, and tools/writes.ts. Omitted → defaults technical; an off-vocab value is rejected at the tool boundary (an error, not silent triage) so an agent can't widen the tab set.
  • Fixed Docs tabs — the UI renders DOC_SPACES = ["technical","product"] directly instead of deriving tabs from data, so a stray/foreign space can never add or change a tab.
  • Triage "assign" surface and the GET /search space filter move in lockstep to technical|product.
  • Migration 0020_docs_space_vocab.sql — idempotent, self-defending: folds any pre-existing off-vocab space (the one sapling doc → correctly Technical) into the default. No-op on fresh local/test DBs; no FTS rebuild needed.

Scope is spaces/tabs only — the parallel section-vocab disconnect is intentionally left alone (noted in the spec).

Testing

  • npm run typecheck — green (also added test/render.docs.test.ts to the tsconfig include/exclude split, matching the existing web-importing-test convention).
  • npm test — the doc-space suite passes: default→technical, explicit product persists, off-vocab rejected at the boundary, and a render test asserting exactly the two fixed tabs. Updated the prior sapling/canopy assertions in consumer.reconcile, mcp.propose_doc, ingest.route, triage-map, render.review.
  • npm run build:web — succeeds.
  • Pre-existing unrelated failure summarize.test.ts (environmental: a real GEMINI_API_KEY in .dev.vars makes the "key-unset → excerpt" case return a real Gemini result) fails identically on main.

Deploy notes

Two prod steps after merge:

  1. npm run db:migrate:remote — applies 0020 (removes the Sapling tab from live data; the currently-deployed data-derived frontend drops the tab on this alone).
  2. npm run deploy — activates the fixed-tabs UI + the write-side enum.

Design spec: docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Docs now use two fixed spaces: Technical and Product.
    • The Docs screen always displays both tabs, regardless of available content.
    • Existing documents with legacy space values are normalized to Technical.
  • Bug Fixes

    • Invalid space values are rejected during document creation, search, and triage assignment.
    • Unspecified document spaces now consistently default to Technical.
    • Legacy values can no longer create additional Docs tabs.

AndresL230and others added 2 commits July 10, 2026 00:16
Design spec: make doc 'space' a hard two-value enum enforced on every
surface (contract, MCP tools, gate, UI tabs, triage assign), default
'technical', and migrate the single stray 'sapling' doc. Removes the
data-derived Sapling tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Docs tabs were data-derived (any doc 'space' value became a tab) while
the write side spoke an unmatched sapling|canopy vocab defaulting to canopy —
so a stray 'sapling' doc surfaced a Sapling tab, and new agent docs would have
spawned a Canopy tab.
- space is now a hard enum {technical, product} on the write contract, the
query/propose_doc_update MCP tools, the gate, and writes.ts; new docs default
to 'technical'. An off-vocab space is rejected at the tool boundary.
- The Docs UI renders a FIXED two-tab set (DOC_SPACES) instead of deriving tabs
from data, so a stray/foreign space can never add or change a tab.
- The triage 'assign' surface and the /search space filter move in lockstep.
- Migration 0020 folds any pre-existing off-vocab space (the one 'sapling' doc,
an engineering reference) into 'technical'.
Tests: default→technical, explicit product persists, off-vocab rejected, and a
render test asserting exactly the two fixed tabs. typecheck + web build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes Docs spaces to technical and product, defaults writes to technical, rejects invalid values, normalizes existing data, updates search and triage handling, and renders exactly two Docs tabs.

Changes

Docs space vocabulary

Layer / File(s)Summary
Vocabulary contract and data normalization
docs/superpowers/specs/..., shared/contract.ts, shared/rows.ts, migrations/0020_docs_space_vocab.sql
Defines the two-value vocabulary, updates shared schemas and documentation, and normalizes existing non-canonical database values to technical.
Write-path defaults and validation
src/mcp.ts, src/consumer.ts, src/tools/writes.ts
Validates technical/product inputs, defaults omitted values to technical, and applies the vocabulary to document creation and triage materialization.
Search, assignment, and fixed Docs tabs
src/routes.ts, web/src/api.ts, web/src/main.ts, web/src/render.ts, web/src/triage-map.ts
Updates search and assignment handling and replaces data-derived Docs tabs with an always-rendered ordered pair of technical and product.
Behavior tests and TypeScript coverage
test/*.test.ts, tsconfig.web.json, tsconfig.worker.json
Tests persistence defaults, validation rejection, fixed tab rendering, and updated triage values while adjusting TypeScript test coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant MCP tool
participant Contract validation
participant Write tool
participant Docs database
MCP tool->>Contract validation: submit space
Contract validation->>Write tool: accept technical/product
Write tool->>Docs database: persist normalized space
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 27.27% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harden-docs-spaces

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
canopy6e0ca1eCommit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

@AndresL230
AndresL230 merged commit 397c612 into mainJul 10, 2026
2 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/tools/writes.ts`:
- Line 463: In the triage item construction near the `space` assignment,
normalize `raw.space` before passing it to `DocProposal.parse()`: preserve
`target.space` when provided, otherwise accept only `"technical"` or `"product"`
from `raw.space`, and use `undefined` for values such as `"sapling"` or any
other legacy value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 264e8404-cdde-4ac7-9996-0a858adc662e

📥 Commits

Reviewing files that changed from the base of the PR and between 594726c and 6e0ca1e.

📒 Files selected for processing (20)
  • docs/superpowers/specs/2026-07-10-harden-docs-spaces-design.md
  • migrations/0020_docs_space_vocab.sql
  • shared/contract.ts
  • shared/rows.ts
  • src/consumer.ts
  • src/mcp.ts
  • src/routes.ts
  • src/tools/writes.ts
  • test/consumer.reconcile.test.ts
  • test/ingest.route.test.ts
  • test/mcp.propose_doc.test.ts
  • test/render.docs.test.ts
  • test/render.review.test.ts
  • test/triage-map.test.ts
  • tsconfig.web.json
  • tsconfig.worker.json
  • web/src/api.ts
  • web/src/main.ts
  • web/src/render.ts
  • web/src/triage-map.ts

Comment threadsrc/tools/writes.ts
section,
confidence: "high", // human-vouched on assign
space: target.space ?? (raw.space as "sapling" | "canopy" | undefined),
space: target.space ?? (raw.space as "technical" | "product" | undefined),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$'||trueecho'--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S
echo'--- outline candidate files if available ---'forfin src/tools/writes.ts;doif [ -f"$f" ];thenecho"## $f"
ast-grep outline "$f" --view expanded ||truefidone

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- writes.ts around the proposal parse ---'
sed -n '440,470p' src/tools/writes.ts | cat -n
echo'--- consumer.ts DocProposal handling ---'
sed -n '100,170p' src/consumer.ts | cat -n
echo'--- shared contract definition for DocProposal ---'
rg -n 'export (type|interface) DocProposal|space' src shared -S
echo'--- migration 0020 ---'
sed -n '1,220p' migrations/0020_docs_space_vocab.sql | cat -n
echo'--- earlier triage/migration touching raw JSON ---'
rg -n 'needs_triage|raw' migrations src -S

Repository: SaplingLearn/canopy

Length of output: 21030


🏁 Script executed:

#!/bin/bashset -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo'--- search historical docs-space changes ---'
rg -n 'sapling|canopy|space.*technical|space.*product|doc_space|docs_space|vocabulary' migrations src shared -S
echo'--- inspect the docs space migration ---'
sed -n '1,200p' migrations/0005_doc_space.sql | cat -n
echo'--- inspect triage table creation ---'
sed -n '40,70p' migrations/0001_init.sql | cat -n

Repository: SaplingLearn/canopy

Length of output: 7161


Filter raw.space before parsing triage items

DocProposal.parse() only accepts "technical" or "product", so a legacy triage row with raw.space = "sapling" will fail assignment unless target.space is set. Normalize the raw value to one of the allowed enums and fall back to undefined for old items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/tools/writes.ts` at line 463, In the triage item construction near the
`space` assignment, normalize `raw.space` before passing it to
`DocProposal.parse()`: preserve `target.space` when provided, otherwise accept
only `"technical"` or `"product"` from `raw.space`, and use `undefined` for
values such as `"sapling"` or any other legacy value.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230