Commit Graph

8634 Commits

Author SHA1 Message Date
gsxdsm
5cd795a3f2 fix(db): use rollback-journal mode for central DB to stop SIGBUS crashes (#1752)
## 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.

---

[![Compound
Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
![Claude
Code](https://img.shields.io/badge/Opus_4.8_%281M%29-D97757?logo=claude&logoColor=white)


<!-- 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 -->
2026-06-25 00:04:43 -07:00
gsxdsm
079c428af9 chore(release): v0.48.0
Version bump via changesets.
2026-06-24 23:56:23 -07:00
gsxdsm
7a00811b4a FN-6987: load mission delete confirmation in modal
Mission delete prompts now open through the shared confirmation modal instead of inline mission panels.

- Route mission list and detail delete buttons through ConfirmDialogProvider.
- Keep inline delete panels reserved for non-mission destructive actions.
- Add desktop, mobile, cancel, success, and failure regression coverage.
- Add a patch changeset for the published Fusion package.

Files changed:
 .changeset/fn-6987-mission-delete-confirm.md       |   7 +
 .../dashboard/app/components/MissionManager.tsx    |  38 +++-
 .../MissionManager.delete-confirm.test.tsx         | 243 +++++++++++++++++++++
 3 files changed, 278 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-6987
Fusion-Task-Lineage: df8a1a05-ff95-4d24-851d-053ecb017353
2026-06-24 23:52:30 -07:00
gsxdsm
e53f50eb38 Address PR review feedback (#1752)
- 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>
2026-06-24 23:51:52 -07:00
gsxdsm
50a94714ec fix(db): use rollback-journal mode for central DB to stop SIGBUS crashes
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>
2026-06-24 23:39:35 -07:00
gsxdsm
8b81cceb17 fix(desktop): externalize @fusion/engine so desktop release builds pass
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>
2026-06-24 23:38:59 -07:00
gsxdsm
f9816799a0 FN-7000: test unified CLI merge entrypoints
Strengthen CLI merge tests around the unified runAiMerge path.\n\n- Mock runAiMerge separately from deprecated aiMergeTask in CLI task and dashboard tests.\n- Assert task merge uses runAiMerge without falling back to workspace landing or aiMergeTask.\n- Verify dashboard manual merge logging streams through runAiMerge and restores spies safely.\n\nFiles changed:\n .../cli/src/commands/__tests__/dashboard.test.ts   | 51 +++++++++++++---------\n packages/cli/src/commands/__tests__/task.test.ts   | 30 +++++++++----\n 2 files changed, 52 insertions(+), 29 deletions(-)

Fusion-Task-Id: FN-7000

Fusion-Task-Lineage: 96080841-c813-4a9e-97bd-a41aba60089f
2026-06-24 23:32:52 -07:00
gsxdsm
bd5a779423 FN-6982: remove task chat guidance copy
Remove the task Chat composer guidance line and rely on placeholders for task state cues.

- Drop the idle/guidance text block above the task Chat entry box.
- Remove obsolete localized guidance strings and generated resource types.
- Update dashboard docs, regression coverage, and release notes for the simplified composer.

Files changed:
 .changeset/fn-6982-task-chat-guidance.md           |   7 ++
 docs/dashboard-guide.md                            |   2 +-
 packages/dashboard/app/components/TaskChatTab.css  |  10 --
 packages/dashboard/app/components/TaskChatTab.tsx  |  83 +----------------
 .../app/components/__tests__/App.test.tsx          |  42 ++++-----
 .../PlanningModeModal.ui-interactions.test.tsx     |   2 +-
 .../app/components/__tests__/TaskChatTab.test.tsx  | 102 +++++++++++----------
 packages/i18n/locales/en/app.json                  |   2 -
 packages/i18n/locales/es/app.json                  |   2 -
 packages/i18n/locales/fr/app.json                  |   2 -
 packages/i18n/locales/ko/app.json                  |   2 -
 packages/i18n/locales/zh-CN/app.json               |   2 -
 packages/i18n/locales/zh-TW/app.json               |   2 -
 packages/i18n/src/resources.d.ts                   |   2 -
 14 files changed, 81 insertions(+), 181 deletions(-)

Fusion-Task-Id: FN-6982

Fusion-Task-Lineage: b880df6c-d84b-4d34-886b-f54c1d568ece
2026-06-24 23:32:52 -07:00
gsxdsm
f918896839 FN-6981: make Git Manager tabs scroll on mobile
Keep Git Manager navigation reachable in narrow mobile and embedded layouts.

- Make Git Manager tab strips non-wrapping horizontal scrollers with touch-friendly panning.
- Constrain repo selector sizing so tabs and Refresh stay reachable on narrow widths.
- Extend modal and CSS coverage for standalone, embedded, and workspace-selector mobile cases.
- Add a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-6981-git-manager-mobile-tabs.md      |  7 +++
 packages/dashboard/app/components/ScriptsModal.css | 43 ++++++++++++-
 .../components/__tests__/GitManagerModal.test.tsx  | 59 +++++++++++++-----
 .../__tests__/core-modals-mobile.test.tsx          | 70 +++++++++++++++++++++-
 4 files changed, 160 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-6981

Fusion-Task-Lineage: c8a2910c-3642-4e14-a841-21cc32bc4cdb
2026-06-24 23:32:52 -07:00
gsxdsm
5a192ece16 FN-6946: add GitHub reference picker to task dialog
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
2026-06-24 23:32:52 -07:00
gsxdsm
3ae053e744 FN-6976: keep planning JSON failures retryable
Persist malformed Planning Mode AI responses as retryable errors while improving JSON response selection.

- Preserve failed initial-turn planning sessions with actionable retry errors instead of deleting them.
- Prefer valid planning-shaped JSON candidates over unrelated embedded JSON blobs.
- Cover streaming, non-streaming, retry recovery, and AI merge test mock compatibility.
- Add a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-6976-planning-json-recovery.md       |   7 +
 .../cli/src/commands/__tests__/dashboard.test.ts   |   1 +
 packages/cli/src/commands/__tests__/task.test.ts   |   9 +-
 .../src/__tests__/session-error-recovery.test.ts   | 144 +++++++++++++++++++++
 packages/dashboard/src/planning.ts                 | 123 ++++++++++--------
 5 files changed, 229 insertions(+), 55 deletions(-)

Fusion-Task-Id: FN-6976

Fusion-Task-Lineage: e1cb59cb-1d7d-4aff-b17f-9633568f823f
2026-06-24 23:32:52 -07:00
gsxdsm
eb3833a542 FN-6971: retire dual-observe cutover gating
Remove stale dual-observe prerequisites from workflow-authoritative readiness while preserving parity-summary safeguards.

- Require the authoritative flag plus clean populated parity summaries for interpreter cutover readiness.
- Keep persisted workflowInterpreterDualObserve values inert in runtime tests and documentation.
- Update cutover, parity, and graph-executor tests to reflect retired shadow observation behavior.
- Add a formatted patch changeset for the operator-facing cutover readiness fix.

Files changed:
 .changeset/fn-6971-workflow-cutover-readiness.md   |  7 ++
 docs/architecture.md                               |  2 +-
 docs/settings-reference.md                         |  4 +-
 docs/workflow-steps.md                             | 19 +++--
 .../core/src/__tests__/workflow-cutover.test.ts    | 19 +++--
 packages/core/src/workflow-cutover.ts              |  9 +--
 .../workflow-interpreter-cutover.test.ts           | 87 +++++++++++++++++++---
 .../workflow-interpreter-dual-observe.test.ts      | 76 ++++++-------------
 .../src/__tests__/stepwise-workflow-parity.test.ts | 25 ++++---
 .../engine/src/workflow-authoritative-driver.ts    | 10 +--
 10 files changed, 152 insertions(+), 106 deletions(-)

Fusion-Task-Id: FN-6971
Fusion-Task-Lineage: 363a441d-62e5-403c-9389-75fcf788352a
2026-06-24 23:32:52 -07:00
gsxdsm
e7021857a9 FN-6999: equalize mobile nav side spacing
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
2026-06-24 23:32:52 -07:00
gsxdsm
e473ba68fb FN-6997: widen task changes diff panel
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
2026-06-24 23:32:52 -07:00
gsxdsm
29530b5fdb FN-6967: widen tablet agent chat bubbles
Improve tablet Chat readability by widening agent-side response bubbles without expanding user or Quick Chat bubbles.

- Add a ChatView container query that widens assistant, streaming, and failure bubbles on tablet-width containers.
- Cover the targeted bubble classes and CSS width contract in ChatView tests.
- Document the tablet behavior and add a structured patch changeset for the published CLI bundle.

Files changed:
 .changeset/fn-6967-tablet-chat-bubble-width.md     |  7 +++
 docs/dashboard-guide.md                            |  3 +-
 packages/dashboard/app/components/ChatView.css     | 12 +++++
 .../app/components/__tests__/ChatView.test.tsx     | 56 ++++++++++++++++++++++
 4 files changed, 77 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-6967
Fusion-Task-Lineage: 74195885-4ec0-4fc9-b628-0b0510129026
2026-06-24 23:32:52 -07:00
gsxdsm
a554ceb672 FN-6959: align footer launcher typography
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
2026-06-24 23:32:52 -07:00
gsxdsm
8c734fe230 FN-6973: stabilize engine liveness tests
Stabilize engine tests around executor liveness cleanup and scheduler handoff expectations.

- Assert task-move cleanup clears active session registry entries for agent, step, and workflow-step sessions.
- Reset active session registry module state between executor tests alongside executing task locks.
- Relax brittle worktree and scheduler assertions to match current merge-base and handoff behavior.

Files changed:
 .../engine/src/__tests__/executor-pause.test.ts    | 52 +++++++++++++++++++---
 .../engine/src/__tests__/executor-test-helpers.ts  |  8 ++--
 .../engine/src/__tests__/executor-worktree.test.ts |  2 +-
 .../owning-node-unavailable-interactions.test.ts   |  2 +-
 .../src/__tests__/restart.integration.test.ts      |  8 +++-
 5 files changed, 59 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-6973

Fusion-Task-Lineage: 37c7d4b3-7ff5-4339-bd3f-6b10f507883e
2026-06-24 23:32:52 -07:00
gsxdsm
214a60c085 FN-6944: reclaim quick entry textarea width
Quick entry textareas now avoid inheriting refine-button padding that caused right-side dead space.

- Override quick-entry textarea right padding with a selector specific enough to beat the shared description refine rule without !important.
- Add CSS cascade regression coverage confirming quick entry reclaims width while global description textareas retain overlay padding.
- Add a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-6944-quick-entry-width.md            |   7 ++
 .../dashboard/app/components/QuickEntryBox.css     |   6 +-
 .../components/__tests__/QuickEntryBox.test.tsx    | 114 +++++++++++++++++++++
 3 files changed, 124 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-6944

Fusion-Task-Lineage: 2a6c7190-ff87-4da6-9437-8e57a113ce56
2026-06-24 23:32:51 -07:00
gsxdsm
d3593065e4 FN-6960: bound PR metadata generation
Keep Create PR metadata generation responsive and ensure fallback PR content remains editable.

- Race metadata collection, prompt execution, and session creation against abort/timeout signals.
- Return fallback PR metadata from the API route when generation exceeds the route budget.
- Seed the Create PR dialog with editable fallback body content after metadata failures and require a non-empty body before submit.
- Validate non-empty PR bodies in both gh CLI and API-backed PR creation paths.
- Add regression coverage for bounded metadata generation, fallback responses, and PR body validation.

Files changed:
 .changeset/fn-6960-pr-metadata-bounded.md          |  7 ++
 .../dashboard/app/components/PrCreateModal.tsx     | 34 ++++++++-
 .../components/__tests__/PrCreateModal.test.tsx    | 41 +++++++++++
 .../src/__tests__/github-create-pr.test.ts         |  6 +-
 .../src/__tests__/github-forced-mode.test.ts       |  2 +-
 packages/dashboard/src/__tests__/github.test.ts    | 21 ++----
 .../src/__tests__/pr-metadata-generator.test.ts    | 55 ++++++++++----
 .../src/__tests__/pr-routes.contract.test.ts       | 10 +++
 .../register-git-github.pr-errors.test.ts          |  2 +-
 ...it-github.pr-options-preflight-metadata.test.ts | 25 +++++++
 .../dashboard/src/__tests__/routes-auth.test.ts    | 12 +--
 .../dashboard/src/__tests__/routes-github.test.ts  |  2 +-
 packages/dashboard/src/github.ts                   | 20 ++++-
 packages/dashboard/src/pr-metadata-generator.ts    | 86 ++++++++++++++--------
 .../dashboard/src/routes/register-git-github.ts    | 67 +++++++++++++----
 15 files changed, 301 insertions(+), 89 deletions(-)

Fusion-Task-Id: FN-6960
Fusion-Task-Lineage: 1ae72062-7180-4de3-999b-98d131f00792
2026-06-24 23:32:51 -07:00
gsxdsm
d7f3c7093e FN-6941: add graph workflow filtering
Adds a Graph view header workflow dropdown that filters dependency graph tasks by the selected workflow.

- Portal the workflow switcher into the Graph view header and clear selection when unmounted.
- Scope plugin graph task context using workflow assignments and default workflow fallback.
- Cover the header integration, task filtering, portal lifecycle, and disabled workflow mode.
- Document Graph workflow filtering and add the published package changeset.

Files changed:
 .changeset/fn-6941-graph-workflow-dropdown.md      |   7 +
 docs/dashboard-guide.md                            |   1 +
 packages/dashboard/app/App.tsx                     |   4 +
 .../app/__tests__/graph-workflow-header.test.tsx   | 127 +++++++++++++++
 .../app/components/GraphWorkflowSwitcherSlot.tsx   | 110 +++++++++++++
 .../__tests__/GraphWorkflowSwitcherSlot.test.tsx   | 173 +++++++++++++++++++++
 .../app/components/dashboard/MainContent.tsx       |  23 ++-
 .../dashboard/app/components/dashboard/types.ts    |   3 +
 8 files changed, 446 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-6941

Fusion-Task-Lineage: a77bc99e-00fe-42fe-a976-d88ffd803388
2026-06-24 23:32:51 -07:00
gsxdsm
fd63260962 Merge branch 'main' into feature/refactor-apptsx 2026-06-24 20:31:59 -07:00
gsxdsm
2ba81c25b4 chore(release): v0.47.0
Version bump via changesets.
2026-06-24 19:32:59 -07:00
gsxdsm
e6e096645a fix(workspace): address code-review findings on the workspace diff + Git Manager
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>
2026-06-24 19:05:11 -07:00
gsxdsm
98fc453f45 feat(workspace): show per-sub-repo changes in the task Changes tab
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>
2026-06-24 18:29:58 -07:00
gsxdsm
e4a0118579 fix(workspace): suppress spurious "Not a git repository" toast in Git Manager
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>
2026-06-24 18:09:39 -07:00
gsxdsm
9dc228549c fix(workspace): make per-repo land resilient to a sub-repo dependency-sync failure
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>
2026-06-24 17:15:38 -07:00
gsxdsm
ea67e4424d Merge branch 'main' into feature/refactor-apptsx 2026-06-24 17:07:22 -07:00
gsxdsm
93cfb88e64 fix(workspace): remove false reattach banner, silence ai-merge ENOENT prune
- 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>
2026-06-24 16:25:23 -07:00
gsxdsm
28ceca2cbd Address PR review feedback (#1747)
- core/store: include workspaceWorktrees in the slim and activity-log-limited
  SELECT lists (rowToTask reads it, but the explicit column lists omitted it, so
  slim/limited reads dropped the field and could misclassify workspace tasks);
  add regression tests for both read surfaces
- dashboard/register-git-github: validate caller-supplied repoPath in resolveGitDir
  via isPathWithin containment check (path-traversal hardening for all git
  endpoints); make loadWorkspaceConfig a static @fusion/core import per AGENTS.md
- dashboard/legacy: preserve repoPath in the string-form pullBranch overload
- dashboard/GitManagerModal: revalidate selectedRepo against the fetched repo list
  so a stale selection can't persist across project switches

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:43:12 -07:00
gsxdsm
f06281961e fix(workspace): persist workspaceWorktrees and isolate concurrent session leases
Multiworkspace tasks could not complete due to two independent bugs:

1. task.workspaceWorktrees had no SQLite column / rowToTask mapping, so
   fn_acquire_repo_worktree's updateTask write was dropped on every persist
   (applyTaskPatch writes the DB-round-tripped task back to task.json). Every
   later getTask returned undefined, so fn_task_done's scope verifier read {}
   and blocked with "acquired no sub-repo worktrees", and isWorkspaceTask()
   consumers misfired. Persist it mirroring mergeDetails (schema column + v129
   migration + db-migrate + defineTaskColumn + TaskRow + rowToTask).

2. In workspace mode every task ran rooted at the shared browse-only root, and
   setActiveSession registered that path keyed only by path — so a second
   concurrent workspace task was rejected by the foreign-task guard
   ("active-session path ... is held by ..."). Give each task a task-scoped
   synthetic session key (sessionRegistryPath), applied at all register and
   unregister sites; the in-memory worktree Set still holds the real root.

Regression tests assert the persistence invariant across getTask/listTasks/
store-reopen and concurrent session registration across all three session
surfaces; both verified to fail without the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:13:43 -07:00
gsxdsm
be2866ee66 feat(git-manager): multi-repo workspace support with repo selector
- Add GET /api/git/workspace-repos endpoint returning sub-repo list
- Add resolveGitDir() helper: resolves repoPath query param to sub-repo dir
- Update all 34 git endpoints to use resolveGitDir for workspace targeting
- Add repoPath param to 30+ frontend git API functions
- GitManagerModal: auto-detect workspace repos on mount, show repo selector
  dropdown at top of sidebar, pass selected repo to all git API calls
- Auto-select first repo when workspace mode is detected
- Repo change triggers data refetch via gitRepoPath dependency
2026-06-24 15:02:12 -07:00
gsxdsm
b680948d8c Address PR review feedback (#1746)
- Fix race condition: add request ID guard for stale workspace detection responses
- Guard workspaceMode:true: only persist when repos.length > 0
- Add server-side taskPrefix validation (/^[A-Z]{1,5}$/)
- Move store.init() inside try/finally in both CLI TaskStore lifecycles
- Accept 1-character prefixes in CLI prompt (was requiring >= 2)
- Fix workspaceMode passing: forward false explicitly (was coerced to undefined)
- Fix 3rd KB→FN fallback in distributed-task-id.ts catch block
- Add try/finally to dashboard TaskStore in register-project-routes
- Gate workspace detection on existing-directory mode only (skip clone mode)
2026-06-24 14:24:07 -07:00
gsxdsm
46d0c4a1f6 Address PR review feedback round 3 (#1741)
- Align dashboard prefix validation to 1-5 chars (was 1-10) matching CLI cap
- Fix distributed-task-id.ts fallback from KB to FN (3 occurrences)
- Move taskPrefix/defaultWorkflowId persistence outside interactive-only block
  so non-interactive CLI registration also gets defaults
- Wrap both TaskStore lifecycles in try/finally to guarantee close() on error
2026-06-24 14:08:32 -07:00
gsxdsm
9e7c85d221 feat(dashboard): workspace detection + task prefix in import wizard
- Add POST /api/projects/detect-workspace endpoint for sub-repo scanning
- Modify POST /api/projects to accept workspaceMode + taskPrefix params
- SetupWizardModal: auto-detect sub-repos when path is entered, show
  workspace mode checkbox with detected repo count, add task prefix field
  auto-derived from project name
- Wire workspaceMode and taskPrefix through registration API call
2026-06-24 14:08:32 -07:00
gsxdsm
2229e25e20 Merge branch 'main' into feature/refactor-apptsx 2026-06-24 11:49:37 -07:00
gsxdsm
b317a39d80 Cap interactive prefix input to 5 chars (#1741) 2026-06-24 11:39:40 -07:00
gsxdsm
06adc190ce Add max-length cap (10) to interactive prefix input (#1741)
CodeRabbit: suggestTaskPrefix caps at 4 chars, but user input was uncapped.
Align with the dashboard validation regex (1-10 uppercase letters).
2026-06-24 11:34:38 -07:00
gsxdsm
5025e8ceb0 Merge branch 'main' into feature/refactor-apptsx 2026-06-24 10:51:17 -07:00
gsxdsm
aae76cecc3 Address PR review feedback (#1741)
- Close first TaskStore before creating second in interactive registration (P1)
- Revert defaultWorkflowId default to undefined; set explicitly in onboarding only (P1)
- Add alpha-only filter + 2-char min to interactive prefix input (P2)
- Move suggestTaskPrefix to @fusion/core, share between CLI and dashboard (P2)
- Fix suggestTaskPrefix JSDoc to match implementation (P2)
2026-06-24 10:45:21 -07:00
gsxdsm
9a7c0c6154 fix: fallback task prefix to FN (was KB) when unset
The hardcoded fallback prefix in store.ts was 'KB' (legacy name). Changed
to 'FN' to match the product name and dashboard placeholder.
2026-06-24 10:31:06 -07:00
gsxdsm
800f845e15 feat(workspace): fix auto-detection, derive prefix from name, default coding workflow
- Fix workspace detection: change workspaceMode default from false to
  undefined so isWorkspaceModeExplicitlyDisabled no longer blocks
  auto-detection on fresh projects (config.json was being written with
  workspaceMode:false during store.init(), causing the guard to skip
  detection before it ever ran)
- Derive task prefix from project name (first 2-4 chars) instead of
  hardcoded 'FN' as the suggested default
- Default workflow is now builtin:coding instead of undefined
- CLI registerProjectInteractive: onboarding prompt for task prefix
  confirmation after project name
- Dashboard POST /api/projects: auto-derive prefix and set default
  workflow for new registrations
2026-06-24 10:31:06 -07:00
gsxdsm
2504447926 test(dashboard): apply test-quality findings from focused re-review
Address the 6 findings from the focused re-review of the hardening commit
(test-only; no production changes):
- useDashboardHealth: drop the false-confidence unmount-state assertion
  (React 19 silently drops setState on unmounted components, so it pinned
  nothing) and document the cancelled-guard as a React-19-untestable-via-state
  invariant; keep the meaningful mount-fetch assertions.
- sseSplitIntegration: assert the onReconnect split explicitly
  (mailbox onReconnect wired, approval onReconnect undefined); prove the
  mailbox approval:requested handler actually refreshes (fetchUnreadCount
  called); replace the magic 2x microtask drain with a deterministic waitFor.
- Extract the duplicated msg()/message() SSE-event helper to a shared
  sseTestHelpers.ts (per-file vi.mock factories stay — vitest hoists them).
- useChatUnreadBadge: add a cross-project filter case for
  chat:room:message:added.

Full hook suite green (1366 tests); App.test.tsx unchanged; typecheck + eslint clean.
2026-06-24 09:23:03 -07:00
gsxdsm
c1b5be7d69 fix(workspace): write config.json before workspace.json, validate settings object
Address PR #1739 review round 3:

- Major (coderabbit): Reorder writes so setWorkspaceModeInConfig runs
  before saveWorkspaceConfig. If the config write fails, no stale
  workspace.json is left behind.
- Major (coderabbit): setWorkspaceModeInConfig only treats ENOENT as
  empty config (not parse errors or permission errors). Validates
  settings is a plain object before merging to prevent clobbering.
2026-06-24 09:02:02 -07:00
gsxdsm
11ffca1611 fix(workspace): persist workspaceMode:true in config.json, let save errors propagate
Address PR #1739 review round 2:

- P1 (greptile): Auto-detection fallback now sets workspaceMode: true in
  config.json so the dashboard toggle reflects the actual state.
- Major (coderabbit): Let saveWorkspaceConfig errors propagate instead of
  silently returning 'existing' when the write fails. A failed write would
  leave the project with no git repo and no workspace config.
2026-06-24 08:36:55 -07:00
gsxdsm
5ae008b6fd Merge branch 'main' into feature/refactor-apptsx 2026-06-24 08:24:35 -07:00
gsxdsm
42342eff03 fix(workspace): respect explicit workspaceMode:false, improve exclusion test
Address PR #1739 review feedback:

- P1 (greptile): When workspaceMode is explicitly false in config.json,
  skip the auto-detection fallback so toggling workspace mode off via the
  dashboard has a lasting effect (was being re-enabled on next registration).
- CodeRabbit: node_modules exclusion test now includes a real sibling
  sub-repo to prove the exclusion is the gate, not just absence of
  detection.
- Add test for workspaceMode:false config.json guard.
2026-06-24 08:16:36 -07:00
gsxdsm
49c2de108b test(dashboard): harden hook tests + drop dead App.tsx re-exports (code review)
Address code-review findings on the App.tsx module-breakup:
- Add behavior-preservation tests: useBoardScrollRestore real double-rAF
  restore path; useApprovalBanner clear-on-leave-awaiting + mailbox-refresh
  dedup; useCapacityRiskBanner threshold-change-clears + dismiss-persist
  assertion; useChatUnreadBadge room-message + cross-project filter; and a
  new sseSplitIntegration test co-mounting useMailboxUnread + useApprovalBanner
  to pin the KTD4 SSE split (no double-fire; awaiting-approval refresh exactly
  once via callback).
- Add unmount/teardown assertions (stash interval clear, auth-token listener
  removal, dashboard-health cancelled flag).
- Drop dead type-only re-exports (ApprovalBannerCandidate, CliActionDeps) from
  App.tsx — zero importers (verified).

All hook tests green; App.test.tsx unchanged (5 pre-existing). typecheck +
eslint clean. No production behavior change.
2026-06-24 01:13:33 -07:00
gsxdsm
9aaf911735 feat(workspace): add per-project workspaceMode setting with interactive confirmation
Add workspaceMode as a first-class ProjectSettings boolean that controls
whether the project root is treated as a workspace parent (multi-repo)
or a single git repo.

- ProjectSettings type + DEFAULT_PROJECT_SETTINGS: workspaceMode?: boolean
- CLI registerProjectInteractive: when sub-repos are detected, ask the
  user to confirm workspace mode instead of auto-applying
- TaskStore.updateSettings: when workspaceMode is toggled on, detect
  sub-repos and persist workspace.json; when toggled off, remove it
- Dashboard SettingsModal GeneralSection: workspace mode toggle checkbox

This lets users change workspace mode per-project at any time via the
dashboard Settings or PUT /settings API.
2026-06-24 00:45:24 -07:00
gsxdsm
ff155b9df7 fix(workspace): exclude node_modules from detection, best-effort save
Address PR #1739 review feedback:

- P1: Exclude node_modules, .fusion, .pi from detectWorkspaceRepos so
  packages installed from git sources don't produce false-positive
  workspace members.
- P2: Wrap saveWorkspaceConfig in try/catch so a write failure (permissions,
  disk full) doesn't fail the current registration.
- Nitpick: Thread runner/timeout through detectWorkspaceRepos so custom-runner
  callers are consistent across all code paths.
2026-06-24 00:26:31 -07:00
gsxdsm
cab375a6f8 fix(workspace): detect sub-repos when workspace.json is missing
The initial fix only checked loadWorkspaceConfig, but the dashboard
POST /api/projects and `fn project add` routes never create workspace.json
(only registerProjectInteractive does). So re-adding a workspace project
through the dashboard still triggered git init because the guard saw no
workspace.json.

Add detectWorkspaceRepos as a fallback: after loadWorkspaceConfig and
isInsideGitWorkTree both miss, probe for git sub-repos. If found, persist
workspace.json and return 'existing' without running git init. This covers
all registration surfaces.
2026-06-24 00:16:37 -07:00