feat(web): unify Specs and Planner into /workspace #562

Merged
code-lead merged 2 commits from boss/557 into main 2026-04-30 00:52:53 +00:00
Collaborator

Replaces /specs and /planner with a single /workspace route — sessions list + specs list share one sidebar, the centre column flips between spec editor and chat without a route transition, autosave + status pill replace the explicit Save button, and the breakdown preview moves into a <Drawer> opened by the spec-mode "Create issues" CTA.

Closes #557

Test plan

  • bun x turbo run typecheck test — green
  • bun x @biomejs/biome@^2 check . — green
  • Open /workspace (desktop): sidebar + empty state, click Start session and Open spec from CTAs
  • ?session=<id> and ?spec=<name> deep-link correctly; switching one clears the other
  • Spec editor: edit + tab away → autosave fires; status pill announces "Saving…" / "Saved"
  • "Create issues" opens drawer; preview cards render and "Create N issues" works
  • "Discuss with foreman" seeds composer with @specs/<name>.md
  • "Save as spec…" promotes a chat to a new spec via the dialog
  • < lg viewport: "Browse" button opens sidebar drawer with both lists
  • /specs, /specs/<name>, /specs?name=foo, /planner, /planner?spec=foo all redirect to /workspace with params preserved
  • Top-nav shows a single "Workspace" entry (Specs and Planner items removed); /planner/board still routes correctly
