## Summary
Running more than one fusion process on a host (multiple dashboards/CLIs
across worktrees, all attaching `~/.fusion/fusion-central.db`) could
crash a `node` process at random — instantly, with no JS stack and
nothing in the logs. This happened 3 times in 3 days on one machine.
After this change those processes coexist without crashing.
The crash was an OS-level `SIGBUS` (`EXC_BAD_ACCESS`, `FS pagein error`
/ kernel `cluster_pagein past EOF`) inside SQLite's `walIndexReadHdr`.
In WAL mode every connection coordinates through a memory-mapped `-shm`
wal-index; on macOS/APFS, when one process resizes/rebuilds that file
during a checkpoint while another has it mmap'd, the reader faults on
the now-out-of-bounds page. A hardware memory fault can't be caught by
`node:sqlite` or JS, so the whole process dies.
The fix switches the central DB to `journal_mode = DELETE` (rollback
journal), which uses no `-shm` memory map and coordinates cross-process
access via POSIX byte-range locks instead — removing the faulting
surface entirely while keeping multi-process access. The existing
`busy_timeout` absorbs the writer serialization that DELETE mode trades
for WAL's reader/writer concurrency. Per-project DBs (`db.ts`) are
intentionally left on WAL: they're single-process-per-project and don't
hit this cross-process fault. SQLite migrates the existing WAL database
on first open (checkpoints `-wal` into the main file and removes
`-wal`/`-shm`), so there is no data loss.
## Test plan
- New regression tests in `central-db.test.ts` assert the central DB
reports `journal_mode = delete` (not `wal`) and that **no `-shm`
wal-index file is ever created** even after write traffic — i.e. the
exact faulted surface is gone.
- All 6 central-DB suites pass (221 tests); `@fusion/core` typechecks
clean.
---
[](https://github.com/EveryInc/compound-engineering-plugin)

<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1752">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved stability when multiple dashboards or CLIs run on the same
machine.
* Switched the local database to a safer journaling mode to reduce rare
crash issues on macOS/APFS.
* Prevented creation of extra database side files during normal
operation, while keeping data durability and lock-based coordination in
place.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Verify the WAL->DELETE journal-mode switch instead of discarding exec()'s
result. During a rolling upgrade a lingering WAL holder blocks the exclusive
lock the switch needs, so SQLite either throws SQLITE_BUSY or no-ops and
returns "wal". Capture both outcomes and warn loudly so the residual -shm
SIGBUS surface is observable, rather than silently swallowed.
- Do not rethrow: the condition is transient and self-healing (the next start
after the last WAL holder exits migrates cleanly); hard-failing would make the
central DB unopenable during the very upgrade window it describes.
- Add a migration-path regression test (a WAL holder blocking the switch) that
the prior fresh-DB-only tests did not cover.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Problem
The latest release **v0.47.0 published 0 binaries**, and every desktop
build leg has been failing.
Two independent bugs in the release pipeline:
### 1. `github-release` job crashed → no binaries published
The job downloads artifacts but never checks out the repo. A step added
2026-06-24 (`Extract release notes from CHANGELOG`) does
`fs.readFileSync('CHANGELOG.md')`, which threw `ENOENT: no such file or
directory` and failed the whole job — so v0.47.0 shipped **0 assets**
even though every bun build leg succeeded. (v0.45/v0.46 predated this
step and still published 21 assets each.)
### 2. Desktop Windows EXE / macOS DMG legs failing
`packages/desktop/src/local-runtime.ts` dynamically imports
`@fusion/engine`, but engine was missing from the esbuild externals list
(only `@fusion/core` and `@fusion/dashboard` were there). esbuild
followed the import and tried to bundle engine's transitive `node-pty`
native binaries:
```
X [ERROR] No loader is configured for ".node" files:
.../@homebridge/node-pty-prebuilt-multiarch/build/Release/pty.node
```
## Fix
- **`.github/workflows/release.yml`** — sparse-checkout `CHANGELOG.md`
in the `github-release` job, and harden the notes script to fall back to
a plain `Release vX.Y.Z` body instead of crashing the publish.
- **`packages/desktop/scripts/build.ts`** — externalize `@fusion/engine`
alongside the other workspace packages; it resolves from `node_modules`
at runtime, same as `core`/`dashboard`.
## Verification
- `pnpm build` runs clean locally — `dist/main.js` (623 kB) builds with
no `.node` loader error.
- YAML validated; `pnpm check:changesets` passes.
## Changeset
`"@runfusion/fusion": patch` (category `fix`) — restores
binary/installer publishing.
## Follow-up
These take effect on the next release. To backfill v0.47.0's missing
binaries, after merge delete + re-push the `v0.47.0` tag (the
tag-triggered run uses the workflow file at the tag).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed release publishing so binary and desktop installer artifacts are
published correctly again.
* Improved release note generation to fall back gracefully when
changelog details aren’t available, preventing failed releases.
* Updated desktop app bundling to avoid packaging native dependencies
incorrectly, reducing build and packaging issues.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The central DB (~/.fusion/fusion-central.db) is opened concurrently by every
fusion process on a host. In WAL mode those connections coordinate through a
memory-mapped `-shm` wal-index; on macOS/APFS a reader takes a SIGBUS
(walIndexReadHdr / `cluster_pagein past EOF`) when another process resizes it
mid-checkpoint, killing the node process with no JS stack or log. Observed 3x
in 3 days. Switch the central DB to journal_mode=DELETE, which uses no `-shm`
mmap and coordinates cross-process access via POSIX byte-range locks instead;
busy_timeout absorbs the added writer serialization. Per-project DBs keep WAL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
local-runtime.ts dynamically imports @fusion/engine, but it was missing from the
esbuild externals list (only @fusion/core and @fusion/dashboard were there). esbuild
followed the import and tried to bundle engine's transitive node-pty native .node
binaries, failing with "No loader is configured for .node files" — which broke every
desktop Windows EXE and macOS DMG build leg. Externalize @fusion/engine like the other
workspace packages; it resolves from node_modules at runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The github-release job downloaded artifacts but never checked out the repo,
so the new "Extract release notes from CHANGELOG" step threw ENOENT and failed
the whole job — v0.47.0 published 0 binaries despite every bun build succeeding.
Sparse-checkout CHANGELOG.md and harden the notes script to fall back to a plain
release body instead of crashing the publish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refresh the weekly test velocity baseline with current timing evidence.
- Update the rendered baseline report with the latest capture timestamps and velocity deltas.
- Replace stale slowest-file timing rows with the refreshed snapshot entries.
- Append the latest velocity history sample used by the weekly report.
Files changed:
docs/test-velocity-baseline.md | 61 +-
scripts/test-timings.json | 1772 +++++++++++++++++++++++++++++-------
scripts/test-velocity-history.json | 112 +++
3 files changed, 1580 insertions(+), 365 deletions(-)
Fusion-Task-Id: FN-6968
Fusion-Task-Lineage: 940f3581-3650-4feb-8730-d6dbb760e30c
Add a GitHub issue and pull request picker that seeds new task prompts from detected remotes.
- Add remote-aware GitHub issue/PR selection to the New Task modal.
- Generate focused task descriptions for selected issues and pull requests while confirming before replacing user text.
- Style and document the compact picker and cover loading, errors, remote selection, and overwrite behavior in tests.
- Add a changeset for the published Fusion CLI package.
Files changed:
.changeset/fn-6946-github-reference-picker.md | 7 +
docs/dashboard-guide.md | 2 +
packages/dashboard/app/components/NewTaskModal.css | 53 ++++
packages/dashboard/app/components/NewTaskModal.tsx | 297 ++++++++++++++++++++-
.../app/components/__tests__/NewTaskModal.test.tsx | 199 +++++++++++++-
5 files changed, 555 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-6946
Fusion-Task-Lineage: e871827c-dfe5-4fc3-a525-c1161639d306
Mobile bottom navigation now keeps balanced horizontal spacing without disturbing existing viewport compensation.\n\n- Add tokenized inline padding to the fixed mobile nav bar while preserving ICB and safe-area behavior.\n- Cover symmetric side spacing in mobile nav CSS and component layout tests.\n- Add a patch changeset for the published CLI package.\n\nFiles changed:\n .changeset/fn-6999-mobile-nav-side-spacing.md | 7 +++++++\n packages/dashboard/app/__tests__/mobile-nav-bar-css.test.ts | 10 ++++++++++\n packages/dashboard/app/components/MobileNavBar.css | 5 +++++\n .../dashboard/app/components/__tests__/MobileNavBar.test.tsx | 8 ++++++++\n 4 files changed, 30 insertions(+)
Fusion-Task-Id: FN-6999
Fusion-Task-Lineage: 91bd2174-8e5d-4227-a035-ff621e309719
Reclaims task detail padding so inline diffs have more readable width on compact screens.
- Expand the compact Changes file list to consume detail-body padding without page overflow.
- Add mobile breakpoint coverage that matches the detail-body padding contract.
- Add regression tests for the phone-width inline diff surface and CSS rules.
- Add a patch changeset for the published Fusion CLI package.
Files changed:
.changeset/fn-6997-diff-panel-width.md | 7 ++
.../dashboard/app/components/TaskChangesTab.css | 20 ++++-
.../components/__tests__/TaskChangesTab.test.tsx | 98 +++++++++++++++++++++-
3 files changed, 121 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-6997
Fusion-Task-Lineage: a7f21c0d-89a8-430e-a84c-341a10af4a4a
Align the Quick Chat and Terminal footer launchers so they read as matching peer controls.
- Inherit the footer font and color contract for the Terminal footer launcher.
- Preserve usable hover, focus, padding, and scripts chevron behavior in the footer variant.
- Document the footer Quick Chat launcher placement and add release notes.
- Expand status bar tests to lock the shared footer launcher styling.
Files changed:
.changeset/fn-6959-footer-launcher-style.md | 7 ++
docs/dashboard-guide.md | 2 +-
.../dashboard/app/components/ExecutorStatusBar.css | 2 +-
.../dashboard/app/components/TerminalLauncher.css | 37 +++++++-
.../__tests__/ExecutorStatusBar.test.tsx | 105 +++++++++++++++++++--
5 files changed, 138 insertions(+), 15 deletions(-)
Fusion-Task-Id: FN-6959
Fusion-Task-Lineage: 539307cb-dc81-441f-8baf-8823826e2195
Add stale timing context to the weekly test velocity baseline so W26 regressions are not misattributed.
- Report timing snapshot age and missing slowest-file paths before the slow-file table.
- Persist timing metadata notes in velocity history and regenerate the W26 baseline report.
- Cover stale and missing timing metadata warnings with node:test assertions.
Files changed:
docs/test-velocity-baseline.md | 23 ++-
scripts/__tests__/test-velocity-baseline.test.mjs | 26 +++
scripts/test-velocity-baseline.mjs | 65 +++++-
scripts/test-velocity-history.json | 231 ++++++++++++++++++++++
4 files changed, 330 insertions(+), 15 deletions(-)
Fusion-Task-Id: FN-6966
Fusion-Task-Lineage: bccac6c2-11c1-4295-b3af-b77dab1748dc
## Summary
Re-captured all README showcase GIFs and screenshots from the running
Fusion dashboard, refreshed across every theme including the new Ember
variant.
## What changed
**Media (32 assets: 24 refreshed, 8 new):**
- Dashboard board + Agents roster shots (Tokyo Night + Ember)
- Command Center, Workflows, Agent Chat, Chat Rooms, Agent Mail GIFs
refreshed across Tokyo Night, Shadcn Light, Shadcn Dark Gray, and Ember
- 6 mobile screenshots (board, agents, missions, command center, chat,
chat list)
- All captures use a 1600x1000 viewport (16:10) at deviceScaleFactor 1
to match the original committed media and avoid vertical squashing from
vh-based CSS
**README.md:**
- Added a "board & agent team" showcase section with live dashboard and
agents shots
- Expanded every theme table from 2 columns (Light / Dark Gray) to 3
(Light / Dark Gray / Ember)
**demo/seed.ts:**
- Extended to seed chat messages and agent mail (5 agents, a direct chat
session, a #leads multi-agent room, and 6 mailbox messages covering
triage summaries, approvals, and hand-offs)
- Fixed two pre-existing task-transition bugs so the full seed runs to
completion
## Capture method
Desktop and agent shots were captured read-only from the live Fusion
instance (port 4040). Agent mail was captured from a throwaway seeded
instance — no running Fusion data was modified.
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1737">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Updated feature showcase galleries with expanded theme coverage,
including new Ember theme variants alongside existing Shadcn themes
across all sections.
* **New Features**
* Demo environment enhanced with agent collaboration capabilities
including multi-agent chat rooms, direct messaging, and mailbox
features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Behavior-preserving decomposition of the dashboard root
`packages/dashboard/app/App.tsx` — a 2,729-line monolith (~2,350-line
single `AppInner` component) — into focused hooks, a pure-helpers util,
and presentational components. **No functional change.**
**`App.tsx`: 2,729 → 1,636 lines**, now under the 2,000-line file-count
ratchet and graduated off its grandfathered baseline (can never regress
above 2,000).
## What was extracted
- **Pure helpers + constants** → `utils/appLifecycle.ts` (re-exported
from `App` so the 7 unit-tested symbols keep their `from "../../App"`
import contract).
- **12 custom hooks** under `app/hooks/`: `useMailboxUnread`,
`useChatUnreadBadge`, `useStashOrphanCount`, `useApprovalBanner`,
`useBranchTaskFilters`, `useDashboardHealth`, `useAuthTokenRecovery`,
`useScopedDismissFlag`, `useCapacityRiskBanner`,
`useMainPanelTaskDetail`, `useBoardScrollRestore`, `usePoppedOutTasks`.
- **2 presentational components** under `app/components/dashboard/`:
`MainContent` (the 647-line view-switch dispatcher) and
`DashboardBanners`, with typed prop interfaces in `types.ts`.
## Key design decisions
- **KTD4 — single `task:updated` subscriber preserved.** The original
one `/api/events` subscriber (mailbox counts + approval banner +
GitHub-star + awaiting-approval refresh) was split: `useApprovalBanner`
owns `task:updated`/`approval:requested` (banner + star +
`onMailboxRefresh` callback); `useMailboxUnread` owns
`message:*`/`approval:*` count refresh. `subscribeSse` multiplexes onto
one shared `EventSource`, so no extra connections.
- **The 18 lazy view declarations stay in `App.tsx`** (the
`lazy-loaded-views-docs.test.ts` guard regex-scans `App.tsx`) and are
threaded to `MainContent` as `LazyExoticComponent` props.
- **Eager `ChatView.css` import stays at the `App.tsx` top level** (FOUC
prevention).
## Verification
- `pnpm lint` (full repo), `typecheck` (both passes), `pnpm build` — all
clean.
- `App.test.tsx` (the full-render behavior contract) **identical**
pre/post: 123 pass / 5 fail, where the 5 are **pre-existing**
experimental-flag failures verified against unmodified `App.tsx`.
- `lazy-loaded-views-docs.test.ts` passes.
- 30+ new `renderHook` tests, incl. a `sseSplitIntegration` test
co-mounting both SSE hooks to pin the split (no double-fire;
awaiting-approval refresh exactly once).
- `App.tsx` graduated off the line-count ratchet baseline (scoped — only
its entry removed).
## Notes
- `useShellOnboarding` was deferred — the shell region is more
intertwined than planned (onboarding + connection-status + local/remote
redirects).
- The browser/visual smoke wasn't run (requires the server stack);
coverage rests on the green gate + fresh build + `App.test.tsx`
rendering the real `<App/>`.
Plan:
`docs/plans/2026-06-24-001-refactor-dashboard-app-tsx-module-breakup-plan.md`
(status: completed).
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1740">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Split dashboard main content and banner rendering into dedicated
components and presenter-style logic.
* Moved unread counters, approval/notification banners, dashboard health
refresh, scroll restoration, task detail flow, branch filtering, and
per-project dismissals into dedicated hooks.
* Centralized dashboard lifecycle helpers to keep banner/session
behavior consistent.
* **Documentation**
* Added a plan describing the behavior-preserving dashboard module
breakup.
* **Tests**
* Added/expanded hook and integration coverage for unread badges,
approval banners, scroll restore, branch filtering, dismissals, health
refresh, and main-panel task detail behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Replaces dense, agent-authored technical changeset paragraphs with a
**structured, concise changeset schema** (end-user summary + category +
optional dev detail), enforced by a linter. Adds a **deterministic
distillation step** at version time that transforms a release's
collected changesets into clean, grouped, end-user-facing release notes.
Unifies both release paths (local `release.mjs` and CI `version.yml` /
`release.yml`) behind a single distilled artifact so the root
`CHANGELOG.md` and GitHub Release both carry the same user-facing notes.
## Changes
### Changeset format (`scripts/lib/changeset-schema.mjs`)
- Each changeset body now uses labeled fields: `summary` (required,
user-facing, max 120 chars), `category` (required:
feature/fix/breaking/security/performance/internal), `dev` (optional
developer detail)
- Legacy freeform changesets are detected and flagged for the transition
period
### Linter (`scripts/check-changeset-format.mjs`)
- Validates structured schema, summary length, category enum, and
frontmatter package scope
- Wired into `test:gate`, `pretest`, `pretest:full`, and `pr-checks.yml`
- Legacy changesets warn (exit 0) during transition; `--strict` flag
fails on them
### Distillation (`scripts/lib/distill-release-notes.mjs`)
- `distillDeterministic` builds grouped, end-user-facing release notes
by category (New, Fixed, Breaking, Security, Performance, Internal)
- `buildDistillationPrompt` and `DISTILLATION_SYSTEM_PROMPT` ready for
AI distillation via `createFnAgent` when model credentials are available
- Graceful fallback: deterministic bullet list when no model is
configured
### Release integration
- **Local path** (`scripts/release.mjs`): captures changeset entries
before `changeset version` deletes them, distills notes post-version,
replaces the version's CHANGELOG section with curated notes
- **CI path** (`scripts/ci-distill-release-notes.mjs`,
`scripts/run-ci-distill.mjs`): chained into `release:version` so both
flows get distilled notes
- **GitHub Release** (`release.yml`): uses curated CHANGELOG notes
instead of `generate_release_notes: true`
### Documentation
- `AGENTS.md`, `RELEASING.md`, `docs/contributing.md` updated with the
structured format guide
- `.changeset/README.md` template for `pnpm changeset` consumers
## Testing
- 54 new unit tests across changeset-schema, check-changeset-format,
distill-release-notes, and extract-version-notes
- `pnpm lint` clean
- `pnpm test:gate` green (371 tests)
- `pnpm check:changesets` passes (13 legacy warnings expected during
transition)
## Plan
Full implementation plan:
`docs/plans/2026-06-24-001-feat-better-changelog-plan.md`
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1750">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added structured release-note guidance and validation for changeset
entries.
* Release notes now use curated, grouped changelog content instead of
auto-generated text.
* Added a fallback process to keep release notes consistent when
automated distillation isn’t available.
* **Bug Fixes**
* Improved handling of legacy changesets and malformed entries.
* Ensured version-specific changelog sections are updated without
affecting older releases.
* **Tests**
* Added coverage for changeset validation, note grouping, and changelog
section replacement.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Update AGENTS.md, RELEASING.md, contributing.md with the structured
changeset format guide. Add .changeset/README.md template. Add changeset
for this change. Update distill-release-notes.mjs with final version.
Chain distillation into release:version so both local and CI versioning
flows get distilled notes. Update release.yml to use curated CHANGELOG
notes instead of GitHub's auto-generated release notes.
Capture changeset entries before 'changeset version' deletes them,
distill deterministic notes post-version, and replace the version's
section in the root CHANGELOG with curated end-user notes.
## Problem
Opening the Git Manager on a multi-repo **workspace** project rendered
the sub-repo dropdown correctly but **also toasted "Not a git
repository"** every time.
## Cause
On open, the section data fetch fires immediately with no `repoPath`
(the `selectedRepo` hasn't resolved yet), so the git status request
targets the **non-git browse-only workspace root** and fails `Not a git
repository`. `fetchWorkspaceRepos` resolves a tick later and the fetch
re-runs against a real sub-repo — but the first failure had already
toasted.
## Fix
Track workspace detection in a ref and suppress that single benign
root-race error: no `repoPath` + `Not a git repository` while detection
is still pending or has confirmed a workspace. A genuinely broken
**non-workspace** project still surfaces the error — once detection
settles as non-workspace, a single *guarded* re-fetch re-surfaces it.
The guard ensures no redundant fetch in the common non-workspace-OK
path, preserving existing call-count expectations.
## Tests
- Added the missing `fetchWorkspaceRepos` api mock — a pre-existing gap
(from the git-manager commit) that had broken the **entire**
GitManagerModal suite at import (130 tests).
- Positive regression: workspace project → no `Not a git repository`
toast.
- Negative control: real non-workspace broken repo → the error still
toasts.
Dashboard typecheck + lint clean; my 2 new tests pass and no
previously-passing test regressed (8 unrelated pre-existing failures
remain, untouched by this change).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1749">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Workspace tasks now return aggregated, repo-prefixed diff results for
both detailed diffs and file-diff listings, including consistent
file/stats aggregation across sub-repositories.
* **Bug Fixes**
* Prevented an initial, harmless “Not a git repository” message from
being shown during early workspace detection; genuine errors still
display the standard error toast.
* Improved workspace detection and retry behavior so the UI loads the
correct repo context after project switching.
* Updated the Task Changes UI to handle workspace tasks without
incorrectly showing the “No worktree available” state.
* **Tests**
* Added/expanded coverage for workspace diff aggregation and
workspace-related UI/error behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
From the multi-agent /ce-code-review of PR #1749 (no P0/P1 correctness bugs; these
are perf, race-hardening, and convention fixes):
- P1 (perf/reliability): the workspace diff ran git subprocesses serially per
sub-repo AND per file — an N×M explosion with no aggregate cap. Add a bounded
mapWithConcurrency helper (order-preserving) and parallelize the per-file patch
loop (cap 8) and the per-sub-repo loop (cap 4). Deleted files still fetch their
patch (skipping it would drop deletes from /file-diffs and zero /diff stats).
- P2 (frontend race): GitManagerModal's workspace-detection could be clobbered by
a previous project's in-flight fetch on a rapid projectId switch / close-reopen.
Add a detectionGenerationRef guard — only the latest detection run may mutate
state; the effect cleanup bumps the generation to abandon superseded runs.
- P2 (DRY): reuse the existing parseStatusCode instead of re-inlining the
status-code mapping.
- P2 (convention): FNXC-tag the new functions/branches per CLAUDE.md.
- P3: extract DIFF_TIMEOUT_MS/FILE_DIFFS_TIMEOUT_MS constants, drop a dead
catch-assignment, note the done-fallback oldPath limitation.
Tests: order-preservation after parallelization; rapid-project-switch generation
guard (a stale workspace verdict must not suppress a new project's real error).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Changes / Files-changed tab showed nothing for multi-repo workspace tasks:
the task-diff backend is single-repo throughout, and a workspace task has null
task.worktree/task.branch (its per-repo state lives in workspaceWorktrees), so
every path fell back to git-diff against the non-git workspace root → empty.
Backend (register-session-diff-routes): extract the single-repo per-worktree
detailed-diff into one shared helper (computeWorktreeDetailedFiles) and add a
workspace branch to BOTH /tasks/:id/diff and /tasks/:id/file-diffs that runs
before the single-repo logic: iterate sorted workspaceWorktrees, compute each
sub-repo's diff in its own live worktree against that repo's baseCommitSha (done
tasks fall back to the per-repo landed range in the sub-repo root), and aggregate
with `${repoRel}/`-prefixed paths. Single-repo behavior is byte-for-byte
preserved (renamed→modified fold retained; 58 existing diff-route tests pass).
Frontend: TaskChangesTab takes an isWorkspace prop and no longer shows the
single-repo "No worktree available" empty state for workspace tasks;
TaskDetailModal passes isWorkspace={isWorkspaceTask(workingTask)}.
Tests: backend aggregation (repo-prefixed paths + stats) and frontend rendering
of workspace changes instead of the empty state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opening the Git Manager on a workspace project rendered the sub-repo dropdown
correctly but ALSO toasted "Not a git repository". On open the section fetch
fires immediately with no repoPath (selectedRepo unresolved), hitting the non-git
browse-only workspace root; fetchWorkspaceRepos resolves a tick later and the
fetch re-runs against a real sub-repo. We now track workspace detection in a ref
and suppress that one benign root-race error (no repoPath + "Not a git
repository" while detection is pending or has confirmed a workspace). A genuinely
broken non-workspace project still surfaces the error: once detection settles as
non-workspace, a single guarded re-fetch re-surfaces it (no redundant fetch in
the common non-workspace path, preserving existing call-count expectations).
Tests: add the missing fetchWorkspaceRepos api mock (pre-existing gap that broke
the whole GitManagerModal suite at import), plus a positive (workspace → no
toast) and negative-control (non-workspace broken → toast) regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up polish on the multiworkspace merge flow (after #1747 merged).
Driven by live testing of multi-repo tasks.
## Changes
**1. Remove the "Branch needs reattachment" banner (dashboard)**
The banner fired for any in-review task with a null singular
`task.branch` — which is the **normal, healthy** state for a workspace
task (its attachment is the per-sub-repo worktrees in
`workspaceWorktrees`, not a root branch). So it was a permanent false
positive on every in-review workspace task. Genuine lost bindings
(non-workspace) are already reattached **automatically** by
self-healing's `reconcileInReviewBranchRebind`, which runs event-driven
on move-to-in-review and on the sweep — no manual user action needed.
Removed the banner UI, its handler/state/imports, the obsolete
`rebind-banner` test, and its registry entry.
**2. self-healing: skip workspace tasks in the branch rebinder
(engine)**
`reconcileInReviewBranchRebind` now explicitly skips workspace tasks.
They are never rebind candidates — their `fusion/<id>` branches live
inside each sub-repo, not in the non-git browse-only workspace root, so
a null root branch is healthy. (The slim list select now carries
`workspaceWorktrees` from #1747, so `isWorkspaceTask` is accurate on
slim rows.)
**3. merger-ai: silence ENOENT in the pre-merge prune (engine)**
The AI-merge pre-merge prune `readdirSync`'d `<repo>/.fusion/ai-merge`
and warned on every workspace merge because that clean-room root is
created lazily and is normally absent. ENOENT now means "nothing to
prune" — skipped silently; non-ENOENT failures (and the tmpdir case)
still surface/throw.
**4. test: add `ToggleRight` to the TaskDetailModal lucide mock**
Pre-existing gap from FN-6880 (optional-group node) that broke the
entire TaskDetailModal suite at import; unrelated to this change but
blocking validation.
## Validation
- engine typecheck + build clean; dashboard typecheck clean
- engine rebind + merger-ai suites pass (63); TaskDetailModal suites
pass (117) after the mock fix
- lint clean on changed files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1748">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Removed the “Branch needs reattachment” banner and reattach action
from task details.
* Workspace tasks are skipped during branch rebind checks to avoid
confusing recovery prompts.
* Temporary AI-merge cleanup now handles missing directories quietly.
* Workspace per-repo landing now continues when dependency
synchronization fails (with degraded dependency availability), while
single-repo landing still fails fast.
* **Tests**
* Updated component test mocks and removed the rebind-banner test
coverage.
* Added coverage for resilient workspace dependency synchronization
behavior and adjusted workspace-merger test fixtures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
A workspace land squash-merges each sub-repo via a clean room that first runs the
configured/inferred install (npm/pnpm/yarn). The install hard-fails by design so
merge verification never runs against an uninstalled checkout — but that let ONE
sub-repo with a manifest npm refuses to install (e.g. a corrupt `-@0.0.1`
lockfile entry rejected by npm 11) block landing every other sub-repo.
landWorkspaceTask now passes nonFatalDependencySync to landOneRepo: a clean-room
install failure is caught, logged + audited as a non-fatal degradation, and the
land proceeds (the git squash needs no installed deps; only dep-dependent
verification degrades for that repo). A real abort signal still propagates. The
single-repo land path keeps the documented hard-fail (flag defaults off).
Tests: new workspace-merger-deps-resilient asserts both the resilient workspace
land (all repos land despite install throwing) and the preserved single-repo
hard-fail. Also fix a pre-existing getTask mock gap in workspace-merger.test
(mergeAndReview reads getTask().comments) that broke 3 tests at the land step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- dashboard: remove the "Branch needs reattachment" banner. It fired for any
in-review task with a null singular task.branch — the NORMAL state for a
workspace task (attachment is per-sub-repo worktrees in workspaceWorktrees), so
it was a permanent false positive. Genuine lost bindings are already reattached
automatically by self-healing's reconcileInReviewBranchRebind (event-driven on
move-to-in-review + sweep), so no manual user action is needed. Delete the
now-obsolete rebind-banner test + its registry entry.
- engine/self-healing: reconcileInReviewBranchRebind now explicitly skips
workspace tasks (never rebind candidates — their fusion/<id> branches live in
the sub-repos, not the non-git browse root; null root branch is healthy).
- engine/merger-ai: pre-merge prune treats an absent ai-merge search root (ENOENT)
as "nothing to prune" instead of warning on every workspace merge.
- test: add ToggleRight to the TaskDetailModal lucide mock (pre-existing gap from
FN-6880 that broke the whole suite at import).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>