## Problem
Reported: planning gets stuck in a cycle of retrying and regenerating
after a response was already supplied.
After the user answers a planning question, `submitResponse` pushed the
answer to history but left `session.currentQuestion` pointing at the
just-answered question for the whole next generation. The planning SSE
route's catch-up path re-emits `currentQuestion` to every fresh
connection — and each FN-7946 auto-retry (#2073) opens a fresh
connection. So after any generation error:
1. Auto-retry connects a fresh stream → the server re-emits the
**already-answered** question.
2. The client treats any question event as progress: it **resets the
3-attempt auto-retry budget** and re-shows the answered question.
3. The retry regenerates; if it errors again the cycle repeats with a
fresh budget — an unbounded retry/regenerate loop. Re-answering the
stale question also 409-collided with the in-flight generation, feeding
the same loop.
## Fix
Invariant: `currentQuestion` is only set while the session is genuinely
awaiting user input.
- `submitResponse` clears it the moment an answer is accepted (normal
turns and the deepening checkpoint), while preserving the legacy 200
respond contract on generation failure (the modal ignores the body and
lets the SSE error drive recovery).
- `retrySession` scrubs stale questions persisted by pre-fix builds
before regenerating.
- `buildSessionFromRow` only restores a question when the persisted row
is `awaiting_input`.
- `didSubmitSameAnswer` now compares against the last history entry so
the duplicate-submit 409 message survives.
- Agent onboarding gets the same fix (its SSE route also re-emits
`currentQuestion` on connect); retry now asks the next question instead
of re-asking the answered one.
Surface enumeration: mission and milestone interviews keep questions the
same way but their SSE routes never re-emit on connect, and the
auto-retry budget machinery is Planning-Mode-only — planning +
onboarding were the two affected surfaces.
## Symptom Verification
- **Original symptom:** after answering a question, Planning Mode loops
between "Retrying…" and regenerating, re-showing the already-answered
question, with the auto-retry budget never exhausting.
- **Exact reproduction:** answer a question, have the next generation
fail (stuck watchdog/provider error), let the client auto-retry open a
fresh SSE connection.
- **Assertion it is gone:** new regression suite
`planning-answered-question-reemit.test.ts` asserts `currentQuestion` is
cleared mid-generation, on generation failure, on retry, and on restore
from non-`awaiting_input` rows — so the SSE catch-up path has nothing
stale to re-emit. All 5 tests fail against pre-fix code and pass with
the fix; an onboarding regression test covers the sibling surface.
## Verification
- New regression tests: 5/5 fail on pre-fix code, pass with the fix
(plus 1 onboarding test).
- Existing suites: 137 planning server tests pass (3 failures in
`routes-planning.test.ts` fail identically without this change —
pre-existing on the branch); all 69 `PlanningModeModal.planning-flow`
client tests pass; `tsc --noEmit` clean; `pnpm check:changesets` passes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Made Planning Mode (and related planning controls) lock-free and
multi-tab—no more take-over/active-in-another-tab lock overlays.
* **Bug Fixes**
* Fixed Planning Mode retry/generation flows where already-answered
questions could reappear.
* Ensured answered questions clear immediately and aren’t re-emitted
during session recovery/SSE catch-up.
* Improved session restoration and preserved legacy recovery behavior
when generation fails after an answer.
* **Tests**
* Added regression coverage for the answered-question invariant and
updated existing tests to reflect lock-free behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
## Follow-up: Planning Mode is now multi-tab via DB state (lock-free)
Second commit removes all cross-tab coordination from planning — the
persisted session row is the single source of truth and multiple tabs
can read and interact with the same session:
- **Server:** `/planning/*` routes no longer run `checkSessionLock` or
parse `tabId`; a stale `tabId` from an older client is ignored instead
of 409'd. Subtask/mission interview routes keep their existing lock
behavior.
- **Client:** `PlanningModeModal` drops `useSessionLock`, the
`useAiSessionSync` BroadcastChannel broadcasts,
`sessionTabId`/`lockSessionId` state, and the "Take Control" overlay.
Tabs stay current via the per-session SSE stream plus the global
`ai_session:updated` events `useBackgroundSessions` already consumes;
concurrent writes resolve via the server's generation-in-progress guard
(409).
- **API client:** planning functions lose their `tabId` params.
- **Fix uncovered by the refactor:** the 8s stuck-poll now resolves the
session id inside each tick — the removed lock state was what previously
re-armed the poll after Start Planning resolved the session id.
- Also fixes a pre-existing PG-cutover break in
`planning-generation-cancellation.test.ts` (`getSession` is async).
Verification: 144 client planning tests and 137 server planning tests
pass (the 3 remaining `routes-planning.test.ts` failures are
pre-existing on the branch and fail identically without these changes);
`tsc --noEmit` and eslint clean on changed files; `pnpm
check:changesets` passes. Lock-conflict route tests were rewritten to
assert lock-free semantics, plus a new modal test proving a session
stays fully interactive with no lock acquisition even when another tab
is active.
---
## Follow-up 2: the per-tab session lock is gone entirely
Third commit extends the multi-tab model from planning to **every** AI
interview surface (planning, subtask breakdown, mission interview,
milestone/slice interview) and deletes the lock machinery root and
branch.
**Server**
- Deleted the `/ai-sessions/:id/lock`, `/lock/force`, and `/lock/beacon`
routes.
- Dropped `checkSessionLock` from every
planning/subtask/mission/milestone route (both copies — `routes.ts` and
`mission-routes.ts`). A `tabId` from an older client is ignored, never
409'd; all `tabId` body parsing is gone.
- Dropped `acquireLock` / `releaseLock` / `forceAcquireLock` /
`getLockHolder` / `releaseStaleLocks` from `AiSessionStore`, plus the
`@fusion/core` async helpers (`acquireAiSessionLock` et al) and core's
re-exports.
- Removed `lockedByTab`/`lockedAt` from
`AiSessionRow`/`AiSessionSummary`, the upsert SQL, and all four session
producers.
**Client**
- Deleted `useSessionLock` and the now-orphaned `getSessionTabId` util.
- Removed the Take Control overlay, the "active in another tab" banners,
and `BackgroundTasksIndicator`'s active-elsewhere gate (the confirm
prompt and lock badge — sessions now just open).
- Reduced `useAiSessionSync` to what its own comments already called it
— a low-latency *status* supplement to SSE: no `activeTabMap`,
`broadcastLock/Unlock/Heartbeat`, `owningTabId`, `tab:*` messages, or
stale-heartbeat sweep.
- Dropped `tabId` from every session API client function; removed the
lock CSS.
**Deliberately kept: the two DB columns.** `ai_sessions.locked_by_tab` /
`locked_at` remain as dead, always-NULL columns with a deprecation note.
Dropping them is an irreversible migration, and released binaries still
name those columns explicitly in their upsert — an older install pointed
at the same database would fail every session write. They can be dropped
once no such binary can reach it. No code reads or writes them.
**Verification**: 397 client tests and 137 server planning tests pass
(the same 3 `routes-planning.test.ts` failures are pre-existing —
verified identical on a clean stash); `tsc --noEmit` clean for
`@fusion/core` and `@fusion/dashboard`; eslint clean on all changed
files; the 30 PG `schema-applier` tests pass (they exercise the retained
columns); `pnpm check:changesets` passes. The lock-conflict route tests
and both modal lock tests were rewritten to assert the inverse: routes
and modals stay fully interactive while another tab "holds" a lock, and
the lock API is never called.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Planning Mode confirm prompts can now preserve a user-authored Other answer instead of forcing Yes or No.
- Add an Other option with a textarea to confirm-style Planning Mode questions.
- Submit confirm Other answers with the shared `_other` payload and render them in agent/history formatting.
- Cover confirm Other behavior in modal flow and formatter tests.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-7369-confirm-other.md | 7 ++
.../dashboard/app/components/PlanningModeModal.css | 10 ++
.../dashboard/app/components/PlanningModeModal.tsx | 74 +++++++++---
.../PlanningModeModal.planning-flow.test.tsx | 125 +++++++++++++++++++++
.../planning-interview-formatters.test.ts | 27 +++++
packages/dashboard/src/planning.ts | 10 +-
6 files changed, 234 insertions(+), 19 deletions(-)
Fusion-Task-Id: FN-7369
Fusion-Task-Lineage: 67b4d943-8ff3-4502-bca1-7e74eb95ac06
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Keep the mobile Planning Mode New session CTA reachable while saved sessions scroll independently.
- Bound the mobile planning list shell so only the session list scrolls.
- Preserve the sidebar footer as a non-shrinking bottom action in list view.
- Add CSS contract coverage and a changeset for the published CLI package.
Files changed:
.changeset/fn-7348-mobile-planning-new-session.md | 7 ++++++
.../dashboard/app/components/PlanningModeModal.css | 23 +++++++++++++++++
.../__tests__/PlanningModeModal.css.test.ts | 29 ++++++++++++++++++++++
3 files changed, 59 insertions(+)
Fusion-Task-Id: FN-7348
Fusion-Task-Lineage: 0142e539-3e4c-4ac5-9a44-d02bce2c139b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The global mobile fullscreen modal rule (`.modal-lg`, `.modal:not(.confirm-dialog)`)
forced the embedded Planning shell to 100dvh, overflowing its bounded `.planning-view`
pane. `overflow:hidden` then clipped the footer action buttons and blocked scrolling.
Qualify the mobile embedded override as `.planning-view.open .planning-modal--embedded`
so it outranks the global rule, and re-pin `max-height:100%` so the inner flex scroll
chain works. Adds a CSS regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Planning: .modal max-height:80vh capped the embedded panel ~49px short; .planning-view .planning-modal--embedded { max-height:none } (higher specificity) lets sidebar + detail fill to the view bottom.
- Goals: .goals-card-main had no flex so a long description grew to content width and starved the equal-weight Linked Missions column to ~13px; flex:1 + min-width:0 rebalances them.
- Small margin below the Automations + Goals headers so content clears the divider.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add --view-header-min-height (~61px = space-lg*2 + 28px btn-sm row + 1px divider) and apply via ViewHeader + the bespoke Mission/Planning headers so action-bearing and title-only headers are the same height. Bring the views the sweep missed to canonical: Automations (.automations-embedded-header), Import Tasks (.github-import-modal__embedded-header), Goals (header via ViewHeader + fix main-pane inset so cards align under the title + fill height). Compound Engineering + Research inherit the token automatically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- .planning-view-footer drops its border-top divider + trims padding so the Start-Planning action flows with the content instead of reading as a large bottom footer band.
- List controls bottom padding + quick-add top padding reduced (space-md -> space-xs) so the toolbar sits close to the quick-add box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- .planning-view-footer:empty -> display:none (no reserved space / divider band when empty); legitimate composer + start-planning footers untouched.
- Planning embedded header icon -> var(--todo), title-row gap -> space-sm, h3 -> 1.125rem/600/var(--text), border-bottom divider -> matches mission-manager__header exactly. Mission already had a single divider (no doubled line); documented the invariant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the New Session button's inset-ring outline (now identical to the Missions sidebar CTA); match the Missions sidebar footer padding/border; add a resizable sidebar mirroring MissionManager (drag handle, persisted width, clamp, keyboard); match the header + content padding (space-lg/space-xl, surface bg) so Planning and Missions read as siblings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Left becomes a full-height .planning-sidebar (Missions-matched surface/border/width) with a scrolling list and a bottom-pinned footer; New Session moves to that footer using btn btn-primary (same CTA tokens as the Missions create button — size/color match). Right .planning-detail fills remaining width + full height, so it reads as a true two-pane view. Non-embedded modal presentation unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Right dock: Files first/default; usage/activity-log/git-manager render inline; embedded Git Manager uses a container query (compact horizontal tab strip in the dock, full two-pane in the wide pop-out); Files inline viewer + pop-out.
- Import Tasks layout fits its container (stacked when narrow, two-pane when wide); Import Tasks uses the GitHub mark.
- Planning embeds full-area and works on mobile; planning shows the board WorkflowSwitcher.
- Automations screen uses theme color tokens.
- Workflow selector matches the project selector height/font.
- Left sidebar: uniform spacing across the primary/secondary boundary.
Adds a changeset for @runfusion/fusion (minor).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- List view split sidebar min-width 120->64 so it can be dragged much narrower; titles wrap to two lines in the split sidebar.
- Embedded planning blends like Command Center: no panel shadow/border/outline, transparent background, matching --space-lg padding.
- Insights view header wraps so action buttons drop to a new line instead of overlapping the title.
- Dependency graph no longer appears in the right dock (left-sidebar destination only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Planning mode embedded view drops the modal header/close and uses a plain common title like Command Center.
- Remove the divider before the secondary section (Goals/Evals) in the left sidebar.
- Flatten the Dev Server view header to match embedded-view styling (no card chrome).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a markdown/plain toggle to the planning summary description so users can preview formatted content before creating a task.
- add a Description header toggle that switches between editable plain textarea and rendered markdown preview
- render description preview with react-markdown and remark-gfm while preserving expand/collapse layout behavior
- add preview container styles and spacing adjustments for summary form
- add UI interaction test coverage for markdown toggle behavior and rendered heading/bold content
- document the new markdown/plain description toggle in dashboard guide
Files changed:
docs/dashboard-guide.md | 1 +
packages/dashboard/app/components/PlanningModeModal.css | 18 +++++++-
packages/dashboard/app/components/PlanningModeModal.tsx | 32 ++++++++++---
packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx | 54 ++++++++++++++++++++++
4 files changed, 98 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-5801
Fusion-Task-Lineage: 34972830-7d51-4fcd-bf00-3de8f5aff028
- Broaden mobile media query to include (max-height: 480px) so landscape
phones (which exceed 768 CSS px wide) still render the bottom nav and
mobile board layout instead of desktop horizontally-scrollable columns.
- Guard useMobileKeyboard against pinch-zoom (vv.scale > 1) — Android
Chrome ignores user-scalable=no, and a focused textarea + zoom was
false-positiving keyboard-open and hiding MobileNavBar.
- Read documentElement.clientHeight instead of stale window.innerHeight
when computing keyboard overlap (Android multi-window can leave
innerHeight cached at a wildly different value than the actual layout
viewport — observed 2848 while html was 797).
- Add interactive-widget=resizes-content to the viewport meta so Android
Chrome shrinks the layout viewport with the soft keyboard, matching iOS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds per-subtask priority controls to planning mode with three implementation steps (route priority support, UI controls, and design-token polish), gates research tools behind an experimental feature flag with a new core infrastructure module, and introduces a WhatsApp chat plugin with discovery and
Fusion-Task-Id: FN-3716
This merge includes three features: FN-3337 fixes mobile keyboard handling in the PlanningModeModal by wiring in `useMobileKeyboard` and adding keyboard-aware CSS sizing, with regression tests; FN-3336 recovers the `isGenerating` chat state on session reload to prevent UI flickering; and FN-3338 fix
Fusion-Task-Id: FN-3337
This merge adds idempotent cancel/retry and SSE streaming to the research lifecycle (FN-2999), tightens mobile spacing in the AgentDetailView header cluster (FN-3275), and wires the planning mode into the agent detail header actions (FN-3238). Research orchestrator, store, routes, and the SSE layer
Fusion-Task-Id: FN-3275
The merge completes UX updates for the planning disclosure flow (steps 2–3) in the PlanningModeModal, including CSS enhancements and test coverage for the new behavior.
Fusion-Task-Id: FN-3268
This merge lands multiple enhancements to the planning mode modal UI and related components. Key changes include improved styling for the planning modal and quick chat FAB, CSS token alignment with the design system, plugin slot mount wiring, and updated drag handle placement. The merge also adds ne
Fusion-Task-Id: FN-3258
This merge lands v0.16.0 with several major features: an advanced planning mode modal redesign with collapsible disclosure controls (FN-3222), a durable insight lifecycle system using bounded run executors with quick recovery (FN-3243/FN-3013), and per-project SQLite connection caching for AgentStor
Fusion-Task-Id: FN-3222
Group the planning model selector and depth controls under a collapsible
"Advanced planning settings" disclosure (defaultOpen) in the Planning Mode
modal. Adds a defaultOpen prop to OnboardingDisclosure and tightens the
node.capabilities readonly typing in NodeDetailModal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a draft lifecycle for planning sessions: typing into the
PlanningModeModal textarea now creates a server-side draft after a
300ms debounce, persisted with status='draft' so the user's in-flight
plan survives modal close/reopen and shows up immediately in the
session list.
- planning.ts: new createDraftSession path; persistSession status
union widened to include 'draft'; Session gains an explicit title
field so subsequent updates don't clobber it.
- register-planning-subtask-routes.ts: wires the createPlanningDraft
POST endpoint that the modal calls on debounce.
- ai-session-store.ts: tracks the draft status across queries so the
session list and locks behave the same as any active session.
- legacy.ts: client wrapper for createPlanningDraft.
- PlanningModeModal styling, tests, and ModalReentry coverage updated
for the new flow.
- docs/architecture.md notes the expanded ai_sessions.status lifecycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The nested action row kept "Create Single Task" and "Break into Tasks"
side-by-side on small screens, pushing the primary button offscreen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merges FN-3139 planning comment infrastructure (comment inputs in mission/milestone-slice/planning modals, comment display in conversation history), FN-3119 Quick Chat FAB with slash-triggered skill menu and plugin integration, FN-3117 plugin dashboard views and chat session icons, and FN-3066 plugi
Fusion-Task-Id: FN-3139
This merge lands four features: a `/clear` command for Chat and Quick Chat with session banner fixes (FN-3062), a todo view redesign with restructured rows and improved styling (FN-3063), deterministic peer exchange shutdown with dashboard improvements (FN-3040), and a summary Q&A disclosure feature
Fusion-Task-Id: FN-3078
Move the show/hide archived control out of the sidebar header (where it
competed visually with the New Session button) into a small muted link
in a new sidebar footer below the list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Planning sidebar's `/api/ai-sessions` listing filtered out `complete`
rows, so a session that finished while the modal was closed disappeared
on refresh even though the result was still in SQLite. Add
`?includeCompleted` / `?includeArchived` flags and a new `archived`
column (migration 57) so users can hide terminal sessions on demand
without deleting them; only `complete`/`error` rows are archivable so
live agents can't be orphaned.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Settings, Workflow Steps, Automations (and friends) modals all carried
desktop `min-width: 480-520px` constraints from the resize work. On phones
that pushed them off-screen, and the overlay's 10vh top padding plus a
fractional `height: 80vh` left awkward gaps top and bottom.
Apply the same `@media (max-width: 768px)` full-screen-sheet treatment
that File Browser already had to every resize-aware modal: drop the
overlay padding, set width/height to `100vw`/`100dvh`, zero out the
min-* / max-* constraints, and disable `resize` (touchscreens can't grab
the corner grip anyway).
Also tightened the existing File Browser and GitHub Import overrides to
match (`min-width: 0`, `min-height: 0`, `resize: none`).
For TaskDetailModal the mobile rules go inside the existing first
`@media (max-width: 768px)` block to keep the FN-1331 detail-body
padding regression test happy (it captures from the first 768px media
query greedily).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Sidebar lists all planning sessions for the project (any status) with
inline status badges, relative timestamps, "+ New session", and an
inline delete confirm. Mobile collapses to a single pane with a back
chevron in the header.
- Closing the modal (X / Escape / overlay) no longer cancels the server
session — it stays in the list, resumable. Only an explicit Delete
cancels + removes. Removes the now-redundant minimize button.
- Add re-sync on reopen so a session whose terminal SSE event was missed
doesn't stay stuck on a stale loading view; previously only a hard
reload recovered.
- Persist user-chosen modal width/height across opens via localStorage,
driven by a ResizeObserver on the modal element.
- Theme the modal scrollbars to match the rest of the app.
- Banner dismiss only hides the banner now — it must not delete the
underlying session, since sessions are first-class in the new sidebar.
- Refresh background sessions list on SSE reconnect so the footer "AI N"
pill never gets stuck on tombstoned sessions after a network blip.
- Drop selection + broadcast completion on Create Task / Create Tasks
so the footer count drops in lockstep instead of waiting on SSE.
- Auto-scroll the AI thinking output as new tokens stream in (only when
already pinned to the tail).
- Guard overlay-click dismissal with a mousedown-on-overlay check so
releasing a resize drag outside the modal doesn't close it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The task card timer chip previously fell back through several metrics
(timed duration → workflow runtime → wallclock), so cards showed only a
subset of execution time. For FN-2714 this rendered <1m on the card while
the stats tab reported >2m of workflow runtime.
The chip now reports the sum of [timing]-tagged log events and workflow
step runtime (matching the new "Total execution time" metric in the stats
panel), with live elapsed for in-progress workflow steps. When neither
metric is recorded, the chip is hidden rather than falling back to
wallclock.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Update PlanningModeModal styles to decrease top padding in the question scroll area
- Improve modal content density so question content starts closer to the top
- Track and thread usage trigger bounds from Header through modal management to UsageIndicator
- Render desktop usage details as an anchored popover while preserving existing modal wiring behavior
- Extract usage indicator styling into a dedicated UsageIndicator.css and remove planning-mode style duplication
- Expand header and usage indicator tests to cover anchor rect propagation and popover behavior
- Stabilize CLI bundle output sqlite assertion for workspace test reliability