GitHub #2121/#2307: Windows browser clients intentionally skip first-tab
auto-create, but TerminalModal showed an endless 'Starting terminal...'
spinner whose only escape was the tab-strip '+'. useTerminalSessions now
exposes autoCreateDisabled and the modal renders an explicit 'Start
terminal' action instead. All-inactive persisted tab payloads are also
normalized on restore so the spinner can't wedge on activeTab=null.
Paste: attachCustomKeyEventHandler returning false does not cancel the
browser's default paste, so Ctrl/Cmd+V delivered the payload twice (custom
clipboard read + xterm helper-textarea paste event). preventDefault() makes
the custom read the single path; when the async clipboard API is missing
(non-HTTPS remote access, older Firefox) the handler returns true so the
native paste path works instead of paste being dead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stopGeneration only aborted generations with an activeGenerations record.
A just-started session whose initial turn was still pending (registered
by start-streaming, not yet consumed by a stream connect) returned false
from Stop and the "stopped" generation sprang back to life on the next
stream connect. Stop now discards the pending turn too, and remains
strictly keyed to its session id so stopping one plan never affects other
concurrently generating sessions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A finished plan must never land on a do-nothing screen:
- submitResponse/rewindSession REOPEN a validated session (clear the
terminal marker; the turn's persistSession durably writes it) instead
of rejecting with "already been validated". validateSession remains the
only terminalizer.
- A complete session with no created task resumes into the full plan
review workspace (read plan, Refine/comments, Proceed) instead of the
create-only retry card; task-linked sessions still resume to the task
handoff, preserving the never-rotated one-task-per-session claim.
- The live create-failure screen gains a Back to plan action.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code-review follow-up to e2ee8ba27: the loader overlay now hosts the
streamed-thinking pane, and on short viewports (landscape phones are in
the mobile breakpoint) the centered column could exceed the overlay and
clip the Stop button and thinking output. The overlay scrolls instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workspace loader that covers all follow-up turns (next question,
refine, contextual comments, question regeneration) showed only a spinner
and elapsed time — streamed thinking/output was visible only on the first
pre-summary turn. Reuse the initial loading view's thinking container and
toggle there, and mirror the generation-activity label instead of a
hardcoded "Generating plan…".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends the Planning Mode no-active-question fix to the other three
interview lanes: a LIVE session (no summary yet) that receives a
submission with no active question now reprompts the agent to continue
the interview and ask a fresh question — carrying the submitted input as
context — instead of throwing "No active question in session". Completed
interviews (summary present) still reject late submissions, preserving
the existing contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refining a plan (or submitting any input) while the session had no active
question — e.g. after a failed retry cleared summary/currentQuestion —
threw InvalidSessionStateError("No active question in session") at the
operator. Now the interview continues instead:
- The refine and contextual-comment branches no longer require
session.summary; they fall back to a running summary rebuilt from
persisted history.
- A submission with no active question reprompts the agent via
formatQuestionRegenerationForAgent to produce a fresh option-driven
question, carrying the submitted operator input along as context.
- The Planning modal forwards no-question submissions to the server
(loading view + SSE) instead of dead-ending with a local error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code-review follow-up to 716e69862: the terminal-error reconcile ran after
the Last-Event-ID replay, so a reconnecting client received a buffered
error event from the replay AND again from the reconcile block (double
onError, duplicate auto-retry triggers). The reconcile now runs before the
replay and skips it for terminal sessions, writing the newest buffered
error event (or a fresh broadcast when the bounded buffer evicted it)
exactly once, gated on lastEventId.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phone fixed bottom:overlap left the panel under the soft keyboard (layout vs
visual viewport). While open on mobile, remeasure visualViewport and pin with
top/max-height so the entry box stays visible when the keyboard opens.
Center the jump-to-latest chip with margin-inline auto instead of
transform:translateX(-50%) so global .btn transform transitions and
:active scale cannot shift it sideways in Quick Chat and full Chat.
Provider errors thrown between persistSession("generating") and the turn's
own error handling (agent rebuild in ensureSessionAgent, history replay,
legacy sync createSession first turn) escaped to the route and left the
session row "generating" forever with no error, no SSE event, and no
watchdog — the modal hung on "Thinking/Generating plan" because its SSE
reconnect loop and 8s poll both treat a persisted "generating" row as
healthy.
- submitResponse/retrySession/createSession now convert any non-abort
escape after entering "generating" into the standard persisted retryable
error + SSE error broadcast before rethrowing.
- The SSE stream route reconciles settled/stranded sessions on connect
(reconcileStalePlanningGeneration): a terminal error is replayed and the
stream closed; a "generating" session with no live/pending turn past the
watchdog window is converted to a retryable interrupted error.
- Provider failures on the JSON reformat retry now surface as themselves
instead of a misleading "no valid JSON" parse error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snapshot the selected quote on Add-comment pointerdown and freeze it for the
composer so mobile selectionchange can no longer clear the quote and unmount
the entry box as soon as it opens.
Add/Submit no longer run on pointerdown (that closed the box immediately).
Match Start Planning: preventDefault keeps focus for the click, then click
commits the action once.
On phone/tablet the first tap of Add/Submit blurred the composer, closed the
keyboard, and shifted the fixed panel so the click was lost. Commit the action
on pointerdown (same pattern as Refine Apply) so one tap is enough.
Pin the selection comment editor as a fixed panel on tablet and phone, and
lift it with visualViewport keyboard metrics so the first focus no longer
hides the form under the OS keyboard (or off-screen on tablet).
Tablet plan-actions no longer use flex nowrap (which put Add comment beside
Refine/Proceed). Keep the two-column grid so the selection control is a
full-width first row, and pin MessageSquarePlus to the same 16px/token size
as mobile.
Phone no longer pins Add comment under the action rail as a fixed bar. It
uses the same full-width in-flow footer row as tablet, above Refine and
Proceed. The composer stays fixed when open.
Reopening a complete plan no longer shows "still being prepared" when the
validated payload marker is missing. Generation Retry that hits
"already been validated" refreshes into create-retry or plan review.
Tablet (≤1024px) now uses the action-rail Add-comment control as a full-width
row above Refine/Proceed instead of the document-end trigger. Phone keeps the
fixed bar above the mobile nav; desktop keeps the in-document control.
Align MissionAutopilot expectations with autonomy-audit attribution: updateMission
carries the system actor options, and autopilot_disabled is recorded on the
mutation instead of a separate logMissionEvent mirror.
- usage-limit-detector + provider-health-monitor: make three bare listTasks()
callers explicit with { slim: true }, restoring the architecture-hot-paths
contract (they only read scalar pause/column/model-provider fields).
- pg-test-harness beforeEach: wipe <rootDir>/.fusion/tasks after TRUNCATE ...
RESTART IDENTITY so filesystem isolation matches the id reset; stale task
dirs from prior tests no longer collide with reused IDs (fixes
store-reservation-atomicity rollback assertions).
Plan review Add-comment controls now track document-level selectionchange so
they appear as soon as text is selected and dismiss when the selection ends.
On mobile the trigger and composer are fixed above the nav (with width auto)
so operators no longer need to scroll to reach them.
## Problem
With `pushAfterMerge` enabled (and `mergeStrategy` other than
`pull-request`), if `origin/<integration-branch>` advances externally
between the local squash-merge and the push, the divergence path opens a
clean-room `git pull --rebase` and an AI agent resolves and stages the
conflicts — but the flow could end there: no `git rebase --continue`, no
push, and no surfaced error.
Because finalize runs *before* the push, the task is already `done`, so
a reviewed, approved merge is silently left container-only, and every
subsequent merge on the project stalls the same way. Separately, an
abort mid-push (`MergeAbortedError`) was swallowed with only a
process-log warning — no task-log entry, no run-audit event.
## Change
- **Deterministic regression coverage** for the conflicting-divergence
path (real-git fixture) proving the rebase runs to completion and the
push lands (refs converge), plus abort/termination scenarios.
- **Recovery-branch safety net:** before the clean-room rebase starts,
the pre-rebase local squash is force-pushed to a per-task remote branch
`fusion/<task-id>-stranded`, so approved content is never container-only
— even across process death or abort. Deleted after a successful target
push; retained on failure/abort as the recovery source.
- **Never-silent outcomes:** every non-pushed outcome (failure or abort)
writes a durable task-log entry and a `push:origin` run-audit event. The
audit contract now documents `push:origin` as polymorphic (dashboard
Smart Push vs. automated post-merge push) and enumerates the automated
path's outcomes, including the new `"aborted"` shutdown case.
- **Cleanup hardening:** `isRebaseInProgress` now probes Git's
worktree-specific `rebase-merge`/`rebase-apply` state directories
(async, timeout-guarded) so a completed rebase can't receive a spurious
second `--continue`; unfinished rebases are cleaned up.
Out of scope by design: withholding the "merge confirmed" state until
the push succeeds — the `FNXC:MergePush` invariant ("a push problem can
never park or roll back a landed merge") is deliberate; the recovery
branch + surfacing satisfy the data-preservation intent without breaking
it.
## Files
`packages/engine/src/merger-ai.ts`, `packages/engine/src/merger.ts`,
`packages/engine/src/run-audit.ts`, new/updated tests under
`packages/engine/src/__tests__/`, `docs/settings-reference.md`,
`docs/dashboard-guide.md`, `AGENTS.md`, and a labeled changeset.
## Validation
`tsc --noEmit` clean; engine divergence + merger suites pass (41 tests);
rebased onto current `main` with no conflicts.
---
_Developed with Claude Code, under human supervision and review._
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented approved post-merge pushes from becoming stranded when the
remote diverges by using a recovery-branch workflow and safer cleanup.
* Improved behavior and reporting when pushes are aborted or fail after
merge, including clearer non-fatal status and audit outcomes.
* **Documentation**
* Expanded push-after-merge and dashboard Smart Push documentation with
recovery-branch and `push:origin`/`push:recovery-branch` outcome
semantics.
* **Tests**
* Added end-to-end regression tests for divergent/conflicting AI
push-after-merge flows, including abort and worktree cleanup
verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Victor Cano <victortroz@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- synchronize all five secondary app locale catalogs with the authored
English key structure
- restore fallback entries for heartbeat controls, release-channel
settings, report targeting, and task provenance labels
- add a patch changeset for the catalog parity fix
## Test Plan
- `pnpm i18n:status`
- `pnpm --filter @fusion/i18n test` (29 tests)
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/AgentsView.test.tsx --silent=passed-only
--reporter=dot` (138 tests)
- `pnpm check:changesets`
- `pnpm build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved localization consistency for heartbeat controls, capability
settings, release-channel configuration, reporting guidance, and task
provenance details.
- Added missing translation entries across Spanish, French, Korean,
Simplified Chinese, and Traditional Chinese locales.
- Untranslated values now fall back cleanly to the authored English text
structure, preventing missing or inconsistent labels in supported
interfaces.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The Workflow Steps bullet in README.md links to
`./docs/workflow-steps.md#workflow-declared-optional-steps`, but the
target heading is `#### Workflow-declared optional steps
(`optional-group` nodes)`, which GitHub slugifies (including the
parenthesized text) to
`#workflow-declared-optional-steps-optional-group-nodes`. As a result
the link lands at the top of the page instead of the intended section.
The correct anchor is already used by a self-link elsewhere in the same
file (docs/workflow-steps.md, the "Optional groups and default-on gates"
row), confirming the expected slug.
This is a one-line fix: append `-optional-group-nodes` to the anchor in
README.md.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Updated the Workflow Steps documentation link to direct readers to the
more specific “optional group nodes” section.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Every PR's blocking checks were dominated by redundant full rebuilds,
not by tests. Measured on recent runs: the Gate job spent ~6 of its ~7.5
min on a cold `pnpm build` (the exact-key dist cache missed on virtually
every PR) for ~45s of actual boot smoke + gate tests; Build ran ~8 min
and Typecheck ~4 min, both fully cold every time. Expected end state
once the warm job has run on main: all four blocking checks in roughly
2–4 min wall-clock.
### Gate job
- New `gate-dist-*` cache namespace with `restore-keys`, additionally
caching `.fusion/cache/plugin-build-cache.json` (build-workspace's
per-package content-hash skip cache) and `packages/cli/dist`. The
always-run `pnpm build` reconciles a near-match restore by content hash
and rebuilds only the packages the PR touched. This is safe *because*
the gate builds after restoring — the shard jobs' "no restore-keys" rule
(FN-4232/FN-4605 stale-dist incidents) still stands there, since they
consume dist without building.
- `FUSION_CLI_FULL_PACKAGE=0` on the gate build: skips the multi-minute
CLI desktop/plugins/DTS packaging tail nothing in the gate consumes
(same shape `pnpm verify:fast` proves locally). Full CLI packaging
coverage stays blocking in the Build job.
### Build job
- Restore-only tap (`actions/cache/restore`) of the same warmed cache.
Restore-only because this job runs FULL CLI packaging (`CI=true`) and
saving that shape would swap the cache's canonical fast-CLI contents out
from under the Gate job. Its distinctive coverage is preserved:
`ensureFullPackageCliPlanned` force-plans the CLI in full mode
regardless of cache state.
### Typecheck job
- Caches per-package tsc incremental buildinfo — self-validating (tsc
hashes every input against it and re-checks whatever changed), so
`restore-keys` is correctness-neutral by construction.
- **Fixes a real incrementality bug:** `tsconfig.json` and
`tsconfig.app.json` in the dashboard both inherited
`${configDir}/dist/.tsbuildinfo` from `tsconfig.base.json`, so the two
typecheck programs clobbered each other's buildinfo and re-checked the
full program every run — incremental typechecking never worked for the
dashboard, in CI or locally. `tsconfig.app.json` now writes
`dist/.tsbuildinfo-app`. Measured: dashboard typecheck 44s cold → 5.6s
warm.
### Warm job (full-suite.yml, push to main)
- New `warm-gate-build-cache` job saves both caches from main on every
push. Caches saved on a PR merge ref are invisible to other PRs, so
without this every PR's *first* run would still build/check cold.
## Guardrails
`ci-workflow.test.ts` pins the coupled invariants so they can't drift
apart silently:
- restore-keys requires the reconciling `pnpm build` after restore,
before boot smoke
- the mtime-defeating seed step stays exact-hit-only
- byte-identical cache path lists between the Gate/Build/warm blocks
(actions/cache versions caches by path list — a drifted list makes
caches mutually invisible)
- Build stays restore-only and must NOT opt out of full CLI packaging
- Typecheck cache shape + the distinct dashboard app buildinfo path
## Notes
- First PR runs after this lands still build cold until the warm job has
run once on main.
- No changeset: CI config + test-only per AGENTS.md.
## Verification
- `ci-workflow.test.ts` + `package-config.test.ts`: 106 tests pass
- Dashboard typecheck run twice locally: 44s cold → 5.6s warm, both
`.tsbuildinfo` and `.tsbuildinfo-app` written, exit 0
- Cache block path/key parity verified programmatically across both
workflow files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance**
* Improved CI build and type-check performance through incremental
caching.
* Added cache warming from the main branch to speed up pull request
checks.
* Enabled faster CLI packaging during gate validation while retaining
full packaging coverage elsewhere.
* **Bug Fixes**
* Prevented dashboard TypeScript build information from being
overwritten, preserving incremental type-checking reliability.
* **Tests**
* Added coverage to verify CI cache behavior, build ordering, cache
paths, and packaging modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Proceed with plan called /api/planning/create-task without the legacy
/validate step, so the persisted AI session stayed awaiting_input and the
session list/needs-input banner kept advertising a finished session. The
create-task route now terminalizes the session via validateSession on every
path that ends with a created task, including alreadyCreated reconciliation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The root CHANGELOG's beta sections (and the matching GitHub prerelease bodies, now edited in place) carried full-cycle aggregates because every beta distilled all preserved pre-mode changesets. Rebuilt each section from packages/cli/CHANGELOG.md's incremental per-beta entries so each beta lists only its own changes; future releases are handled by the channel-scoped selection in release.mjs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep all selected GitHub issue actions touch-safe and visible on a single mobile row.
- Group detail action controls separately from the comment composer and make their mobile tracks shrinkable.
- Add responsive browser-smoke coverage at 320px, 390px, and 412px plus modal structure tests.
- Document the mobile behavior and add a patch changeset.
Files changed:
.changeset/fn-8548-mobile-github-import-actions.md | 7 +
docs/dashboard-guide.md | 8 +-
.../dashboard/app/components/GitHubImportModal.css | 42 +++++-
.../dashboard/app/components/GitHubImportModal.tsx | 80 ++++++------
.../__tests__/GitHubImportModal.test.tsx | 50 ++++++++
packages/dashboard/app/styles.css | 11 +-
.../dashboard/scripts/browser-layout-smoke.mjs | 142 ++++++++++++++++++++-
7 files changed, 290 insertions(+), 50 deletions(-)
Fusion-Task-Id: FN-8548
Fusion-Task-Lineage: 8fab6a1d-0ca9-4246-9707-e1fc84ca58e5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Render complete mission hierarchy details from the fn_mission_show agent tool.
- Format mission, linked-goal, milestone, slice, and feature metadata with IDs and statuses
- Link features to their tasks and bound verbose acceptance and verification text
- Cover populated and empty hierarchy responses with regression tests
- Add a patch changeset for the agent lookup fix
Files changed:
.changeset/fn-8540-mission-show-hierarchy.md | 7 ++
.../src/__tests__/agent-mission-tools.test.ts | 68 +++++++++++++++++++
packages/engine/src/agent-tools.ts | 77 +++++++++++++++++++++-
3 files changed, 150 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-8540
Fusion-Task-Lineage: f2282226-3f4e-4d9f-bd8e-d18ad9639c03
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Pre-mode preserves consumed changeset .md files, so every beta's distilled notes and GitHub prerelease body aggregated the entire cycle since the last stable (v0.73.0-beta.4 shipped the full 0.72.0→0.73.0 aggregate). Betas now distill only changesets not yet recorded in pre.json's consumed ledger, and fail loudly when a beta would ship nothing new. Stable promotion still feeds the full preserved set, keeping its notes an explicit rollup of every beta in the cycle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep Planning Refine and Proceed controls visible across mobile plan-review hosts.
- Make the plan document pane the responsive scroll owner while preserving its action rail.
- Cover portrait and short-landscape embedded and modal layouts with CSS and browser tests.
- Add a patch changeset for the mobile planning action fix.
Files changed:
.changeset/fn-8537-mobile-planning-actions.md | 7 ++++++
.../dashboard/app/components/PlanningModeModal.css | 25 ++++++++++++++++++++++
.../__tests__/PlanningModeModal.css.test.ts | 17 +++++++++++++++
.../src/__tests__/planning-browser-e2e.test.ts | 20 +++++++++++++++--
4 files changed, 67 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-8537
Fusion-Task-Lineage: 7a53aa74-859d-4c76-bf26-ab6ea72873dd
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Port the planning turn-admission invariant (FNXC:PlanningTurnAdmission,
2026-07-22) into the Compound Engineering orchestrator: at most one turn
(opening/answer/resume-rehydration) is admitted per CE session, reserved
synchronously and held until the turn settles — a re-entered mobile view
re-submitting a turn now gets CeTurnInProgressError (HTTP 409) instead of
displacing the in-flight turn's live agent, which surfaced as "Failed to
parse agent response: AI returned no valid JSON". cancel()/discard()
force-clear the reservation; releases are token-scoped so a stale release
can't drop a newer turn's slot.
In the engine interactive-ai-session seam: bump the reformat retry from
one to two attempts (non-Anthropic default models comply less reliably
with the JSON-only protocol), and log every failed parse with a bounded
raw-response snippet plus resolved provider/model — including a distinct
empty-assistant-message marker — so support can diagnose these reports
without a repro.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Release a settled retry token so a later planning error can start the next bounded attempt.
- Clear only the matching retry owner before scheduling its successor.
- Cover distinct stream errors after retry settlement.
- Add a patch changeset for the recovery fix.
Files changed:
.changeset/fn-8536-planning-retry.md | 7 +++++++
packages/dashboard/app/components/PlanningModeModal.tsx | 10 ++++++++++
.../__tests__/PlanningModeModal.planning-flow.test.tsx | 5 +++++
3 files changed, 22 insertions(+)
Fusion-Task-Id: FN-8536
Fusion-Task-Lineage: cccb0793-390e-4bdb-b459-939760e644bb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
After a hard host crash (SIGKILL, power loss), postmaster.pid survives with no
postmaster behind it. The optimistic join handed every subsequent boot a URL to
the dead port, so the dashboard could never start again without a manual pid
delete. Probe the recorded pid with signal 0: provably dead (ESRCH) rebuts the
live-lock presumption and the boot takes an owned start — PostgreSQL itself
re-validates and reclaims the stale lock file, so a recycled live pid keeps the
old join-then-fail behavior and a genuinely live postmaster still surfaces the
lock collision we already join on. EPERM counts as alive (fail-closed).
Verified end to end: real cluster started, postmaster SIGKILLed leaving the pid
file + interrupted WAL, fresh lifecycle detected the stale lock, ran an owned
start, and crash recovery preserved the marker row.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reloading Planning re-entered the generation view ("Generating initial
plan…", Stop button, elapsed timer, 8s watchdog) while merely fetching a
persisted session. A new session_loading view state renders a neutral
"Loading session…" spinner during hydration; the generating view is
reserved for sessions the server reports as generating. Unrecognized
persisted session shapes now land in the retryable error view instead of
spinning forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The runner stage installed the application at /project, which was also the
documented bind-mount point — mounting a host project there shadowed the CLI
and the container exited with MODULE_NOT_FOUND on packages/cli/dist/bin.js.
- Install the app under /app and run the entrypoint by absolute path.
- Reserve /workspace (empty in the image, container workdir) as the project
mount point.
- Update docs/docker.md: mount at /workspace, and document that embedded
Postgres/global state lives in /home/node/.fusion with a named-volume
example so persistence actually captures it.
Verified: image builds; `docker run -v host:/workspace` boots, embedded
Postgres initializes, /api/health returns ok.
Fixes#2414
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>