feat(web): breakdown toolbar + open-in-chat on /specs/$specName #1128
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/agent-hooks!1128
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1120"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes #1120 — wsplit-4 toolbar additions on the focused spec editor.
ListTodo) button opens a right-side<Drawer>hosting the existing<BreakdownPreview>panel. The preview now runs against the live in-memory draft:<SpecEditor>mirrors its body up through a newonDraftChangecallback andfetchBreakdownPreviewforwards it as an optionalspec_contentfield onPOST /architect/breakdown-preview(allowlist gate unchanged). A banner inside the drawer warns when the editor is dirty.MessageSquare) button stages an@specs/<name>.mdmention in a new single-shot Zustand store (useComposerHandoffStore) and navigates to/workspace?session=new. The workspace's draft-restore effect drains the store on mount — no URL leak, no localStorage spill.The legacy
/workspacebreakdown drawer and the in-chat/breakdownslash command both stay; this is purely additive.Acceptance criteria
routes/specs.$specName.tsxtoolbar has a "Breakdown" button (lucideListTodo) that opens<BreakdownPreview>inside a right-side<Drawer>.BreakdownPreviewis invoked with the current spec body — even if dirty, uses the in-memory draft (banner warns the operator when unsaved)./workspaceinvocation (same<BreakdownPreview>component + props).<Drawer>primitive).MessageSquare) navigates to/workspace?session=new.@specs/<name>.mdmention via a transient Zustand key (useComposerHandoffStore) — not a URL param./workspacemount, if the handoff key holds a value, the composer draft is seeded and the key cleared.fetchBreakdownPreviewwith the spec body + renders preview rows.session=newand handoff store holds the expected mention.renderHookproof that the effect consumes a staged handoff on first render.Out of scope (per the issue)
/workspace(operator decision: both stay).Test plan
just qa— typecheck + Biome + sql-layer-check + paraglide-check + 3305 server tests + 1119 web tests all pass./specs/<name>→ click Breakdown → drawer shows preview rows; edit body → reopen → banner appears, preview reflects unsaved edits./workspace?session=newwith composer pre-filled@specs/<name>.md; navigate away and back → no stale handoff.🤖 Generated with Claude Code
Review — APPROVED ✅
CI green. All 10 ACs verified against the diff.
What this does
Adds two toolbar buttons to
/specs/$specName:ListTodo) — opens a right-side<Drawer>hosting<BreakdownPreview>driven by the live in-memory draft (not the last saved disk state). Unsaved-draft banner warns when dirty.MessageSquare) — stages@specs/<name>.mdin a new single-shot Zustand store (useComposerHandoffStore) and navigates to/workspace?session=new. The workspace mount drains the store before the localStorage fallback path.Server gains an optional
spec_contentfield onPOST /architect/breakdown-preview; allowlist gate onspec_pathis preserved even for inline content.Positives
spec_pathallowlist check runs regardless of whetherspec_contentis supplied (both local and remote branches). No bypass possible.liveBody = draftBody || data?.content || ""handles the cold path (editor hasn't firedonDraftChangeyet) without ever sending an empty string when loaded content is available.consume()atomically reads and clears;buildSpecMentionis exported for test assertability; single-slot design is well-motivated in the module comment.consume()runs before thelocalStoragefallback and the earlyreturnprevents the stale-draft path from clobbering the handoff.composerFocusTriggerbump is in the right branch.renderHookdrain proof all land in the same PR as the feature. Good.en.jsonandfr.jsonupdated; French copy is idiomatic./breakdownslash command are untouched.Minor observations (non-blocking)
Focus-trap comment on the query key —
breakdownPreviewQuerykeys onliveBody, so anyonDraftChangewhile the drawer is open would fire a new request. This is safe today because<Drawer>applies a focus trap (perCLAUDE.md) so the user can't type in the editor while the drawer has focus. A one-line comment on thequeryKeyline noting this invariant would protect a future refactor from accidentally regressing it.Missing error-state test — the suite covers loading, success, dirty-draft, and clean-draft, but not
breakdownPreviewQuery.isError. The path is a simple<p>render so the risk is low, but a rejected-mock test would complete the matrix.useState<string>("")— the type annotation is redundant given the string initialiser. Cosmetic only.Neither observation blocks merge.