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.
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>
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>
- 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
- 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"
- 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)
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
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>
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>
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>
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>
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>
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>
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>
FN-8557 excluded any touch device from mobile mode when its reported physical
screen exceeded the 480px phone threshold, at any CSS width. Large Android
phones report exactly that, so they resolved to tablet: MobileNavBar returns
null off mode "mobile", while MobileNavBar.css still displayed at
(max-width: 768px) — JS and CSS disagreeing about the same device. The board
also fell back to the horizontally-scrollable desktop layout.
No tablet uses a <=600px viewport as its primary layout, so width now
overrides the physical-screen heuristic below that floor; the carve-out
FN-8557 wanted (portrait tablets at the 768 boundary) keeps the 601-768 band.
The floor checks all three width signals the classifier already trusts —
media query, innerWidth, visualViewport — since each can lead on a different
device or update late on rotation. Regression tests cover all three; each
fails against the pre-fix classifier.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A paused engine with nothing running derived executorState "idle", so the
footer badge was indistinguishable from a healthy engine waiting for work.
That is exactly the state a pause settles into once in-flight tasks drain:
triage and planning stall while the board keeps moving, with the pause only
visible by opening the Engine Control menu. Pause state now dominates run
state; the adjacent counters still report throughput.
In the CLI TUI, the global `t` (Git view) branch returned before the Utilities
dispatch in the same key handler, so the advertised "[t] Toggle Engine Pause"
was unreachable dead UI. It now yields when Utilities owns input, and because
the shortcut can stop the board, pausing takes a second `t` within 5s while
resuming stays single-press.
Tests assert the invariant across the whole state matrix (both pause flags x
0/1/5 running), not just the zero-running repro, plus the TUI routing, the
two-press pause, single-press resume, and re-arm behavior.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two auto-save tests waited out the real 500ms debounce via waitFor (up to two
cycles each), unlike the sibling coalesces test which already uses fake timers.
Switch both to vi.advanceTimersByTimeAsync(500) to remove ~1.7s of wall-clock
dead time from the dashboard's slowest feedback-loop suite with identical
coverage (Standing Rule: prefer fake timers over real time waits).
Full Suite shard 4 timed out runTaskShow lock-exhaustion at the default 5s
budget (run 30096660913): each case re-imported the heavy task.js graph under
fake timers, so cold CI workers spent the whole budget on transform and left
unhandled store.getTask / process.exit races after the timeout.
Import task.js once per cached-store describe under real timers via a mutable
store holder, and enable fake timers only around the backoff body. Exhaustion
cases now finish in ~1ms locally while keeping the shipped entry path.
- general: FN-8453 removed the duplicate "Max Triage Concurrent" control
(assert it stays gone); #2400 replaced the per-phase moved-to-workflow note
with the editable "Project workflow model lanes" section.
- scheduling-merge: await the updateSettings call that carries
worktreeCopyFiles instead of assuming calls[0] (CI lane-load ordering).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On GitHub runners headless system Chrome dies at launch without --no-sandbox
and --disable-dev-shm-usage, cascading all five tests as "browser has been
closed" (run 30081843074). The lane had been masked by the shard-4 watchdog
kill since the file landed, so it had never actually executed on CI. Flags
apply only under CI; local launches keep the default sandbox.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nine more api-lane files get the established getProjectScopedPluginMcpServers
stub (includes shared-branch-group-entry-points, whose two current failures
were FN-8491 500s — the historical per-task-derived pair is gone). App lane:
FN-8557 made window.innerWidth<=768 a mobile signal, so leaked innerWidth=375
defineProperty stamps flipped later ListView/settings tests into mobile
layout (reset in beforeEach), and useModalResizePersist's 700px "mobile"
fixture became tablet-class (now a phone-class 375px viewport).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The core watchdog kill had been hiding everything scheduled after it in
shard 4; with the budget fixed, 15 api-lane files surfaced. Thirteen needed
the established FN-8491 recipe (mock stores expose
getProjectScopedPluginMcpServers so the binder short-circuits). Also:
mcp-lane-forwarding tracks resolveManualAiPromptMcpServers' move to
automation-step-execution and FN-8538's getSettings on planning stores;
planning-answered-question-reemit handles PR #2417's synchronous single-turn
admission with a bounded onceAdmitted retry (admission rejections are
side-effect free); routes-system tracks getProjectPluginLoader resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full-suite run 30077108784 hit an additive insert under CI lane load (group
spliced while merge→end survived) because the toolbar pick ran before the
canvas settled its edge-target state; 5/5 green locally. Same
settle-before-interact class as 5a5796bca; recurrence goes to quarantine.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Shard watchdog floor 25min (was 15): the July PG-cutover test growth pushed
@fusion/core past 900s on contended CI runners; run 30075604930 killed a
healthy core run at exactly the floor because the 27-day-old (still "fresh")
undercounting timings snapshot tightened the budget to it — the same
false-kill class as the 5->15min raise. Floor pin + in-band example updated.
- full-suite.yml timing upload: include-hidden-files — the .timings/ dot-dirs
were silently excluded by upload-artifact@v4, so the step has uploaded
nothing since it was added and the snapshot could never be refreshed from CI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b2a7425c7 replaced the six-legacy-id whitelist with normalizeColumnId: custom
workflow column ids pass through untouched, only structurally unusable values
(non-string/empty) fall back to triage. The two normalization tests now assert
both halves of that invariant instead of the deleted whitelist behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same drift as chat-commands/mobile: sendMessage gained the {onDelivered,onFailed}
third argument; autosize and draft suites now expect it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- routes-auth/automation/git/github/tasks: FN-8491 binds the plugin-MCP
provider on every project-context resolution; mock stores now expose
getProjectScopedPluginMcpServers so routes stop 500ing.
- routes-github: stub FN-8442 durable planning-claim plumbing whose internal
getSession bypassed the namespace spy; the scoped-store routing contract
is asserted unchanged.
- api-git: POST /planning/create-task now returns the FN-8442
{task, alreadyCreated} envelope; mock matches.
- TaskDetailModal.tab-persistence: settle-then-requery before clicking the
Session tab (detached-node race under CI load).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On some systems the terminal opened blank — buffered prompt present but
never painted (renderer stalled at init: WebGL activation on a zero-sized
canvas or context-loss fallback). Every automatic recovery path fit() but
never refreshed, and fit() with unchanged cols/rows triggers no internal
xterm repaint, so only user input, a font-size change, or a new tab
repaired it. Observer/geometry-driven fits in TerminalModal and
SessionTerminal now always follow fit() with an explicit full-viewport
terminal.refresh(0, rows-1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>