feat(example): editor + constellation tabs in web_ui_demo - #370
Conversation
Two new workspace views ported from the "CSI MasterFormat Specification Editor" Claude Design project: - Editor: full-page document editing for one section — project TOC rail grouped by MasterFormat division (add-from-masters via POST /projects/:id/specs, flag-for-removal review queue backed by DELETE /projects/:id/specs/:id), the live tree.js sheet in the middle (same paragraph PATCH / reversible-removal affordances as the map), and a CITES / CITED BY inspector rail. - Constellation: the project corpus as division solar systems — umbrella section (NN 00 00) as the sun, black hole when undefined, planets sized by inbound citations, orphan/flagged styling, broken citations as red severed lanes (amber ghost when the target is one click away in a source library), hover-to-trace, focused per-division view with portals, and click-a-sightline → citing paragraph in the Editor. Both views read the same client state as the Reference Web, so edits, flags, additions, and removals reshape the map immediately. Demo-only: no changes under src/. Closes #369 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds an Editor tab and Constellation tab to the web UI demo, plus shared inline-editing utilities and a new paragraph insertion API, database query, and MCP tool path. ChangesWeb UI demo
Paragraph insertion API and MCP support
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant tree.js
participant inline-edit.js
participant restructure.js
participant editor.js
User->>tree.js: open paragraph or heading
tree.js->>inline-edit.js: renderInlineText() / makeCollapsible()
User->>inline-edit.js: blur, Enter, Tab, or Shift+Tab
inline-edit.js->>restructure.js: tryRestructure() or applyRestructureOps()
inline-edit.js->>editor.js: commit callbacks or preview refresh
editor.js->>tree.js: re-render editor sheet and inspector
sequenceDiagram
participant Client
participant paragraphs.ts
participant paragraph-insert.ts
participant PostgreSQL
Client->>paragraphs.ts: POST /specs/:id/paragraphs
paragraphs.ts->>paragraph-insert.ts: insertParagraphAfter(specId, input)
paragraph-insert.ts->>PostgreSQL: lock anchor, shift positions, insert row
PostgreSQL-->>paragraph-insert.ts: created / not-found / wrong-spec / invalid-type
paragraph-insert.ts-->>paragraphs.ts: result
paragraphs.ts-->>Client: 201 or mapped error
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
… tab Second pass at the reference design: the Editor's sheet now edits like the design instead of swapping textareas. - Inline editing: paragraph, article, and part text is contenteditable in place (segment model — editable text runs around non-editable citation chips). Changes save on blur via the existing paragraph PATCH; Enter/Escape commit; editability-locked paragraphs stay read-only with a chip; the focused row shows the design's EDITING · SAVES WHEN YOU CLICK AWAY state. - Citation chips carry the verbatim matched text (data-raw) so reconstruction never leaks the display format into stored text; × removal runs through the existing removed-reference dialog, or a plain confirm for untracked (self/unindexed) citations. - Tab / Shift+Tab restructures through the CSI tier ladder with outline semantics (js/restructure.js): indent under the previous sibling with subtree tier-shift (pr7 cap), outdent carrying following siblings as children, pr1 ⇄ article promotion/demotion (hover ⇥ demotes an article). Labels are render-derived, so the sheet renumbers live. No restructure endpoint exists yet (#371), so ops are held as an explicit RENUMBER PREVIEW overlay (with RESET) replayed over server truth — text edits inside a preview persist for real and the preview survives reloads. - Consistency: the citation matcher moved to refs-text.js (shared by tree.js and inline-edit.js); the reference-design 3px row rule now applies to the shared sheet styles so Map, Report, and Editor render fixtures identically. Closes nothing new; extends #369 (PR #370). Backend gap filed as #371. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pressing Enter in an editable run starts a new empty paragraph of the same CSI tier immediately below (a new article when pressed in an article heading), caret ready, sheet renumbered live. Parts are guarded — CSI's three-part format stays intact. The API has no paragraph-creation endpoint (filed as #372), so Enter-drafts join the same structure-op overlay as Tab/Shift+Tab moves: an 'insert' op carries the draft's id, tier, and text; drafts render with a DRAFT · LOCAL tag and an amber dashed rule, take text edits (stored in the op, replayed across reloads), can be restructured, and never receive server-only affordances (⊘ removal) or leak into the Map/Report views. The preview chip is now LOCAL PREVIEW · n, covering both op kinds; RESET drops everything. Typing + Enter in one gesture mirrors the committed text onto the base tree before the re-render (same capture-before-blur ordering as Tab), so the text never flashes stale while its PATCH is in flight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
examples/web_ui_demo/js/constellation.js (1)
263-263: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract the hub/bright threshold into a named constant.
The magic number
3(inbound-citation threshold for "hub" stars and "bright" suns) is repeated in three separate spots. A shared constant would keep the threshold consistent if it's ever tuned.♻️ Suggested constant
const FOCUS_H = 780; +const HUB_INBOUND_THRESHOLD = 3;- const core = svgEl('circle', { cx, cy, r: 10, class: `cst-sun-core${crossIn >= 3 ? ' is-bright' : ''}` }); + const core = svgEl('circle', { cx, cy, r: 10, class: `cst-sun-core${crossIn >= HUB_INBOUND_THRESHOLD ? ' is-bright' : ''}` });Also applies to: 294-294, 422-422
🤖 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 `@examples/web_ui_demo/js/constellation.js` at line 263, The hub/bright threshold is hardcoded as the repeated magic number 3 in the constellation rendering logic. Introduce a shared named constant for this inbound-citation threshold and update the relevant checks in the constellation code (including the class assignment and the other hub/bright comparisons) to use that constant so the value stays consistent in one place.
🤖 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 `@examples/web_ui_demo/js/editor.js`:
- Around line 268-270: The draft text update path in editor.js only mutates the
existing base node, so local draft content from node.meta.localDraft can be lost
on the next render. Update the insert-op handling in the pendingText flow so
drafts are written back to the operation itself, not just base.tree.parts, and
make the guard check pendingText !== null instead of a truthy check so empty
edits are preserved. Apply the same fix in the matching insert/update path
referenced by findBaseNode and the render replay logic.
- Around line 499-517: The refRow helper is leaving unloaded/ghost references as
enabled button elements without any click behavior, which makes them appear
interactive when they are not. Update refRow to treat the non-loaded branch as
non-interactive by either rendering a non-button element or explicitly disabling
the existing button, while preserving the loaded branch behavior that attaches
openSection(section) and the active title.
---
Nitpick comments:
In `@examples/web_ui_demo/js/constellation.js`:
- Line 263: The hub/bright threshold is hardcoded as the repeated magic number 3
in the constellation rendering logic. Introduce a shared named constant for this
inbound-citation threshold and update the relevant checks in the constellation
code (including the class assignment and the other hub/bright comparisons) to
use that constant so the value stays consistent in one place.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b8f548c3-37df-4e36-8d84-4e2316067e58
📒 Files selected for processing (14)
examples/web_ui_demo/README.mdexamples/web_ui_demo/css/app.cssexamples/web_ui_demo/css/constellation.cssexamples/web_ui_demo/css/editor.cssexamples/web_ui_demo/index.htmlexamples/web_ui_demo/js/app.jsexamples/web_ui_demo/js/constellation.jsexamples/web_ui_demo/js/divisions.jsexamples/web_ui_demo/js/editor.jsexamples/web_ui_demo/js/inline-edit.jsexamples/web_ui_demo/js/labels.jsexamples/web_ui_demo/js/refs-text.jsexamples/web_ui_demo/js/restructure.jsexamples/web_ui_demo/js/tree.js
A 3px dashed left border on a ~27px row renders as two or three fragments — the "vertical line" reads as broken. The row rule is now solid amber; the dashed not-persisted language stays on the DRAFT · LOCAL chip. Verified via Playwright: draft rows compute `3px solid rgb(217,119,6)`, editing rows `3px solid rgb(37,99,235)` after the 140ms transition, and the blur-save round-trip still persists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The paragraph-creation primitive (#372): inserts a new node immediately after anchorNodeId, under the same parent, shifting later siblings' positions — CSI labels are render-derived, so consumers renumber automatically. nodeType defaults to the anchor's own type; only article, pr1–pr7, and continuation are insertable (never part — CSI keeps its three-part shape — and never note, which materializes via accept-as-note). Generalizes the insertNoteSibling mechanic (reclassify.ts) with the same lock order (spec gate before paragraph FOR UPDATE), the composed edit gate (ADR-018) with optional expectedVersion, and a content_version bump. Responds 201 with the created SpecNode. Contract lockstep in the same commit: openapi.yaml operation (insertParagraph) and the insert_paragraph MCP tool (write tier, contract-map entry) so INV-1/2/2b/3 stay green (ADR-044/045). Immediate consumer: the web_ui_demo WYSIWYG editor's Enter gesture (PR #370) persists its drafts through this. The merge engine's added-op apply (#374) is designed to reuse the same DB primitive. Closes #372 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With #372's endpoint cherry-picked into this branch (430097d), the demo wires the WYSIWYG Enter gesture to real persistence: a draft POSTs as soon as it has non-empty text and a server-side anchor, the insert op is dropped and any ops referencing the draft id (moves on it, drafts chained after it) are remapped to the real node, and chained drafts cascade as their anchors persist. The draft tag and LOCAL PREVIEW chip clear on the follow-up re-render (the persist pipeline's own repaint runs while the op still exists, so the loop requests a deferral-aware render after the remap). Capability-gated as API_FEATURES.paragraphCreate — against an older API build the editor degrades to the previous DRAFT · LOCAL behavior, and the "no endpoint" notice only shows then. Verified live: Enter → type → blur round-tripped to Postgres, the sheet renumbered with the new row in place, no draft residue, zero console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pass 4: the paragraph-creation endpoint from #376 (cherry-picked here as 430097d) is now wired into the Editor — Enter-drafts persist for real via Note for merge sequencing: #376 should land first; this branch then rebases cleanly (the cherry-picked commit deduplicates). |
There was a problem hiding this comment.
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/db/queries/paragraph-insert.ts`:
- Around line 56-59: Add a regression test in
paragraph-insert.integration.test.ts that covers the uppercase UUID spec path
for the paragraph-insert flow, because the normalization fix in the spec
comparison needs a symptom-based assertion. Use the paragraph insertion/query
path that exercises the anchor.spec_id to specId match and verify that an
uppercase specId no longer returns wrong-spec/false-403 when it should succeed,
so the case-insensitive behavior is pinned by the test.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 738d1954-c522-4555-9244-d13158b58bb2
📒 Files selected for processing (21)
examples/web_ui_demo/README.mdexamples/web_ui_demo/js/api.jsexamples/web_ui_demo/js/app.jsexamples/web_ui_demo/js/editor.jsexamples/web_ui_demo/js/features.jsopenapi.yamlsrc/api/contract.integration.test.tssrc/api/paragraph-insert.integration.test.tssrc/api/paragraphs.tssrc/api/router.tssrc/ast/index.tssrc/ast/paragraph-schemas.tssrc/db/index.tssrc/db/queries/paragraph-insert.integration.test.tssrc/db/queries/paragraph-insert.tssrc/db/queries/paragraphs.tssrc/mcp/capabilities.tssrc/mcp/contract-map.tssrc/mcp/paragraph-handlers.tssrc/mcp/paragraph-tools.integration.test.tssrc/mcp/paragraph-tools.ts
✅ Files skipped from review due to trivial changes (2)
- src/db/queries/paragraphs.ts
- examples/web_ui_demo/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
- examples/web_ui_demo/js/editor.js
- examples/web_ui_demo/js/app.js
…raphAfter Two regression/coverage cases CodeRabbit flagged on the insertParagraphAfter DB query: - uppercase specId: pg returns spec_id lowercased while z.uuid() preserves an uppercase input, so the raw string compare would false-403 a valid write. Pins the case-folding fix (verified red without it). - multi-follower shift: anchor the FIRST sibling so two followers shift, not just the single trailing one the existing test covers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex (adversarial review, #370/#376) found insertParagraphAfter only checked that an explicit nodeType was *insertable*, not that it was valid beside the anchor. A part anchor (parent_id NULL) with nodeType:'article' slipped past the insertable check and persisted an article at parent_id = NULL — a root node the renderers mislabel as a PART, breaking round-trip. A part's only valid sibling is another part, which is deliberately non- insertable, so refuse any insert after a part outright. The default path stays safe (defaulted type == anchor type, always a valid sibling). Pinned with a regression test; broader cross-level explicit-type validation (e.g. pr1 beside an article) is a separate CSI-hierarchy decision left as follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two CodeRabbit findings on the editor/constellation demo:
- refRow rendered an unloaded ('ghost') citation as an enabled <button> with no
click handler — a focusable control that does nothing. Render ghosts as a
plain <div> (the .ed-ref CSS is tag-agnostic; is-ghost already sets
cursor:default), keeping the button only when the target is loaded.
- Extract the repeated inbound-citation magic number 3 into a named
HUB_INBOUND_THRESHOLD constant across the three hub/bright checks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex (adversarial review, #370/#376) found insertParagraphAfter only checked that an explicit nodeType was *insertable*, not that it was valid beside the anchor. A part anchor (parent_id NULL) with nodeType:'article' slipped past the insertable check and persisted an article at parent_id = NULL — a root node the renderers mislabel as a PART, breaking round-trip. A part's only valid sibling is another part, which is deliberately non- insertable, so refuse any insert after a part outright. The default path stays safe (defaulted type == anchor type, always a valid sibling). Pinned with a regression test; broader cross-level explicit-type validation (e.g. pr1 beside an article) is a separate CSI-hierarchy decision left as follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Review items addressed (this push):
|
Why
The web_ui_demo had no view where a spec writer actually works in the document — the map and audit views operate around the spec. This ports the "CSI MasterFormat Specification Editor" Claude Design project into the demo as two new workspace views, exercising the paragraph-edit, project-membership, and reference APIs end-to-end.
What
Editor tab — a WYSIWYG writing surface for one section at a time, matching the reference design's pop-into-text interaction:
PATCH /specs/:id/paragraphs/:nodeId; Escape commits; editability-locked paragraphs stay read-only with a chip; the focused row shows the design's EDITING · SAVES WHEN YOU CLICK AWAY state.pr1 ⇄ articlepromotion/demotion, with a hover ⇥ on article bars). Labels are render-derived, so the whole sheet renumbers live.DRAFT · LOCALtag, accept text (stored in the op), never receive server-only affordances, and never leak into Map/Report. Text edits on real paragraphs persist for real, including type-then-Tab/Enter in one gesture (capture-before-blur keeps them from flashing stale).sourceParagraphId— filed as feat(api): include sourceParagraphId in project outbound references #373) or a plain confirm otherwise.POST /projects/:id/specs; flag-for-removal review queue stagingDELETE /projects/:id/specs/:idbehind CONFIRM) and a CITES / CITED BY inspector rail.Constellation tab — the whole project corpus as division solar systems: the umbrella section (
NN 00 00) is the sun (brighter with more cross-division citations) or a black hole when undefined (click → add from masters); planets sized by inbound citations; orphans amber-dashed; flagged sections amber; broken citations as red severed lanes ending in ✕ (amber ghost dot when the target sits in a source library); hover-to-trace; a focused per-division view with portals; lane filters. Clicking a sightline opens the citing paragraph in the Editor.Consistency: one renderer + shared styles mean the spec fixtures render identically across Project Spec Map, Report, and Editor (the reference design's 3px row treatment moved into the shared sheet styles; the citation matcher was extracted to
refs-text.jsshared by read mode and the WYSIWYG editor; every local-state cue — preview chip, draft tags — speaks the same amber-dashed language).Both views read the same client state as the Reference Web, so edits, flags, additions, and removals reshape the map immediately. Demo-only — no changes under
src/(per the demo charter). Design features with no backend support (collaborator simulation, review locks) were deliberately not faked. Backend gaps discovered and filed: #371 (restructure), #372 (paragraph creation), #373 (sourceParagraphId on outbound refs).Three 4-/2-lens adversarial review workflows (33 verification agents total) ran across the three passes; all 19 confirmed findings are fixed — notably SVG hit-lane z-ordering, dense-division layout, flag-queue reconciliation, a Tab-handler ordering bug that defeated the optimistic text mirror, a mid-edit board refresh that could eat in-progress typing, pr6/pr7 labels missing from the demo renderer, a cancelled-dialog path that could poison client state, and a
crypto.randomUUIDsecure-context crash in the advertised LAN mode.Testing
eslint src/ && tsc --noEmit && prettier --check src/)node --test)🤖 Co-authored by Claude Fable 5. Closes #369.
Summary by CodeRabbit