A pause/resume abort parked status:"failed" on an earlier non-todo
observation stays dispatchable (scheduler filters on column+paused, not
status, scheduler.ts:1288) and re-enters the FN-6782 benign-todo branch.
That branch logged "benign" but left status:"failed"/error on the row,
so the board kept showing it failed and the deferred failure
notification fired (notification-service fire-time check sees
status==="failed") — contradicting the benign log. recoverPausedAbortFailures
that would clear it is suppressed during global/engine pause
(self-healing.ts:8125), so the failure survived the pause window.
Reconcile the row with the benign reclassification: clear status/error
when the live row carries them, so the board agrees it's benign and the
pending notification is suppressed at dispatch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the sidebar brand-row collapse control with a floating border toggle.\n\n- Remove the empty sidebar brand shell and keep the collapse control reachable in both expanded and rail modes.\n- Style the collapse button as a floating affordance on the sidebar edge.\n- Cover the missing brand shell, accessible toggle labels, pressed state, and persisted collapsed state in sidebar tests.\n- Update dashboard documentation to describe the border-mounted toggle.\n\nFiles changed:\n docs/dashboard-guide.md | 2 +-\n .../dashboard/app/components/LeftSidebarNav.css | 30 +++++++++++-----------\n .../dashboard/app/components/LeftSidebarNav.tsx | 28 ++++++++++----------\n .../components/__tests__/LeftSidebarNav.test.tsx | 27 +++++++++++++++++++\n 4 files changed, 56 insertions(+), 31 deletions(-)
Fusion-Task-Id: FN-6801
Fusion-Task-Lineage: e1384565-c0cc-4564-b556-f5b74a501c41
Add the Shadcn color family to dashboard theme selection and persisted theme validation.
- Register blue, green, red, purple, pink, orange, yellow, mono, and black Shadcn theme IDs across core and dashboard selectors.
- Add swatches and CSS token blocks for light and dark variants, including bootstrap loading coverage.
- Document the expanded theme count and include a minor changeset for the published CLI package.
Files changed:
.changeset/shadcn-color-variants.md | 5 +
docs/dashboard-guide.md | 2 +-
packages/core/src/types.ts | 10 +
.../dashboard/app/components/ThemeSelector.css | 126 +++
packages/dashboard/app/components/themeOptions.ts | 9 +
.../dashboard/app/hooks/__tests__/useTheme.test.ts | 41 +
packages/dashboard/app/index.html | 2 +-
packages/dashboard/app/public/theme-data.css | 1177 ++++++++++++++++++++
8 files changed, 1370 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-6756
Fusion-Task-Lineage: 143e717a-9718-419f-a6c5-34305e53c65d
Normalize mobile bottom navigation icon slots so unread indicators no longer skew perceived spacing.
- Wrap every mobile nav icon in a consistent token-sized slot.
- Keep chat unread dots inside the icon slot instead of protruding past it.
- Extend MobileNavBar tests to assert icon-slot structure and spacing CSS.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/fn-6800-mobile-nav-spacing.md | 5 ++++
packages/dashboard/app/components/MobileNavBar.css | 22 ++++++++++++-----
packages/dashboard/app/components/MobileNavBar.tsx | 28 ++++++++++++++++------
.../app/components/__tests__/MobileNavBar.test.tsx | 24 ++++++++++++++++++-
4 files changed, 65 insertions(+), 14 deletions(-)
Fusion-Task-Id: FN-6800
Fusion-Task-Lineage: 66ac21cf-6761-4b2c-b2ae-723998ae2632
Addresses review feedback (Greptile P1/P2, CodeRabbit) on the async
background integrity check. All three issues shared one root cause:
state read/cleared from a pre-await snapshot across the async boundary.
- Closed-instance race (P1): runBackgroundIntegrityCheck guards `this.closed`
before every in-process integrityCheck() call (both before the offload and
after the await). A connection closing mid-offload no longer throws out of
the scheduler.
- Stuck-pending on throw + late-subscriber race (P1/P2): the scheduler now
clears integrityCheckPending UNCONDITIONALLY in a finally, iterating the
CURRENT shared.subscribers set rather than a pre-await snapshot. A thrown
check, or a Database that subscribed during the seconds-long await window,
can no longer be left pinned at integrityCheckPending=true forever.
- No spawn timeout (P2): integrityCheckSqliteFileAsync now passes
AbortSignal.timeout(5m); a disk-stalled/hung sqlite3 child always settles
the promise (→ verified:false fallback) instead of leaking and stranding
the shared entry. AbortSignal.timeout's timer is unref'd, so it never keeps
the process alive at shutdown.
- FNXC markers added to the two new symbols (per AGENTS.md convention).
Rejected: the suggestion to remove the `// FNXC:Database` marker — FNXC is a
mandatory project convention (AGENTS.md), and a prior review flagged the
absence of these markers as a defect.
Tests: regression test that pending clears for all participants when the
check throws; test that runBackgroundIntegrityCheck returns ok without
throwing on a closed instance; strengthened the healthy-DB assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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