Commit Graph

12229 Commits

Author SHA1 Message Date
gsxdsm
10ebd0e025 fix(dashboard): keep quick-entry portal menus attached when space is tight
Extract shared fixed-menu positioning so deps/agent/node/priority pickers
clamp max-height without detaching from the trigger when free space is
shorter than the preferred dropdown height.
2026-07-25 21:01:25 -07:00
gsxdsm
c8427f1c1e test(engine): repair Full Suite drifts after plan-worktree cutover
Re-pin the execSync allowlist after self-healing/executor line shifts, capture
implementation-session tools under the graph-owned pause harness, treat
worktree alone as not past planning for replan targets, and age starved-
refinement fixtures past the post-escalation cooldown window.
2026-07-25 21:01:21 -07:00
gsxdsm
9f6aaa933d fix(engine): save worktree-written plans to the project and the database
Planning sessions run in the task's own worktree with the coding tool
surface, but the spec path handed to the planner is relative
(.fusion/tasks/<id>/PROMPT.md) while finalization reads it against
rootDir. A planner using the generic write tool instead of
fn_task_prompt_write stranded the spec inside the worktree, where
finalization could not see it and worktree disposal destroyed it.

project.tasks also has no `prompt` column, so PROMPT.md was
filesystem-only and the project checkout was the sole durable copy of
every plan.

Add plan-artifact-writeback.ts:

- reconcileWorktreePlanArtifact copies a worktree-stranded PROMPT.md
  back into the main project .fusion folder through
  store.updateTask({ prompt }), keeping File Scope validation, the root
  write, and the task.json sync atomic. Empty, absent, and identical
  worktree copies are no-ops so a correct spec is never clobbered.
- mirrorPlanToProjectDb mirrors the authoritative plan into the `plan`
  task document, which triage already reads as a planning-draft
  fallback, making that recovery path DB-backed. Identical content is
  skipped so revisions do not churn.

Both are best-effort: a failure never turns a good planning pass into an
error. Wired at the reconcile-before-finalize-read seam, at finalization
with the post-hygiene accepted content, and inside fn_task_prompt_write.

Covers the invariant rather than the repro: tests assert worktree-
stranded, root-only, empty worktree file, absent file, identical
content, persistence failure, redundant-mirror skip, and mirror failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 20:46:07 -07:00
gsxdsm
20c5e1ff4e fix(dashboard): let the mobile settings footer scroll sideways by touch
The footer rail already had overflow-x: auto, so it scrolled with a mouse,
but the global mobile `* { touch-action: pan-y }` lock intersected horizontal
pans away from every element. Opt the rail and its inner touch targets (the
buttons receive the touchstart; touch-action is not inherited) back into
pan-x, contain overscroll so a fling does not chain out to the document, and
free the button groups from the mobile max-width: 100% reset that squeezed
them into overlap instead of widening the scrollable content.

Also align the footer's media query with MOBILE_MEDIA_QUERY (max-width 768px
OR max-height 480px), which SettingsModal.tsx uses to pick the mobile footer
markup: landscape phones were rendering mobile markup under desktop CSS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 20:18:45 -07:00
gsxdsm
17b8bfecde fix(dashboard): stop small mobile board swipes from jumping several columns
Multi-column fling reach came from release velocity alone, so a quick short
thumb flick (~30px, several px/ms) bought 2-3 extra columns and the board flew
past the intended column. Extra pages now also have to be earned with travel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 20:02:20 -07:00
gsxdsm
b5707318b5 feat(engine): add force to fn_task_promote for agent-native override parity
fn_task_promote can now pass force:true to start execution when a task is still
waiting on planning or plan review, matching the dashboard's promote override.
The rejection message names the flag so a caller that hits the gate can decide,
and a forced release says the pending replan was cancelled rather than burying it.

Force stays opt-in per explicit promote request: the hold-release sweep and the
webhook event release have no force parameter, so FN-7648 still holds for every
automatic surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 20:02:14 -07:00
gsxdsm
82e0ce3132 fix(engine): reclaim task branches whose tip is an inherited foreign commit
The reclaim sweep's tip-already-merged arm vetoed on the branch tip's foreign
Fusion-Task-Id trailer alone. A task branch cut from the base that never
committed anything (planning aborted, card moved back to todo) points at the
PREVIOUS task's landed commit, so the veto fired on inherited metadata: the card
kept stale worktree/branch/baseCommitSha and re-logged
"already-merged rejected ... reason=foreign-task-tip" every sweep.

Hoist the merge-base diff proof already used by already-merged and
branch-misbound recovery into a shared foreignTipRejection helper and route all
three callers through it. Rejection still fires when the branch carries unique
content or the base already has this task's own commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 20:01:50 -07:00
gsxdsm
41d60f0355 feat(board): explain the unplanned promote rejection and let operators force past it
Promote on a held card printed the raw i18n key `board.rejection.unplannedForExecution`:
FN-8471 added the server-side code without a client case or catalog entry, so
translateRejection fell through to `t(messageKey, messageKey)`.

- Add the explicit rejection case (both translate helpers) plus the en catalog
  entry and secondary-locale stubs.
- promoteHeldTask(..., { force }) waives ONLY the unplanned-for-execution gate;
  hold membership, capacity and slot reservation still arbitrate. It clears a
  needs-replan/plan-review-unavailable status so triage rediscovery cannot pull
  the card back into the waived replan, and emits task:promote-forced-unplanned.
- POST /tasks/:id/promote accepts { force: true }; the board asks for explicit
  confirmation first and only offers the override for this rejection.