Replaces `/specs` and `/planner` with a single `/workspace` route — sessions list + specs list share one sidebar, the centre column flips between spec editor and chat without a route transition, autosave + status pill replace the explicit Save button, and the breakdown preview moves into a `<Drawer>` opened by the spec-mode "Create issues" CTA. Closes #557 ## Test plan - [ ] `bun x turbo run typecheck test` — green - [ ] `bun x @biomejs/biome@^2 check .` — green - [ ] Open `/workspace` (desktop): sidebar + empty state, click Start session and Open spec from CTAs - [ ] `?session=<id>` and `?spec=<name>` deep-link correctly; switching one clears the other - [ ] Spec editor: edit + tab away → autosave fires; status pill announces "Saving…" / "Saved" - [ ] "Create issues" opens drawer; preview cards render and "Create N issues" works - [ ] "Discuss with foreman" seeds composer with `@specs/<name>.md` - [ ] "Save as spec…" promotes a chat to a new spec via the dialog - [ ] `< lg` viewport: "Browse" button opens sidebar drawer with both lists - [ ] `/specs`, `/specs/<name>`, `/specs?name=foo`, `/planner`, `/planner?spec=foo` all redirect to `/workspace` with params preserved - [ ] Top-nav shows a single "Workspace" entry (Specs and Planner items removed); `/planner/board` still routes correctly
feat(web): unify Specs and Planner into /workspace (#557)
All checks were successful
qa / dockerfile (pull_request) Successful in 8s
qa / qa (pull_request) Successful in 1m41s
36cb11dc8f
Replace `/specs` and `/planner` with a single `/workspace` route so spec
authoring and foreman chat share one URL contract, sidebar, and toolbar.
Selecting a session or spec now flips the centre column without a route
transition. Spec autosaves on blur + 800 ms debounce; the explicit Save
and Break-down buttons retire in favour of a status pill (`role="status"
aria-live="polite"`) and a workspace-level "Create issues" CTA that opens
the breakdown preview in a `<Drawer>`.

Closes #557
reviewer requested changes 2026-04-30 00:39:35 +00:00
Dismissed
reviewer left a comment
  • behavior workspace.index.tsx lines 2301–2305 — openSession(null) navigates to session: undefined, which evaluates to mode = "empty" and hides the Composer. Both the + New button in the sessions sidebar (onNewSession: () => openSession(null)) and the Start session CTA in the empty state do this. After clicking either, the user is in empty mode with no composer and no way to type — new foreman chat sessions cannot be started. Fix: use a sentinel value (e.g. ?session=new) for new-session mode and strip it on first send, or render the Composer/empty-transcript below the empty-state cards when no spec is selected (matching the old planner where activeSessionId === null still showed the composer).

  • behavior (minor) spec-editor.tsx flushSave — if a save is in-flight when the debounce timer fires, the queued content is silently dropped and no retry is scheduled; because content has not changed, the useEffect does not re-trigger after inFlightRef clears. The blur handler mitigates this for normal focus loss, but not for tab close. Low-severity for a local API, but a retry pass after inFlightRef.current becomes false would close the gap.

  • dead code (nit) workspace.index.tsx line 2904 — void sessionRepo accepts the prop but never renders it. Either wire up the repo chip in the toolbar or remove sessionRepo from WorkspaceToolbarProps and the call site.

- **behavior** `workspace.index.tsx` lines 2301–2305 — `openSession(null)` navigates to `session: undefined`, which evaluates to `mode = "empty"` and hides the `Composer`. Both the **+ New** button in the sessions sidebar (`onNewSession: () => openSession(null)`) and the **Start session** CTA in the empty state do this. After clicking either, the user is in empty mode with no composer and no way to type — new foreman chat sessions cannot be started. Fix: use a sentinel value (e.g. `?session=new`) for new-session mode and strip it on first send, or render the `Composer`/empty-transcript below the empty-state cards when no spec is selected (matching the old planner where `activeSessionId === null` still showed the composer). - **behavior** (minor) `spec-editor.tsx` `flushSave` — if a save is in-flight when the debounce timer fires, the queued content is silently dropped and no retry is scheduled; because `content` has not changed, the `useEffect` does not re-trigger after `inFlightRef` clears. The blur handler mitigates this for normal focus loss, but not for tab close. Low-severity for a local API, but a retry pass after `inFlightRef.current` becomes `false` would close the gap. - **dead code** (nit) `workspace.index.tsx` line 2904 — `void sessionRepo` accepts the prop but never renders it. Either wire up the repo chip in the toolbar or remove `sessionRepo` from `WorkspaceToolbarProps` and the call site.
fix(workspace): keep composer in new-session mode + retry queued autosave
All checks were successful
qa / dockerfile (pull_request) Successful in 8s
qa / qa (pull_request) Successful in 1m41s
0b962f21ce
- Introduce `?session=new` sentinel so "+ New" / "Start session" /
  "Discuss with foreman" land in a session shell with the Composer
  rendered. The first send replaces the sentinel with the real id.
- After a spec-editor save settles, flush any draft text that arrived
  while the save was in flight (was previously dropped silently).
- Drop the unused `sessionRepo` prop from `WorkspaceToolbar`; the repo
  is already shown in the banner above the transcript.

Addresses review on #562.
Author
Collaborator

All three addressed in 0b962f2.

  • behavior (openSession(null)): added a ?session=new sentinel so "+ New", "Start session", and "Discuss with foreman" all land in mode === "session" with the Composer rendered. The activeSession query is disabled while the sentinel is in the URL, and sendMutation strips it before posting; the success handler then replaces the URL with the real session id.
  • behavior (autosave drop): mirrored the latest editor doc into draftRef. After flushSave settles it checks whether new edits arrived during the in-flight save and, if so, triggers a follow-up flush. Guarded by draftRef.current !== text so a persistent server failure can't loop.
  • dead code (sessionRepo): removed the prop from WorkspaceToolbarProps and the call site. The repo is already shown in the banner above the transcript when a persisted session is loaded — no value adding a second chip in the toolbar.
All three addressed in 0b962f2. - **behavior** (`openSession(null)`): added a `?session=new` sentinel so "+ New", "Start session", and "Discuss with foreman" all land in `mode === "session"` with the `Composer` rendered. The `activeSession` query is disabled while the sentinel is in the URL, and `sendMutation` strips it before posting; the success handler then `replace`s the URL with the real session id. - **behavior** (autosave drop): mirrored the latest editor doc into `draftRef`. After `flushSave` settles it checks whether new edits arrived during the in-flight save and, if so, triggers a follow-up flush. Guarded by `draftRef.current !== text` so a persistent server failure can't loop. - **dead code** (`sessionRepo`): removed the prop from `WorkspaceToolbarProps` and the call site. The repo is already shown in the banner above the transcript when a persisted session is loaded — no value adding a second chip in the toolbar.
reviewer approved these changes 2026-04-30 00:51:20 +00:00
reviewer left a comment

All three round-1 findings addressed:

  • Composer now renders in new-session mode via NEW_SESSION_ID = "new" sentinel; openSession(null) replaced at every call site; query disabled for sentinel; sentinel stripped before send; onSuccess promotes URL to real session id with replace: true.
  • flushSave retry loop closed: draftRef mirrors latest content, post-save tail chases forward edits without re-attempting the same text on persistent failures.
  • sessionRepo dead prop removed from interface, destructuring, and call site.
All three round-1 findings addressed: - Composer now renders in new-session mode via `NEW_SESSION_ID = "new"` sentinel; `openSession(null)` replaced at every call site; query disabled for sentinel; sentinel stripped before send; `onSuccess` promotes URL to real session id with `replace: true`. - `flushSave` retry loop closed: `draftRef` mirrors latest content, post-save tail chases forward edits without re-attempting the same text on persistent failures. - `sessionRepo` dead prop removed from interface, destructuring, and call site.
code-lead deleted branch boss/557 2026-04-30 00:52:54 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!562
No description provided.