The boot-time integrity check (scheduleBackgroundIntegrityCheck, ~60s
after init) ran PRAGMA integrity_check on the live connection, walking
every page and freezing the event loop for several seconds — the largest
single read-contention stall in normal operation.
Offload it to the sqlite3 CLI in a child process (async spawn), matching
the existing out-of-process pattern used by quickCheckSqliteFile and
.recover. The CLI connection is opened -readonly so it can never
checkpoint or write the live WAL; this works because the live process
holds the DB open (so the -shm exists). When the CLI is unavailable or
can't open read-only, fall back to the in-process check (verified=false),
preserving today's behavior on those environments.
- New integrityCheckSqliteFileAsync(dbPath, limit) module helper.
- New private runBackgroundIntegrityCheck() seam (offload + fallback) so
the scheduler has one testable, deterministic policy point.
- Background scheduler callback is now async (IIFE + finally) with errors
swallowed so a background timer can't crash the process.
VACUUM is intentionally NOT offloaded: the call graph shows it is invoked
only by the `fn db vacuum` CLI command and tests, never from the periodic
maintenance loop, so it is not a background event-loop stall — and an
out-of-process VACUUM on a live WAL DB would add corruption surface for no
hot-path benefit.
Tests updated to the async/offloaded seam (deterministic regardless of
whether the sqlite3 CLI exists in the environment), plus coverage for the
new helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Investigation of reported DB read contention found two cross-process
contention sources in the SQLite layer (single synchronous node:sqlite
connection per process, WAL mode):
- Unbounded WAL on central-db and archive-db. Neither set
journal_size_limit, so their WAL never truncated back down after a
checkpoint and every reader paid an ever-growing WAL-index scan. Add
journal_size_limit=4MB (matching db.ts) plus explicit
synchronous=FULL/wal_autocheckpoint=1000 for intent. central-db is the
most cross-process-shared DB; archive-db had the same latent gap.
- vacuum() held the EXCLUSIVE lock past its own runtime. Resetting
locking_mode to NORMAL does not drop the WAL exclusive lock until the
connection next touches the DB, so other processes stayed locked out of
reads (SQLITE_BUSY) until some unrelated query ran. A plain read does
NOT release it in WAL mode (verified); a PASSIVE checkpoint does. Run
one in the finally, guard the locking_mode reset so it can't mask the
original error or skip the release, and log swallowed failures.
Tests: assert the new PRAGMAs on central-db and archive-db, and that a
second connection can read immediately after vacuum() returns.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses findings from a multi-agent review of the two prior fixes.
P0 (executor.ts): the stale-conflict recovery force-removed worktreePath with
no bounds check; that path can come from a git admin entry resolving outside
.worktrees/. Now refuses unless the path is inside the worktrees dir, not a
symlink (realpathSync), not a registered worktree, and not actively owned, and
re-verifies liveness in the catch instead of trusting the error string. Also
excludes spawn failures (spawn git ENOENT) from the stale-path classification.
worktree-pool.ts: resolveGitdirPointer -> dotGitPointerIsDangling. Reaps only
when a .git link's gitdir target is confirmed missing; a real .git dir,
unparseable pointer, or any read/stat failure is treated as NOT dangling
(conservative) so a transient read error on a live worktree can't trigger rm.
Drops the string|"directory"|null sentinel union.
core store.ts: bypass the reconcile recency window when the live task table is
empty (corruption/restore: surviving task.json keep old mtimes) and when
fusion.db was auto-recovered on startup, so .recover row loss isn't stranded.
Adds an ignoreRecencyWindow option.
Tests: executor recovery + out-of-bounds refusal, unparseable .git skip,
recency boundary, empty-DB/forced bypass. engine 135 + core 12 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On restart, reconcileOrphanedTaskDirs re-imported ancient .fusion/tasks/<id>/
directories that had no DB row, surfacing old low-numbered tasks (FN-001, ...)
onto the live board — looking like "all task IDs reset / starting over".
The sweep is meant to recover dirs that appear after store init (heartbeat
races) or rows lost to recent DB corruption. Modern deletes leave a soft-delete
tombstone (caught by taskIdExistsAnywhere), but legacy hard-deletes left none,
so a months-old task.json with no DB row was silently re-imported.
Gate recovery on a 7-day recency window (task.json mtime). Older orphans are
skipped (reason: stale-orphan-dir-beyond-recency-window) and left for explicit
recovery or directory cleanup; heartbeat-race and recent-corruption recovery
still work. Adds a regression test for the stale-dir skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Directories under .worktrees/ that survive with a dangling .git pointer
(present on disk, but their .git/worktrees/<name> admin entry is gone) are
invisible to `git worktree list`/`prune` yet collide with freshly generated
worktree names. The executor's conflict cleanup then fails with
"is not a working tree", failing the workflow graph at node 'execute' after
3 attempts.
- executor.ts: extend FN-4813 stale-conflict recovery to also treat
"is not a working tree" and ENOENT (not just "validation failed, cannot
remove working tree") as "no live worktree here" — prune the admin entry,
force-remove the leftover dir, and proceed with fresh creation.
- worktree-pool.ts: reapOrphanWorktrees skipped any dir on mere .git-file
presence, contradicting its own documented invariant. Resolve the .git
pointer and only skip when the gitdir target exists; reap dangling
pointers like any other orphan so they stop accumulating across runs.
- Tests for both the dangling (reaped) and valid (skipped) .git cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a selectable business workflow for managing lead-generation tasks.
- Register a built-in lead-generation workflow with custom business columns, lead fields, and stage prompts.
- Cover workflow registration, column traits, field metadata, compilation, and IR round-tripping with core tests.
- Document the workflow catalog entry and add a minor changeset for the published CLI package.
Files changed:
.changeset/fn-6759-lead-generation-workflow.md | 5 +
docs/workflow-steps.md | 4 +
.../builtin-lead-generation-workflow-ir.test.ts | 123 ++++++++++++++++++
.../core/src/__tests__/builtin-workflows.test.ts | 10 ++
.../src/builtin-lead-generation-workflow-ir.ts | 141 +++++++++++++++++++++
packages/core/src/builtin-workflows.ts | 20 +++
packages/core/src/index.ts | 1 +
7 files changed, 304 insertions(+)
Fusion-Task-Id: FN-6759
Fusion-Task-Lineage: 6c724f84-a6a6-4b01-93ec-cabb9387be2f
Add a built-in workflow path for UI-heavy implementation work with a required design review gate.
- Register builtin:design as a selectable built-in workflow after implementation and before standard review/merge.
- Define design review criteria covering hierarchy, spacing, typography, design tokens, component reuse, responsiveness, and design-language fit.
- Add catalog coverage and update workflow documentation to list the new Design workflow.
- Add a minor changeset for the published Fusion package.
Files changed:
.changeset/fn-6760-design-workflow.md | 5 +++++
docs/getting-started.md | 2 +-
docs/workflow-editor.md | 4 ++++
docs/workflow-steps.md | 1 +
.../core/src/__tests__/builtin-workflows.test.ts | 23 +++++++++++++++++++++
packages/core/src/builtin-workflows.ts | 24 ++++++++++++++++++++++
6 files changed, 58 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-6760
Fusion-Task-Lineage: 2682ce3f-a89d-4237-b2cf-675439eea1d6
Command Center concurrency controls now preserve persisted scheduler values and improve mobile slider handling.
- Load concurrency defaults from project settings and display persisted values without clamping the readout.
- Expand range slider maxima when existing settings exceed normal UI bounds while keeping edits bounded.
- Add mobile touch-drag affordance styling and regression coverage for actual values, overflow values, and CSS contracts.
Files changed:
.../command-center/CommandCenterControls.css | 26 +++++++-
.../command-center/CommandCenterControls.tsx | 63 +++++++++++++-----
.../__tests__/CommandCenterControls.test.tsx | 74 +++++++++++++++++++++-
3 files changed, 143 insertions(+), 20 deletions(-)
Fusion-Task-Id: FN-6768
Fusion-Task-Lineage: 2508c2ab-9e57-4d29-934c-828a015bdfe1
Make task-detail chat disclose when sent guidance will not receive an immediate agent reply.
- Add an idle session hint for non-done tasks without an active steerable agent while keeping the composer sendable.
- Style the idle hint with warning color and expose a stable test id for regression coverage.
- Expand TaskChatTab tests across inline, expanded, empty, populated, paused, and non-live session states.
- Document the idle saved-guidance behavior in the dashboard guide.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.css | 5 ++
packages/dashboard/app/components/TaskChatTab.tsx | 13 ++-
.../app/components/__tests__/TaskChatTab.test.tsx | 95 +++++++++++++++++++---
4 files changed, 99 insertions(+), 16 deletions(-)
Fusion-Task-Id: FN-6751
Fusion-Task-Lineage: 21f0764d-23db-49ca-b737-3b6a8be7a918
Expand LeftSidebarNav tests to lock the settings control at the sidebar footer.
- Assert the settings button renders as the last sidebar button in the footer.
- Preserve bottom settings coverage when optional destinations are gated off and when the rail is collapsed.
- Cover clicking the settings button without an optional handler.
Files changed:
.../components/__tests__/LeftSidebarNav.test.tsx | 26 +++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-6788
Fusion-Task-Lineage: 7f378f59-094f-4be1-9fee-eafb1b631cf8
Keeps mobile bottom navigation icons evenly centered across tab variants.
- Make each mobile nav tab an equal-width flex column that ignores intrinsic label width.
- Center and truncate labels without affecting icon spacing.
- Add coverage for optional tabs, active tabs, badges, status dots, and overflow plugin views.
- Add a patch changeset for the published CLI bundle.
Files changed:
.changeset/fn-6766-mobile-nav-spacing.md | 5 ++
packages/dashboard/app/components/MobileNavBar.css | 18 ++--
.../app/components/__tests__/MobileNavBar.test.tsx | 95 ++++++++++++++++++++++
3 files changed, 113 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-6766
Fusion-Task-Lineage: 0fe92813-9110-4bc9-b0d0-39d98dc7dba9
Update the default shadcn dashboard theme to use orange highlight accents while keeping its neutral base.\n\n- Set dark and light shadcn accent tokens and accent text for orange highlights.\n- Update shadcn theme swatches and documentation to reflect the orange accent.\n- Adjust theme tests to assert the orange accent token and preserved glow neutralization.\n\nFiles changed:\n docs/dashboard-guide.md | 2 +-\n packages/dashboard/app/components/ThemeSelector.css | 4 ++--\n packages/dashboard/app/hooks/__tests__/useTheme.test.ts | 12 ++++++++----\n packages/dashboard/app/public/theme-data.css | 11 +++++++----\n 4 files changed, 18 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-6758
Fusion-Task-Lineage: b4fe5a6d-416e-4cd1-be57-7e6da5a07f59
Keep Mailbox as a single top-level entry while Todos stays in overflow navigation.
- Remove the duplicate Mailbox action from the mobile More sheet while preserving its primary-tab badges.
- Cover desktop, tablet, and mobile navigation placement expectations for Todos and Mailbox.
- Update dashboard docs to describe compact Header and mobile bottom-nav mailbox indicators.
Files changed:
docs/dashboard-guide.md | 2 +-
.../mobile-feature-access-regression.test.tsx | 3 +-
packages/dashboard/app/components/MobileNavBar.tsx | 22 +++---------
.../app/components/__tests__/Header.test.tsx | 19 +++++++----
.../app/components/__tests__/MobileNavBar.test.tsx | 39 ++++++++++++++++++----
5 files changed, 52 insertions(+), 33 deletions(-)
Fusion-Task-Id: FN-6765
Fusion-Task-Lineage: e32f271e-7dab-43e0-b824-da598641aa77
Research and Insights now reflow at tablet widths to avoid cut-off content while preserving mobile and desktop behavior.
- Add tablet-specific full-width Research layout rules with shrink-safe reader/history containers.
- Add tablet-specific Insights layout rules that stack the category rail above the detail pane.
- Cover Research and Insights responsive CSS contracts across desktop, mobile, and tablet tiers.
Files changed:
packages/dashboard/app/components/InsightsView.css | 67 ++++++++++++++++++++++
packages/dashboard/app/components/ResearchView.css | 47 +++++++++++++++
.../app/components/__tests__/InsightsView.test.tsx | 20 +++++++
.../app/components/__tests__/ResearchView.test.tsx | 13 +++++
4 files changed, 147 insertions(+)
Fusion-Task-Id: FN-6764
Fusion-Task-Lineage: ed528038-e69b-4794-9885-fb3b5f47b3ed
Preserve auth route coverage by moving the suite out of the contended API backfill shard.\n\n- Add routes-auth to the curated dashboard API quality shard.\n- Document the FN-6753 suite-load coupling classification and shard-isolation remedy.\n\nFiles changed:\n docs/testing.md | 4 ++++\n packages/dashboard/vitest.config.ts | 7 ++++++-\n 2 files changed, 10 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-6753
Fusion-Task-Lineage: 4da2f45f-87ba-4b80-baed-68dbde93a09e
Restore the Command Center tablet layout test mock contract so hook refresh calls resolve cleanly.
- Export fetchConfig, fetchSettings, and updateSettings from the tablet test api mock.
- Document why the real useAppSettings hook requires the full api mock surface.
- Preserve task fixture updates for populated productivity metrics.
Files changed:
.../command-center/__tests__/CommandCenter.mobile-scroll.test.tsx | 2 +-
.../command-center/__tests__/CommandCenter.tablet-layout.test.tsx | 8 ++++++--
2 files changed, 7 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-6754
Fusion-Task-Lineage: 5c5955e3-7b23-4fb3-baf5-4ba9dcfeef2e
Command Center token totals now shrink to fit instead of wrapping digit groups.
- Add inline-size containers around stat cards and live metrics for container-query scaling.
- Replace digit wrapping with nowrap overflow clipping and clamp-based numeric font sizes.
- Strengthen Command Center tests for exact live metric values and CSS no-wrap shrink contracts.
Files changed:
.../app/components/command-center/CommandCenter.css | 17 ++++++++++++-----
.../command-center/__tests__/CommandCenter.test.tsx | 3 ++-
.../CommandCenter.token-validity.css.test.ts | 19 +++++++++++++++----
3 files changed, 29 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-6784
Fusion-Task-Lineage: dfe165f4-7304-49eb-b46b-83c0a9e2c4a3
Agents stop heartbeating during long legitimate work (e.g. a verification
step blocked on a multi-minute test command). The 5-minute floor could
misread a busy agent as dead and reclaim its in-progress task mid-run.
Raise MIN_HEARTBEAT_STALENESS_MS to 10 minutes and strengthen the floor
test (7-minute-silent fast-interval agent stays healthy — would have read
stale under the old 5-minute floor). Engine typecheck clean; heartbeat
suite 148/148 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three failures in the non-blocking Full Suite:
- CommandCenter mobile-scroll / tablet-layout regression tests rendered with
productivity fixtures missing the `hoursSaved` field added in FN-6721. That
made ProductivityArea throw on `data.hoursSaved.unavailable`, an uncaught
exception that crashed the CommandCenter render (no `command-center` testid)
and polluted the shared worker, causing collateral QuickEntryBox failures.
Add `hoursSaved` to both fixtures.
- github-tracking-delete route test failed intermittently with
`ENOTEMPTY: ... rmdir '.../.fusion'` because the fire-and-forget delete
handler can write into `.fusion` while afterEach removes the temp dir. Make
rm tolerant via maxRetries/retryDelay.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep Command Center anchored as an inline header destination while preserving changed-test coverage selection.
- Render Command Center immediately after Agents on tablet and desktop instead of moving it into the More views overflow.
- Update header and tablet tests plus docs to lock the stable inline navigation contract.
- Treat the quarantine data file as test-irrelevant so edits do not force gate mode and drop affected-package tests.
Files changed:
.changeset/fix-quarantine-json-gate-mode.md | 5 ++
.changeset/fn-6781-command-center-header.md | 5 ++
CONCEPTS.md | 3 +
docs/dashboard-guide.md | 4 +-
...data-file-forces-gate-mode-dropping-coverage.md | 74 ++++++++++++++++++++++
.../app/__tests__/tablet-header-controls.test.tsx | 7 +-
packages/dashboard/app/components/Header.tsx | 51 +++++----------
.../app/components/__tests__/Header.test.tsx | 8 ++-
scripts/__tests__/test-changed.test.mjs | 20 ++++++
scripts/test-changed.mjs | 8 +++
10 files changed, 142 insertions(+), 43 deletions(-)
Fusion-Task-Id: FN-6781
Fusion-Task-Lineage: d328501d-7ec4-40ac-a140-6a491ea560e2
The gate test asserted the OLD behavior — a paused graph exit in the `todo`
column parked `status:"failed"` with "operator action required". FN-6782
made the todo case benign (no failed park; benign log + cleared marker), so
split the parameterized test: `todo` now asserts the benign path (never
parked failed), `done` keeps the operator-action surfacing (log only, no
park). Full engine-core gate suite passes (644/644).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
coderabbit Major: reapLeakedConcurrencySlots captured executingIds once
before the loop, but each holder awaits getTask — a task could start
executing mid-sweep and have its worktree slot pulled. Refresh the
executing set immediately before clearPhantomExecutorBinding and skip if
the holder is now executing (same race the A1 recovery fix closed).
clearPhantomExecutorBinding's live-session refusal remains the last line
of defense; this avoids racing it. Added a mid-sweep race test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Substantive (A1 recoverPausedAbortFailures):
- Self-guard on globalPause/enginePaused at method entry (greptile P1) — the
public method must not requeue tasks an operator intentionally froze.
- Re-validate the FULL predicate with a FRESH executing set on the re-read
before the backward move (coderabbit Major + greptile): add fresh.userPaused
and column re-check so a task that became ineligible across awaits is skipped.
- Isolate audit emission in its own try/catch (coderabbit) so an audit throw
after a successful mutation can't log a false "recovery failed".
- Decouple the recovery predicate from the literal error text via shared
PAUSE_ABORT_PARK_ERROR_MARKER/OPERATOR_MARKER constants (greptile) — the
executor builds the parked message from the same constants.
- Use the wired clearPhantomExecutorBinding (live-session-guarded) instead of
the declared-but-never-wired releaseExecutorWorktreeOwnership, which no-op'd.
Nits:
- FNXC-prefix new comments in executor.ts, run-audit.ts, and the benign test
per repo comment policy.
- Fix a test-only type error on the clearPhantomExecutorBinding mock.
Added a test asserting the globalPause self-guard. Engine typecheck clean;
pause-abort/reaper/benign + regression suites pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reapLeakedConcurrencySlots() reclaims in-memory worktree slots whose
holder is no longer in-progress (the FN-6756 "in todo yet still a
maxWorktrees holder" leak) without an engine restart — defense-in-depth
behind the source fix.
- executor: new listWorktreeHolders() read-only introspection over
activeWorktrees; wired through in-process-runtime to SelfHealingManager.
- reaper releases ONLY when every guard agrees: not executing, task
missing or in todo/triage, past a 60s grace, and clearPhantomExecutor
Binding itself refuses (returns false) if a live session surface is
registered — so it can never pull a worktree from a running agent.
- registered in maintenance batch 2 (respects globalPause/enginePaused
skip + FN-4962 ordering).
- widened the clearPhantomExecutorBinding option type to surface its
boolean refusal signal.
Engine typecheck clean; 19 tests pass (new reaper 7 cases + regression).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>