Force stays operator-only — the sweep, the webhook release and fn_task_promote
never set it, so FN-7648 still holds for every automatic surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 19:56:54 -07:00
gsxdsm
1efff4e83c test(core): speed up schema-applier PG tests by dropping psql subprocess spawns
Route admin CREATE/DROP DATABASE through a short-lived postgres.js maintenance
connection instead of spawning psql via execSync per call, and remove the
redundant DROP-before-CREATE (db names are pid+random, never pre-exist).
Cuts ~2 of 3 subprocess forks per test across ~55 tests; the slowest core
test file drops from ~90s under full-suite contention (32.6s->27s standalone)
with all 75 tests still green.

Fusion-Task-Id: FN-SLOW-TEST
2026-07-25 17:20:18 -07:00
gsxdsm
0c93f5c41e test(dashboard): speed up SettingsModal close-flush tests
Replace whole-tree *ByRole("button", { name: "Close" }) dismissal queries
with scoped querySelector targets (.modal-close, .modal-actions-right button,
.settings-embedded-mobile-close) in the SettingsModal suites and shared harness.
The role+name query recomputes accessible names across the large Settings tree,
adding ~700ms per dismissal test. Assertions are unchanged.

- footer Close 1058ms -> 332ms; embedded mobile close 1003ms -> 344ms
- models-auth workflow-lane flush ~1s -> 235ms
- expectSettingPersists/assertProjectModelSavePayload harness helpers scoped too

422 SettingsModal tests pass; lint clean.
2026-07-25 17:17:24 -07:00
gsxdsm
2560944663 chore(release): v0.74.0-beta.2
Version bump via changesets.
2026-07-25 16:16:07 -07:00
gsxdsm
d10d91bae5 fix(engine): stop planning when a card is withdrawn; sweep stale pre-execution worktrees
Withdrawing a card from planning (todo -> Ideas) now stops the work:
- triage aborts and disposes the planning session through the same path
  pause/delete already use, and clears status:"planning" so the planning badge
  goes away and the card reads as a plain idea again;
- the executor aborts in-flight graph work on any backward move out of
  todo/triage, so a Plan Review does not keep streaming against a card the
  operator pulled back;
- moving it back to todo needs no new code: the existing column wake fires and,
  with the status cleared, the card is an ordinary planning candidate again.

Pre-execution worktrees (planning acquires one now) are reclaimed two ways: an
immediate release on an explicit withdrawal, and a self-healing sweep
`reconcile-pre-execution-worktrees`. The sweep is deliberately timid — 30 days
of complete inactivity, and it skips anything active or waiting (todo,
executing, in-review, done, paused, carrying any status, blocked, or scheduled
for recovery). Every real safety condition lives in the executor: never
executed, no live session, clean branch, nothing uncommitted.

hasAdvancedPastPlanning no longer reads a worktree as execution evidence.
Planning owns a worktree now, so that signal would have made every planning
write skip; execution timestamps carry the meaning instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 15:20:17 -07:00
gsxdsm
168819b35d fix(engine): run every lane in the task worktree; contention is a wait, not a failure
Contention prevention (why tasks shared a path at all):
- Planning ran `tools: "coding"` at the repo root, so every planner had write
  tools in the operator's checkout and all planners shared one path. Planning
  now acquires the task's own worktree (TriageProcessor.acquirePlanningWorktree
  -> TaskExecutor.ensureTaskWorktreeForPlanning).
- Graph nodes with no worktree acquired one instead of falling back to rootDir,
  so Plan Review / Code Review / custom gates all run isolated. Plan Review
  re-acquires when its recorded worktree is gone, replacing FN-7996's
  run-from-the-repo-root degrade. Workspace projects are unchanged.
- Registration goes through acquireActiveSessionPath, which reclaims a leaked
  entry whose holder is provably dead and aged past the FN-5256 floor. A live
  holder still contends — real serialization is never clobbered.

Classification (the reported symptom):
- A lease held by another task is no longer a provider failure. It carries
  SESSION_CONTENTION_HOLD_VALUE, classifies transient, is excluded from
  isNonPlanDefectPlanReviewFailure, and stops burning the node's fast retries.
- The executor waits it out on a 10-attempt 5s->60s ladder and then leaves the
  task cleanly queued. There is no terminal branch: contention always ends, so
  parking would only ask a human to press Retry on a condition that fixed
  itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:59:53 -07:00
