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

@AndresL230 AndresL230 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.

AndresL230 and 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

coderabbitai Bot 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 name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring 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 name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Docs spaces are being fixed to the technical/product vocabulary.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check 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.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
canopy 6e0ca1e Commit Preview URL

Branch Preview URL
Jul 10 2026, 06:08 AM

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

@coderabbitai coderabbitai Bot 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 thread src/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/bash
set -euo pipefail

echo '--- locate relevant files ---'
git ls-files | rg '^(src/tools/writes\.ts|src/.*|migrations/.*|.*0020.*)$' || true

echo '--- find DocProposal and raw.space usages ---'
rg -n 'DocProposal|raw\.space|space: target\.space' src migrations -S

echo '--- outline candidate files if available ---'
for f in src/tools/writes.ts; do
  if [ -f "$f" ]; then
    echo "## $f"
    ast-grep outline "$f" --view expanded || true
  fi
done

Repository: SaplingLearn/canopy

Length of output: 3783


🏁 Script executed:

#!/bin/bash
set -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/bash
set -euo pipefail
sed -n '1,60p' shared/contract.ts | cat -n

Repository: SaplingLearn/canopy

Length of output: 2809


🏁 Script executed:

#!/bin/bash
set -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 free to 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