gsxdsm
e6b2da6cae fix(engine): let concurrent tasks run Plan Review on the shared repo root
Plan Review needs no worktree, so it runs rooted at the project root. The
activeSessionRegistry key was the bare root path, so the second task to reach
Plan Review hit ActiveSessionPathHeldByForeignTaskError ("path ... is held by
task FN-1398; task FN-1403 may not overwrite it"). That surfaced as a Plan
Review provider failure, burned the in-place retry budget against a hold no
retry could clear, and left the task parked.

Task-scope the registry key for any session rooted at rootDir, in every project
mode — the workspace fix already did this for the shared browse-root. Root
exclusivity protects nothing here: write-capable nodes are refused at the root
outright, and every isPathActive consumer guards removable worktree paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 13:55:54 -07:00
gsxdsm
b0fdef433d fix(dashboard): stop the mobile Settings footer clipping the update notice
The mobile footer is a single nowrap scrolling rail, so an update banner
joining it clipped mid-sentence and pushed Import/Export/Reset/Close
off-screen. Render the update-check result in its own full-width row above
the rail on mobile; desktop/tablet keep it inline next to the version button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 11:21:11 -07:00
gsxdsm
a0496c175c chore(release): v0.74.0-beta.1
Version bump via changesets.
2026-07-25 10:08:15 -07:00
gsxdsm
e4fb3f994e fix(dashboard): one Planning add-comment trigger, shown when the selection is done
The plan review pane rendered both the document-adjacent and the action-rail
"Add comment to selection" triggers, so operators saw duplicate buttons. Delete
the document variant and its --document/--mobile CSS pair; the rail button is
now the single control at every breakpoint.

Selection capture also ran on every mid-drag selectionchange, which mounted and
unmounted the trigger as the user dragged. Gate quote writes between pointerdown
and pointerup inside the plan document so the control appears once, on release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 10:02:02 -07:00
gsxdsm
10df734bd1 fix(core): seed the prompt for quick-add Start creates; instrument hold-release
Quick-add "Start" collapses create+promote into one request: it submits the
workflow id AND the post-intake `todo` column together, so the card lands in
`todo` having never sat in the workflow's manual intake column. The intake test
in task-creation.ts only matched `triage` or the resolved intake column, so the
card got generateSpecifiedPrompt — whose hard-coded boilerplate steps
("Implement the required changes") no planner ever wrote.

That stranded the card permanently: triage's todo-discovery admits a card only
when its PROMPT.md reads as a seed, so the placeholder spec was classified
"already planned" and never planned, while nothing could execute it either
(steps: []). It sat in Todo forever with no log line in any lane. Observed on
FN-8587.

Creates into `todo` on a manual-intake workflow (resolved intake is not the
legacy `triage`) now get the bootstrap seed. The pinned contract for a plain
direct create into todo on the default workflow — which intentionally keeps
generateSpecifiedPrompt — is untouched, and both create sites are fixed in step.

Also instrument the hold/release sweep, which had reasons but no timings:
per-task held duration reported on release, a per-sweep summary breaking out the
prefetch cost (a sequential await per non-archived task, so it scales with board
size rather than with held cards), and a warn when a sweep exceeds 2s — so a
"ready card doesn't move" delay can be attributed between poll cadence, sweep
cost, and a card genuinely queued on capacity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:52:18 -07:00
gsxdsm
e712b6faea fix(engine): dispatch as soon as planning finishes
Closes the second half of the "started card does nothing" gap. Plan-in-place
workflows (Coding (Ideas)) finalize by clearing `status` in place — finalize
deliberately skips the triage->todo move — so a card that just became executable
emits only a bare task:updated. None of the scheduler's existing event wakes
(task:created, globalPause/enginePaused unpause, per-task unpause) fire for that
transition, so the operator paid one poll interval for planning to start and
another for execution to start.

Track ids seen with status "planning" and trigger a scheduling pass when they
return to a dispatchable state, mirroring the pausedTaskIds unpause tracker.
Guarded on !status, not paused/userPaused, and a schedulable column, so a
planning -> failed/awaiting-approval park does not trigger a pointless pass.
schedule()'s re-entrance guard drops the call if a pass is already in flight,
and the id is cleared on task:deleted alongside the other per-task sets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:27:26 -07:00
gsxdsm
9cbed745f9 feat(dashboard): surface the planning-capacity wait and stop overclaiming Start
Start performs a bare column move, so "Started planning {id}" reported an
outcome the handler cannot observe: the engine still has to admit the card, and
a busy pool (per-project maxConcurrent or cross-project globalMaxConcurrent) can
defer that indefinitely. The wait was only visible in the engine log
("Plan throttled by running-agent cap|global semaphore"), so a throttled card
looked like a bug.

- Add a "Queued to plan" badge: the exact complement of "Ready" (same idle-Todo
  conditions, but no steps yet, so it waits for a PLANNING slot rather than a WIP
  slot). Three Todo states are now distinguishable: planning in flight, queued to
  plan, and ready. Pause suppression matches Ready; the badge reuses the existing
  status-badge primitives with a color-mix tint, no new tokens.
- Retitle the Start toast to "Queued {id} for planning" in both call sites
  (TaskCard Start and QuickEntryBox quick-add Start).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:14:11 -07:00
gsxdsm
b5578448fa fix(workflow): start planning immediately when a task is started
Pressing Start on a Coding (Ideas) card only writes a column move — there is no
dispatch call in that path — so planning did not begin until the triage
processor's next timer tick, up to pollIntervalMs (15s default) later. The
"Started planning" toast was optimistic and the card just sat in Todo.

- Wake planning discovery on the store's task:updated/task:created event when a
  task lands in todo/triage. Binding the wake to the store event rather than the
  Start button covers every move surface (board drag, context menu, task detail,
  List view, CLI, agent tools, POST /tasks/:id/move) by construction. The wake is
  advisory: it only advances WHEN the poll runs, so every pause, seed-prompt,
  dependency, and concurrency gate still applies.
- Admit a todo task whose PROMPT.md is missing instead of dropping it through a
  silent `catch {}`. The scheduler KEEPS a candidate whose prompt it cannot read,
  so such a card was invisible to planning while still visible to dispatch, with
  no log line in either lane. Unreadable (non-ENOENT) prompts now log.
- Route the scheduler's dispatch filter through the shared isUnplannedSeedPrompt
  predicate. Its open-coded strict bootstrap compare disagreed with triage on the
  refinement-seed shape, leaving hold-release as the only thing between an
  executor and a prompt containing just the operator's feedback text. The
  predicate also normalizes line endings/trailing whitespace, so a CRLF or
  trailing-newline round-trip no longer reclassifies an unplanned card as planned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:14:11 -07:00
gsxdsm
1c38c6e93b fix(dashboard): make duplicate-warning "Open" actually open the task
The "Open" button on a possible-duplicate warning did nothing — the modal
closed and no task appeared. The app has two task deep-link shapes and only
one had a consumer: `?task=<id>` was handled by useDeepLink, while
`#/tasks/<id>` had no `hashchange` listener anywhere in the dashboard.

Five surfaces write the hash form. InlineCreateCard and NewTaskModal write it
unconditionally, so their Open was always dead. QuickEntryBox, Column, and
ListView try an in-memory board lookup first and fall through to the dead hash,
which is why it looked intermittent: duplicate matches come from a
project-wide searchTasks, so a match that is `done` or outside the loaded
board slice misses the lookup and lands on the no-op.

Handle the hash form inside useDeepLink so the app keeps one deep-link
authority owning both shapes, rather than forking a parallel hook. The id is
resolved by fetch instead of an in-memory lookup (that lookup is the dead end
being removed), the hash is cleared via replaceState so re-Opening the same
task fires again, and an unresolvable id toasts instead of failing silently.

Regression tests assert the invariant shared by all five surfaces rather than
the single reported repro: a written hash always resolves to an open or a
toast, never a no-op. Verified against the pre-fix code — 5 of the 6 new tests
fail without this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:07:09 -07:00
gsxdsm
13ff8850f4 fix(dashboard): restore System panel "Restart all agents" under PostgreSQL
POST /system/agents/restart-all constructed its AgentStore from rootDir alone,
with no AsyncDataLayer, so it fell through to the sync SQLite Database path
deleted under VAL-REMOVAL-005 and threw instead of bouncing agents. It now
builds the store against the scoped project's PostgreSQL layer via
requireAsyncLayer, failing loudly when project wiring is incomplete rather
than reading a SQLite shadow. This was the last unmigrated AgentStore call
site; the route test harness lacked getAsyncLayer, which is why nothing
caught it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:05:41 -07:00
gsxdsm
2ea5ac206d fix(cli-tests,dashboard): align quiet-mode result spies and voiceInput allowlist
CLI JSON/create success lines write via result() (raw stdout) so quiet mode
cannot drop machine-readable output; capture that seam in research/update/task
tests instead of console.log. Allowlist nested voiceInput settings for the
FN-7505 default-description guard and ship locale keys for Voice Input UI.
2026-07-25 09:02:59 -07:00
gsxdsm
927efb1477 fix(workflow): allow Coding (Ideas) cards to move back from Todo to Ideas
A legacy source column (todo/in-progress/...) validated moves only against the
closed VALID_TRANSITIONS map, which cannot know about a workflow-declared
column, so Todo -> Ideas was rejected even though the board drag pre-check and
context menu both offered it. Legacy sources now union VALID_TRANSITIONS with
the task's workflow-resolved adjacency, resolved lazily only when the legacy
table alone would reject. builtin:coding adjacency is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 08:45:52 -07:00
gsxdsm
3597d06380 FN-8578: pin verified Parakeet v3 model asset
Enable secure downloads of the upstream Parakeet v3 voice model.

- Pin the archive URL, filename, SHA-256, expected files, and nested extraction layout.
- Accept the archive's BSD tar timestamp format while preserving safe extraction checks.
- Cover download gates and installation behavior, and document the verified model asset.

Files changed:
 .changeset/fn-8578-parakeet-v3-asset-pin.md        |  7 ++++
 docs/dashboard-guide.md                            |  2 +-
 docs/settings-reference.md                         |  8 ++--
 .../src/stt/__tests__/model-manager.test.ts        | 46 ++++++++++++++++++++++
 .../dashboard/src/stt/__tests__/voice-stt.test.ts  | 33 +++++++++++++++-
 packages/dashboard/src/stt/model-manager.ts        | 13 +++---
 packages/dashboard/src/stt/types.ts                | 18 +++++++--
 7 files changed, 114 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-8578

Fusion-Task-Lineage: ab397190-fa0c-491e-b0f8-0ab6641be95a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 08:21:00 -07:00
gsxdsm
2a2b157cb9 FN-8585: fix dashboard composer test source reads
Stabilize dashboard composer tests when Vitest launches from the workspace root.

- Resolve dashboard test source fixtures relative to the app directory.
- Migrate affected component tests away from cwd-relative CSS reads.
- Enforce the fixture convention in test hooks and document it.

Files changed:
 docs/testing.md                                    |  4 +++
 package.json                                       |  6 ++--
 .../__tests__/AuthTokenRecoveryDialog.test.tsx     |  3 +-
 .../components/__tests__/ChatView.mobile.test.tsx  |  5 +--
 .../__tests__/EngineControlMenu.test.tsx           | 11 ++----
 .../components/__tests__/FloatingWindow.test.tsx   |  5 +--
 .../app/components/__tests__/ListView.test.tsx     |  5 +--
 .../__tests__/MissionInterviewModal.test.tsx       |  3 +-
 .../app/components/__tests__/MobileNavBar.test.tsx |  3 +-
 .../app/components/__tests__/NewTaskModal.test.tsx |  3 +-
 .../__tests__/PlanningModeModal.initial.test.tsx   |  3 +-
 .../PlanningModeModal.ui-interactions.test.tsx     |  7 ++--
 .../components/__tests__/PrCreateModal.test.tsx    |  3 +-
 .../__tests__/QuickChat.persist.test.tsx           |  3 +-
 .../components/__tests__/QuickEntryBox.test.tsx    |  3 +-
 .../components/__tests__/ReportActionMenu.test.tsx |  9 ++---
 .../app/components/__tests__/ReportModal.test.tsx  |  3 +-
 .../__tests__/ShadcnColorPicker.test.tsx           |  3 +-
 .../components/__tests__/TerminalModal.test.tsx    |  3 +-
 .../components/__tests__/ThemeDropdown.test.tsx    |  9 ++---
 .../__tests__/WorkflowNodeEditor.test.tsx          |  5 +--
 .../WorkflowOptionalStepsDropdown.test.tsx         |  3 +-
 .../components/__tests__/WorkflowSwitcher.test.tsx |  5 +--
 .../app/components/__tests__/board-mobile.test.tsx |  4 +--
 .../__tests__/CommandCenterControls.test.tsx       |  6 ++--
 .../__tests__/SystemControlsArea.test.tsx          |  5 +--
 .../__tests__/SystemStatsArea.test.tsx             |  3 +-
 .../command-center/areas/__tests__/areas.test.tsx  |  3 +-
 .../__tests__/KeyboardShortcutsSection.test.tsx    |  3 +-
 .../app/test/__tests__/cssFixture.test.ts          | 35 +++++++++++++++++++
 packages/dashboard/app/test/cssFixture.ts          | 12 +++++++
 .../check-no-cwd-relative-dashboard-test-reads.mjs | 39 ++++++++++++++++++++++
 32 files changed, 162 insertions(+), 55 deletions(-)

Fusion-Task-Id: FN-8585

Fusion-Task-Lineage: 83a35fb6-a29d-4e97-b282-1054c68b8cc9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 06:22:23 -07:00
gsxdsm
9575e4b493 FN-8580: deduplicate voice availability settings reads
Voice dictation now shares a project-scoped availability request without fetching settings separately.

- Add a deduplicated voice availability hook for status and browser support
- Update voice dictation to consume shared availability state
- Cover composer settings isolation and voice availability behavior
- Add a patch changeset for duplicate settings request fixes

Files changed:
 .changeset/fn-8580-voice-settings-read-dedupe.md   |   7 +
 .../composer-settings-read-isolation.test.tsx      | 200 +++++++++++++++++++++
 .../hooks/__tests__/useVoiceAvailability.test.tsx  |  70 ++++++++
 .../app/hooks/__tests__/useVoiceDictation.test.tsx |  27 ++-
 .../dashboard/app/hooks/useVoiceAvailability.ts    |  69 +++++++
 packages/dashboard/app/hooks/useVoiceDictation.ts  |  31 +---
 6 files changed, 360 insertions(+), 44 deletions(-)

Fusion-Task-Id: FN-8580

Fusion-Task-Lineage: fac6f7f6-dda9-45d8-b501-d3762dca26df

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 05:48:35 -07:00
gsxdsm
32adc0a153 FN-8573: add voice dictation controls to composers
Add shared voice dictation support across chat, planning, and task-creation composers.

- Add browser speech-recognition and composer insertion hooks.
- Provide a reusable mic button with accessible recording states.
- Integrate and test dictation across all supported composer surfaces.
- Document the feature and add a package changeset.

Files changed:
 .changeset/fn-8573-voice-dictation-mic.md          |   7 +
 docs/dashboard-guide.md                            |   6 +
 packages/dashboard/app/components/ChatView.tsx     |  44 +++-
 .../dashboard/app/components/ComposeChatPanel.tsx  |   8 +-
 packages/dashboard/app/components/MicButton.css    |   5 +
 packages/dashboard/app/components/MicButton.tsx    |  20 ++
 .../dashboard/app/components/PlanningModeModal.tsx |  82 ++++--
 .../dashboard/app/components/QuickEntryBox.tsx     |   4 +
 .../app/components/StandardChatSurface.tsx         | 108 +++++---
 packages/dashboard/app/components/TaskChatTab.tsx  |   4 +
 packages/dashboard/app/components/TaskComments.tsx |  66 ++---
 packages/dashboard/app/components/TaskForm.tsx     |  18 +-
 .../app/components/TaskPlannerChatTab.tsx          |   7 +
 .../app/components/__tests__/MicButton.test.tsx    |  28 +++
 .../components/__tests__/QuickEntryBox.test.tsx    |   5 +
 .../app/components/__tests__/TaskForm.test.tsx     |   5 +
 .../app/components/__tests__/insertAtCaret.test.ts |  25 ++
 .../__tests__/voice-dictation-composers.test.tsx   | 230 +++++++++++++++++
 packages/dashboard/app/components/insertAtCaret.ts |  20 ++
 .../hooks/__tests__/useComposerDictation.test.tsx  |  58 +++++
 .../app/hooks/__tests__/useVoiceDictation.test.tsx | 158 ++++++++++++
 .../dashboard/app/hooks/useComposerDictation.ts    | 112 +++++++++
 .../dashboard/app/hooks/useVoiceDictation.ts       | 277 +++++++++++++++++++++
 23 files changed, 1204 insertions(+), 93 deletions(-)

Fusion-Task-Id: FN-8573

Fusion-Task-Lineage: 905ae982-3e63-494f-aba8-e3665f00cdda

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 05:15:02 -07:00
gsxdsm
11db36187f FN-8572: add voice input settings and model controls
Add project-scoped voice dictation controls with observable local model management.

- Add the Voice Input settings section with opt-in enablement and runtime-safe status handling.
- Add Parakeet v3 model download, removal, progress polling, search, documentation, and regression coverage.
- Add a minor release changeset for Voice Input settings.

Files changed:
 .changeset/fn-8572-voice-input-settings.md         |   7 ++
 docs/dashboard-guide.md                            |   4 +
 docs/settings-reference.md                         |   1 +
 .../dashboard/app/components/SettingsModal.tsx     |   7 +-
 .../VoiceInputSection.modal-visibility.test.tsx    |  19 +++
 .../settings/__tests__/VoiceInputSection.test.tsx  | 137 +++++++++++++++++++++
 .../settings/__tests__/section-keys.test.ts        |   1 +
 .../search/__tests__/settings-search-index.test.ts |   1 +
 .../app/components/settings/search/entries.ts      |   2 +
 .../app/components/settings/section-keys.ts        |   2 +
 .../settings/sections/VoiceInputSection.css        |  48 ++++++++
 .../settings/sections/VoiceInputSection.search.ts  |  14 +++
 .../settings/sections/VoiceInputSection.tsx        | 128 ++++++++++++++++++++
 13 files changed, 370 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8572
Fusion-Task-Lineage: 2d2ad0df-3765-4d19-a6f3-7c2b5ca5df95
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 02:51:28 -07:00
gsxdsm
9a2aea6120 FN-8571: add Parakeet STT transcription backend
Add a configurable sherpa-onnx Parakeet v3 speech-to-text service and dashboard API.

- Add model download, validation, caching, and lifecycle management for the bundled STT runtime.
- Register multipart-safe voice transcription routes with request-size handling and service-backed responses.
- Expose STT settings, documentation, release metadata, and focused integration coverage.

Files changed:
 .changeset/fn-8571-voice-stt-backend.md            |   7 +
 docs/settings-reference.md                         |  12 +
 .../core/src/__tests__/settings-parity.test.ts     |  10 +
 packages/core/src/index.ts                         |   2 +-
 packages/core/src/settings-schema.ts               |   2 +
 packages/core/src/types.ts                         |   2 +
 packages/core/src/types/settings-scope.ts          |  19 ++
 packages/dashboard/package.json                    |   3 +
 .../voice-body-parser-integration.test.ts          |  67 ++++++
 packages/dashboard/src/routes.ts                   |   2 +
 packages/dashboard/src/routes/README.md            |  74 +++---
 .../routes/__tests__/register-voice-routes.test.ts | 159 ++++++++++++
 .../src/routes/create-api-routes-mount-sequence.ts |   2 +-
 .../dashboard/src/routes/register-voice-routes.ts  |  92 +++++++
 packages/dashboard/src/server.ts                   |  21 +-
 .../src/stt/__tests__/model-manager.test.ts        | 171 +++++++++++++
 .../dashboard/src/stt/__tests__/voice-stt.test.ts  |  48 ++++
 packages/dashboard/src/stt/model-manager.ts        | 268 +++++++++++++++++++++
 packages/dashboard/src/stt/parakeet-service.ts     |  77 ++++++
 packages/dashboard/src/stt/types.ts                |  14 ++
 pnpm-lock.yaml                                     |  65 +++++
 21 files changed, 1086 insertions(+), 31 deletions(-)

Fusion-Task-Id: FN-8571

Fusion-Task-Lineage: b11e0cea-9c9c-41d4-a141-c47e5ed56dba

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 02:17:31 -07:00
gsxdsm
b31bee03a8 FN-8576: add global quiet flag to CLI
Add a global quiet mode that suppresses informational CLI stdout without hiding requested results.

- Parse --quiet/-q and FUSION_QUIET with command and output exemptions.
- Route result output and interactive prompts around the reversible stdout gate.
- Document the flag and cover quiet output, prompts, and argument parsing.

Files changed:
 .changeset/fn-8576-cli-quiet-flag.md               |   7 ++
 docs/cli-reference.md                              |   8 ++
 packages/cli/src/__tests__/bin.test.ts             |  15 +++
 packages/cli/src/__tests__/cli-quiet-mode.test.ts  |  63 +++++++++++++
 .../__tests__/cli-quiet-prompt-surfaces.test.ts    |  33 +++++++
 packages/cli/src/bin.ts                            |  39 ++++++--
 packages/cli/src/commands/experiment-finalize.ts   |   3 +-
 packages/cli/src/commands/git.ts                   |  17 ++--
 packages/cli/src/commands/goals.ts                 |   4 +-
 packages/cli/src/commands/mission.ts               |   8 +-
 packages/cli/src/commands/node.ts                  |   4 +-
 packages/cli/src/commands/onboard.ts               |   4 +-
 packages/cli/src/commands/org-import.ts            |   5 +-
 packages/cli/src/commands/plugin.ts                |  14 ++-
 packages/cli/src/commands/port-prompt.ts           |   4 +-
 packages/cli/src/commands/project.ts               |   6 +-
 packages/cli/src/commands/research.ts              |   3 +-
 packages/cli/src/commands/task.ts                  | 101 +++++++++++---------
 packages/cli/src/commands/update.ts                |   3 +-
 packages/cli/src/commands/workflow.ts              |  13 +--
 packages/cli/src/output.ts                         | 104 +++++++++++++++++++++
 packages/cli/src/project-resolver.ts               |  20 ++--
 22 files changed, 388 insertions(+), 90 deletions(-)

Fusion-Task-Id: FN-8576

Fusion-Task-Lineage: 2493d6d5-bbd9-4fc2-b903-950457ef30b0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 00:54:46 -07:00
gsxdsm
0056d75314 FN-8569: surface unrecoverable report health
Classify parked direct reports as operator-actionable even when their stored state appears live.

- Add a reusable Reports Health classifier that prioritizes pause markers.
- Clear stale pause markers during live-state resumes without removing diagnostic errors.
- Cover desynchronized report states and document the health invariant.

Files changed:
 .../fn-8569-reports-health-error-unrecoverable.md  |  7 +++
 docs/architecture.md                               |  1 +
 .../agent-store-pause-marker-clear.test.ts         | 65 +++++++++++++++++++
 packages/core/src/agent-store.ts                   | 12 ++++
 .../src/__tests__/heartbeat-executor.test.ts       | 27 ++++++--
 .../engine/src/__tests__/reports-health.test.ts    | 73 ++++++++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             | 36 ++++++-----
 packages/engine/src/index.ts                       |  6 ++
 packages/engine/src/reports-health.ts              | 70 +++++++++++++++++++++
 9 files changed, 278 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-8569

Fusion-Task-Lineage: 37c798a0-1f2b-4221-a0c6-ccbff8d72696

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-24 23:57:49 -07:00
gsxdsm
38e625a3c2 fix(i18n): add anthropicAuthPreference strings to all locales
English-source keys were added for en only; the i18n parity gate requires
identical key sets across es/fr/ko/zh-CN/zh-TW catalogs.
2026-07-24 23:35:05 -07:00
gsxdsm
cb8d116b2d FN-8568: apply new tags to open chats
Assign context-menu-created tags to their open conversations immediately.

- Return the created chat tag from the chat hook.
- Preserve existing session tags while assigning the new tag through either creation control.
- Cover Enter, Add, existing-tag, and blank-name paths.
- Add a patch changeset for the chat-tag behavior fix.

Files changed:
 .changeset/fn-8568-chat-tag-creation.md            |  7 ++
 packages/dashboard/app/components/ChatView.tsx     | 36 ++++++++-
 .../__tests__/ChatView.core-interactions.test.tsx  | 90 ++++++++++++++++++++++
 packages/dashboard/app/hooks/useChat.ts            |  4 +-
 4 files changed, 133 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-8568

Fusion-Task-Lineage: 38ee0e02-adbb-4b21-9058-486310da2190

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-24 23:31:11 -07:00
gsxdsm
4b0edf65d5 fix(dashboard-tests): index Anthropic auth preference and pause badge contract
- Register AuthenticationSection search entry for anthropicAuthPreference
- Map setting default description + en locale strings for the new control
- Expect executorState paused when enginePaused with zero running tasks
2026-07-24 23:24:28 -07:00
gsxdsm
084dd76d64 feat(release): write release copy with opus and draft tweets for betas too
- distillation runs on opus (env-overridable) with a 4-minute budget
- highlights must name the surface and outcome; vague filler is banned
- tweets target 200-280 chars with concrete changes and varied structure
- betas get their own tester-facing draft carrying `fn update --channel beta`
- prerelease openers read as "Fusion 0.74 beta:" instead of "Fusion 0.74-beta.0"
2026-07-24 23:15:36 -07:00
gsxdsm
58d55d6439 chore(release): v0.74.0-beta.0
Version bump via changesets.
2026-07-24 23:12:53 -07:00
gsxdsm
a03eb0b39c fix(dashboard-tests): green Full Suite after chat tags, TaskForm meta, remote lifecycle
- Replace undefined --space-2xs in ChatView tag menu with calc(--space-xs / 2)
- Stub fetchChatTags/create/rename/delete on streaming-thread api mock for useChat mount
- Expect TaskForm model/tracking callbacks with TaskFormValueChangeMeta source
- Drive remote tunnel lifecycle via one stop then error status (no double Stop race)
2026-07-24 23:02:16 -07:00
gsxdsm
330e4970f0 refactor(release): move the version-anchor package.json rewrite into the shared lib
Makes the re-anchor file mutation unit-testable alongside the anchor decision.
2026-07-24 23:01:11 -07:00
gsxdsm
dba9746287 fix(release): base the next beta on the shipped stable version
After a stable release, main stayed inside the old pre-mode cycle, so the next
beta numbered below the published stable (v0.73.0-beta.7 after v0.73.0) and the
dev checkout kept reporting the last beta.

- beta releases re-anchor a stale pre-mode cycle on the newest stable tag
- both channels refuse a version at or below the newest published stable
- stable promotion now back-merges release into main automatically (fail-soft
  on conflict) so the local dev version is the stable version
2026-07-24 22:59:19 -07:00
gsxdsm
1847d2bc2a chore(release): back-merge v0.73.0 from release
Keeps .changeset/project-switch-modal-reset.md: it was re-edited on main after
the v0.73.0 cut (f9f283293) and covers an unreleased fix.
2026-07-24 22:55:17 -07:00
gsxdsm
3c3acbf769 docs(settings): record why global settings are not all in postgres
Global settings are already split three ways -- values in settings.json, the
revision journal in postgres, and globalMaxConcurrent/defaultProjectId in
central tables -- so the recurring "finish the cutover" proposal keeps getting
re-litigated from scratch.

Write down the two hard constraints (startup-factory reads
embeddedPostgresMaxConnections to start postgres; createFusionAuthStorage is
synchronous and host-agnostic), the recovery argument, and the one real
motivation for a partial move (multi-node policy consistency), plus the
machine-tier vs operator-policy-tier rule for placing new keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:46:27 -07:00
gsxdsm
a727e20843 fix(auth): read the Anthropic credential preference from the resolved global dir
Global settings live in settings.json under the resolved global dir, which
falls back to the pre-rename ~/.pi/fusion and ~/.pi/kb dirs for installs that
never migrated. The reader hardcoded ~/.fusion, so on those installs the
operator's preference was silently ignored and resolution fell back to raw-key
precedence -- the same silent fallback the preference exists to remove.

Mirror the legacy-aware lookup getModelRegistryModelsPath already does for
models.json rather than importing core's resolver, which throws under VITEST
when called without an explicit dir.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:38:15 -07:00
gsxdsm
190cc041bc fix(lanes): resolve a model pair everywhere a session is constructed
Audit of the class behind the planning bug: createFnAgent forwards no model
unless both defaultProvider and defaultModelId are set, after which
pi-coding-agent picks its own built-in default (anthropic/claude-opus-4-8).
Seven lanes resolved no pair at all, so they hit that path on every call --
a permanent 401 invalid x-api-key for custom-provider and subscription
operators, and a hole in test-mode forcing:

- milestone/slice interviews (no model plumbing at all)
- subtask breakdown, triage and streaming paths
- agent generation
- text refine and goal drafting
- agent reflection (optional ctor pair no production caller supplies)

Two more resolved the halves independently, which the runtime treats as
unset: research synthesis defaults and pr-conflict-resolver's hand-rolled
copy of resolveProjectDefaultModel (which also skipped test-mode overrides).

Add lane-session-model.ts as the shared resolver and a source ratchet that
fails when a dashboard session is constructed from an inline literal with no
model decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:32:49 -07:00
gsxdsm
8dc6598aa0 feat(auth): let operators choose which Anthropic credential wins
An operator can hold a raw Anthropic API key and a Claude subscription OAuth
login at once, and the raw key always won silently. A stale or revoked saved
key therefore shadowed a working subscription and failed every direct Anthropic
call with 401 invalid x-api-key, while both Settings cards still read Active.

Add the global anthropicAuthPreference setting ("api-key" default, preserving
the historical precedence, or "subscription"), read in resolveAnthropicRuntimeApiKey
straight from ~/.fusion/settings.json so it applies without a restart and needs
no settings plumbing through createFusionAuthStorage. Neither value removes a
source: with one credential configured, resolution reaches it either way.

Settings -> Authentication now names the credential in use on the two Anthropic
cards and renders the control, but only when both are actually connected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:32:39 -07:00
gsxdsm
8156308695 feat(quick-add): show a visible Start button for waiting-column workflows
Replace the hidden long-press/right-click Start menu on Quick Add's Save with a
visible Start button in the action row, rendered only for workflows whose first
visible lane is a hold column (or Coding (Ideas)). Eligibility, the workflow
snapshot, the create-time column override, and the hold-first follow-up move are
unchanged; ineligible workflows render no Start chip or shell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:32:30 -07:00
gsxdsm
1959e7c02c fix(planning): keep the selected model pair on rebuilt planning agents
ensureSessionAgent rebuilt the agent with an empty provider/model pair while
preserving draftThinkingLevel, so resumed turns (respond, retry, rewind, drafts
resumed after the in-memory agent was dropped) fell through to the runtime's
built-in default model (anthropic/claude-opus-4-8) and hit api.anthropic.com
with a key the operator never configured. The non-streaming start had the same
hole. Resolve the pair from the persisted draft, then the lane's settings, on
every rebuild and start.

Also route planning through createResolvedAgentSession like chat/executor/merger
so CLI and plugin runtimes can own their own auth and planning emits
session:runtime-resolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:17:39 -07:00
gsxdsm
e01dc7d3d3 feat(board): speed up kanban scrolling and drop desktop snapping
Desktop board scrollers no longer snap: the browser's proximity-snap settle
animation was capturing wheel/trackpad pans and reading as a slow, sticky drag
toward a column center. Base `.board`, `.board-workflow-columns`, and
`.lane-columns` declare `scroll-snap-type: none`; proximity snap is re-declared
in phone-tier media blocks only, where the JS column pager owns paging.

On phones, the hook now owns post-lift motion instead of waiting it out. A
directional lift kills native inertia and animates to its target column via rAF
ease-out (~190-300ms), so the page starts moving on lift rather than after a
native fling that can coast for most of a second. Fling reach is preserved by
deriving a page count (1-3) from release velocity sampled off the board's own
scroll ticks, not from however far inertia happens to travel.

Guards: re-touch cancels the animation and hands the axis back to the finger;
reduced motion, missing rAF, and sub-2px distances fall back to the instant hard
jump; unmount mid-animation restores the frozen inline styles; a fast drag that
rests before lifting is not treated as a flick.

Tap-to-stop-during-momentum is gone as an interaction (no long coast remains to
interrupt). Its regression test is reframed around the equivalent seam: a drag
that interrupts the page animation wins over the pending page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:16:11 -07:00
gsxdsm
4b4df1ff30 FN-8565: restore tablet task modal resizing
Restore resizable Task Detail and New Task windows on 768px touch tablets.

- Classify known touch tablets separately from phone sheets
- Restore bounded modal geometry and resize grips with keyboard support
- Add regression coverage, documentation, and a patch changeset

Files changed:
 .changeset/fn-8565-tablet-task-modal-resize.md     |  7 +++
 docs/dashboard-guide.md                            |  5 ++
 .../task-detail-modal-tablet-width.test.ts         | 15 +++++
 packages/dashboard/app/components/NewTaskModal.css | 18 +++++-
 packages/dashboard/app/components/NewTaskModal.tsx | 44 +++++++++++++-
 .../dashboard/app/components/TaskDetailModal.css   | 30 ++++++++++
 .../dashboard/app/components/TaskDetailModal.tsx   | 40 +++++--------
 .../app/components/__tests__/NewTaskModal.test.tsx | 68 +++++++++++++++++++++-
 ...etailModal.responsive-and-dependencies.test.tsx | 51 ++++++++++++++++
 .../hooks/__tests__/useModalResizePersist.test.tsx | 53 ++++++++++++++++-
 .../dashboard/app/hooks/useModalResizePersist.ts   | 58 +++++++++++++++---
 11 files changed, 352 insertions(+), 37 deletions(-)

Fusion-Task-Id: FN-8565

Fusion-Task-Lineage: 7d9bbeb9-0e6a-4ad9-8b4d-e9f3d8aef650

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-24 19:20:16 -07:00