Keep the App test suite aligned with the mobile scroll lock hook exports.\n\n- Add a vi.fn() mock for useMobileKeyboardViewportLock in the existing useMobileScrollLock module mock.\n- Preserve the iOS and lock-state mocks used by App test coverage.\n\nFiles changed:\n packages/dashboard/app/components/__tests__/App.test.tsx | 1 +\n 1 file changed, 1 insertion(+)
Fusion-Task-Id: FN-6323
Fusion-Task-Lineage: cc257afe-2ab7-4e97-b04f-2ccae28eaf38
Quarantine unrelated engine test flakes so the gate avoids known unstable fixtures.
- Add the soft-delete blocker residue reliability test to the quarantine ledger and reliability interaction exclusions.
- Add the bubblewrap sandbox backend test to the quarantine ledger and engine-core exclusions.
Files changed:
packages/engine/vitest.config.ts | 6 +++++-
scripts/lib/test-quarantine.json | 10 ++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-6319
Fusion-Task-Lineage: 9a47ae20-76b8-47d8-8858-f4fe51ea807e
Task Chat can now steer active assigned sessions while tasks are being reviewed or merged.
- Permit composer steering for in-review reviewing, merging, merging-fix, and fixing statuses when an agent is assigned or checked out.
- Keep paused, user-paused, unassigned, todo, and done tasks disabled with clearer active-session messaging.
- Cover in-review steering and disabled-session states in TaskChatTab tests and update dashboard documentation.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskChatTab.tsx | 13 +++--
.../app/components/__tests__/TaskChatTab.test.tsx | 61 ++++++++++++++++++++--
3 files changed, 67 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-6314
Fusion-Task-Lineage: 42c0f4f7-58f5-4e0c-837f-9f4b7c497fa3
Stabilize the process supervisor cascade teardown assertion without relying on the lifetime timer.
- Disable maxLifetimeMs for the explicit cascade teardown test so it cannot race the test's wait window.
- Assert the supervised child exits from SIGTERM and the supervisor registry is cleaned up.
- Keep the grandchild termination assertion explicit after cascade cleanup.
Files changed:
packages/core/src/__tests__/process-supervisor.test.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-6293
Fusion-Task-Lineage: 56fbedee-57e3-4b33-b84e-03fc1a7b7212
Title summarization now accepts oversized task descriptions while bounding the model prompt.
- Remove the 2000-character validation ceiling for summarize-title requests.
- Truncate long descriptions only at model-input time with a dedicated exported cap.
- Update route/API docs and tests for long-description behavior.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/fn-6305-title-summarize-any-length.md | 5 +++
packages/core/src/__tests__/ai-summarize.test.ts | 43 ++++++++++++++++++----
packages/core/src/ai-summarize.ts | 34 +++++++++++------
packages/core/src/index.ts | 1 +
packages/dashboard/app/api/legacy.ts | 2 +-
.../src/__tests__/routes-planning.test.ts | 25 +++++++++++++
packages/dashboard/src/routes.ts | 2 +-
7 files changed, 92 insertions(+), 20 deletions(-)
Fusion-Task-Id: FN-6305
Fusion-Task-Lineage: 69a83173-ac85-489f-b4cc-d1a70b6d25eb
Direct-chat composer no longer scrolls on focus (which aborted the iOS keyboard
raise on every tap after a dismiss). Layout-viewport drift is now undone on blur
— immediately plus a short follow-up cancelled on the next focus — so each focus
starts at scrollY 0 and the keyboard lock's scrollTo is a no-op.
Also hide the mobile bottom nav while the keyboard is up: it previously pinned to
bottom:0 and relied on the keyboard to cover it, but on iOS the layout viewport
does not shrink, so the bar overlapped the composer. It now slides off-screen
(translateY(100%) + pointer-events:none); safe because the nav is a sibling of
the input, not an ancestor.
Folds into the existing iOS chat-keyboard changeset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The send/stop buttons run their action on pointerdown/touchstart and set a
shared handledMobileActionRef latch so the trailing synthetic onClick does not
double-fire. The latch was only cleared inside onClick, but iOS suppresses that
click after preventDefault() in touchstart — leaving it stuck true, so the next
real click (e.g. after switching chats) was swallowed and the button looked
dead. Make the latch self-clearing (auto-reset timer + consume-on-click) so it
can never persist across taps; also stops a stuck stop-button latch from killing
the next send tap.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes workflow edge details behave like a full-screen mobile editor stage while keeping the mobile tabs visible.
- Add a mobile edge-detail editor body state that hides the canvas and expands the inspector.\n- Add a dismiss control to the mobile edge inspector.\n- Preserve the top mobile tab row sizing and cover the edge-detail/mobile-tab CSS contracts with tests.
Files changed:\n .../app/components/WorkflowNodeEditor.css | 19 +++++++++++++\n .../app/components/WorkflowNodeEditor.tsx | 22 ++++++++++++---\n .../__tests__/WorkflowNodeEditor.css.test.ts | 11 ++++++++\n .../__tests__/WorkflowNodeEditor.test.tsx | 31 ++++++++++++++++++++++\n 4 files changed, 80 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-6297
Fusion-Task-Lineage: be7cea97-0790-4d88-9fa5-3d484b4d3dfb
Move frontend UX criteria out of triage self-instructions and into deterministic workflow policy.
- Add a shared frontend UX policy helper that matches frontend file scopes and injects the byte-equivalent checklist idempotently.
- Apply the policy during triage finalization and persist the updated PROMPT.md when applicable.
- Replace the legacy prompt-based injection instructions with a pointer to the deterministic policy.
- Cover path matching, insertion behavior, and checklist parity with focused core tests.
- Add a minor changeset for the published Fusion package.
Files changed:
.changeset/fn-6237-frontend-ux-policy.md | 5 +
.../core/src/__tests__/frontend-ux-policy.test.ts | 119 +++++++++++++++++
packages/core/src/agent-prompts.ts | 29 +---
packages/core/src/frontend-ux-policy.ts | 148 +++++++++++++++++++++
packages/core/src/index.ts | 1 +
packages/engine/src/triage.ts | 19 ++-
6 files changed, 291 insertions(+), 30 deletions(-)
Fusion-Task-Id: FN-6237
Fusion-Task-Lineage: 7ac95064-9ec6-4760-8609-20c35075aca1
Replace the position:fixed body pin for the inline chat composer with an
overflow-only viewport lock so iOS no longer blurs the focused textarea
when the keyboard opens. Modals keep the position:fixed lock.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mobile chat keyboard collapsed the instant it opened on iOS because
the keyboard scroll-lock pinned body to position:fixed a beat after the
composer was already focused — pinning an ancestor to position:fixed
after focus blurs the input on iOS Safari (no visible jump, since the
dashboard base layout is already at scrollY 0).
Add useMobileKeyboardViewportLock: an overflow-only viewport lock
(overflow:hidden on html/body + scrollTo(0,0)) that does NOT touch
position, mirroring the working Quick Chat panel. App-level and ChatView
keyboard pins use it now; modals keep the position:fixed lock unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep the iOS soft keyboard up in the main chat composer (drive the
.chat-thread drift transform in JS only on real viewport offset, so a
transformed ancestor never blurs the focused textarea) and make the
Quick Chat FAB open on iPhone (toggle on pointerup since setPointerCapture
suppresses the synthetic click on WebKit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two iOS Safari-specific fixes for mobile chat:
- ChatView: the main chat keyboard collapsed the instant it opened
because .chat-thread--keyboard-active declared transform/will-change in
CSS, keeping a non-none transform on an ancestor of the focused
composer textarea. iOS blurs a focused input when an ancestor
establishes a transform containing block. Drive the drift transform in
JS only when iOS actually shifts the viewport (offsetTop > 0); the
ancestor stays transform:none on focus so the keyboard stays up.
- QuickChatFAB: the FAB never opened on iPhone because the drag hook
calls setPointerCapture() in pointerdown, which makes WebKit swallow
the synthetic click. Fire the open/close toggle from the drag hook's
pointerup on a tap (a real gesture, so stealth-input focus still raises
the keyboard); keep onClick for mouse/tests with a timer-cleared dedupe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mobile chat keyboard would not reliably stay up on iOS. The cause was
the visibilitychange/pageshow 'resync' effect in ChatView: it only ever
ran while the composer was already focused (its activeElement guard), and
on iOS a programmatic focus() fired from setTimeout has no user-gesture
context, so it could not re-raise the keyboard after the blur(). Every
spurious iOS visibilitychange (Control Center, notification banners, app
switches) therefore dismissed the keyboard mid-session and never restored
it.
Remove the blur()+focus() resync. The visualViewport half-state it
targeted is already handled by useMobileKeyboard, which re-snapshots vv
metrics on visibilitychange/pageshow via its settle tail + rAF stability
poll without ever touching textarea focus.
The mobile resync effect did an unconditional textarea blur()+focus() on
every visibilitychange/pageshow across all platforms. On Android,
focus() after blur() cannot re-raise the soft keyboard, so spurious
visibilitychange events (including those fired mid-keyboard-transition)
collapsed the keyboard. Gate the resync to iOS only (its documented
purpose) and only run it when the document is actually becoming visible.
Align the mobile auto-merge integration test viewport mock with screen dimensions.\n\n- Add explicit mobile width and height media-query handling to the viewport mock.\n- Mock window.screen dimensions alongside innerWidth and innerHeight.\n- Restore the original screen object after each test.\n\nFiles changed:\n ...-merge-toggle-blank.mobile-integration.test.tsx | 23 ++++++++++++++++++++++\n 1 file changed, 23 insertions(+)
Fusion-Task-Id: FN-6288
Fusion-Task-Lineage: 3c929d54-b482-41f3-a03e-0a22df0aa9e9
Document how custom providers are configured and surfaced in model pickers.
- Add Custom Providers dashboard guide coverage for adding, editing, deleting, detecting models, and masked API key behavior.
- Explain that saved custom provider models appear in Project Models and workflow model dropdowns.
- Add a stable settings-reference anchor for the customProviders setting.
Files changed:
docs/dashboard-guide.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++
docs/settings-reference.md | 2 +-
2 files changed, 65 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-6286
Fusion-Task-Lineage: 79520376-d448-4048-af43-a532fba68853
Stabilize the self-healing dirty worktree recovery test by avoiding timer and async cleanup races.
- Force real timers for branch conflict reclaim tests.
- Provide explicit integration branch settings and dispatcher behavior for the recovery path.
- Use synchronous fixture setup and cleanup around the recovery patch assertion.
Files changed:
packages/engine/src/__tests__/self-healing.test.ts | 77 +++++++++++++---------
1 file changed, 46 insertions(+), 31 deletions(-)
Fusion-Task-Id: FN-6280
Fusion-Task-Lineage: ef93f14a-1d16-4cca-ac6d-3cfd7249ca21
Use per-invocation Vitest worker roots to keep compound-engineering tests from timing out on stale temp fixtures.
- Allocate a fresh FUSION_TEST_WORKER_ROOT during Vitest global setup and remove it during teardown.
- Preserve per-worker fallback root creation when global setup is not available.
- Clean compound-engineering harness project roots on close and cover the setup invariants with regression tests.
Files changed:
packages/core/src/__test-utils__/vitest-setup.ts | 20 +++++++---
.../core/src/__test-utils__/vitest-teardown.ts | 38 +++++++++++--------
.../src/__tests__/_harness.ts | 7 +++-
.../src/__tests__/setup-invariant.test.ts | 43 ++++++++++++++++++++++
4 files changed, 85 insertions(+), 23 deletions(-)
Fusion-Task-Id: FN-6282
Fusion-Task-Lineage: 8b842dd0-4f51-44de-b2db-8e8bfa97239c
Ensure assigned agents resume task work even when their heartbeat loop is idle.
- add deferred assignment refiring when an agent is assigned work without an active heartbeat run
- track in-process runtime activity so scheduled work is not double-started
- cover idle, active, and concurrent heartbeat assignment paths with scheduler and runtime tests
- document the deferred assignment wake-up behavior and add a changeset
Files changed:
.changeset/fn-6284-deferred-assignment-refire.md | 5 +
docs/agents.md | 2 +
docs/architecture.md | 1 +
.../src/__tests__/heartbeat-scheduler.test.ts | 174 ++++++++++++++++++++-
packages/engine/src/agent-heartbeat.ts | 109 ++++++++++++-
.../runtimes/__tests__/in-process-runtime.test.ts | 24 +++
packages/engine/src/runtimes/in-process-runtime.ts | 3 +
7 files changed, 311 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-6284
Fusion-Task-Lineage: cf8a9abe-163e-45c6-961a-095891e45ea5
The custom provider edit form seeded the API key input with the masked
value (e.g. "abc•••••wxyz") returned by the sanitized GET. Saving or
running "Detect Models" without retyping echoed that mask back — the
probe endpoint rejects it and a persisted mask broke HTTP header
encoding. The field now starts blank with a "leave blank to keep" hint;
an empty field omits apiKey so the stored credential is preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The settings UI displays saved API keys masked with '•' (U+2022). Saving
a provider without retyping the key echoed that mask back, and the PUT
handler persisted it as the real credential. The masked key then flowed
into an Authorization/x-api-key header, throwing "Cannot convert argument
to a ByteString ... value 8226" at request time.
Treat masked values echoed back on update as "unchanged" so the stored
key is preserved, and reject masked values on create/probe. Add tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /models endpoint restricted results to providers configured in
Fusion's auth.json/models.json stores, but custom providers live in
global settings and register under customProviderRegistryKey(). Their
keys were never added to the allowlist, so their models were filtered
out before reaching the dropdowns. Add the custom provider registry
keys to the configured-providers set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deduplicate self-healing log entries for queued tasks that remain blocked by the same active file-scope overlap.
- Track the last active overlap blocker logged per task and skip duplicate preserved-queued log entries across self-healing passes.
- Clear the memo when blockers change, resolve, or the manager stops so legitimate future recoveries are still logged.
- Cover unchanged blockers, blocker changes, resolved blockers, stop/reset behavior, and FN-5488 stale-blockedBy preservation with regression tests.
Files changed:
packages/engine/src/__tests__/self-healing.test.ts | 123 +++++++++++++++++++++
packages/engine/src/self-healing.ts | 61 ++++++++--
2 files changed, 177 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-6276
Fusion-Task-Lineage: 169d4402-ec4e-4fa9-8225-2f545c11a3f7
Keep task detail PR and review affordances synchronized with the current project auto-merge setting.
- Thread the live auto-merge value into task detail modals and list split-pane detail views.
- Prefer the live auto-merge setting over the stale fetched modal snapshot while preserving task-level overrides.
- Cover PR and review tab behavior for auto-merge on/off and document the dashboard behavior.
- Evict flaky engine gate entries and add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-6247-automerge-off-modal-stale.md | 5 +
docs/dashboard-guide.md | 1 +
packages/dashboard/app/App.tsx | 3 +-
packages/dashboard/app/components/AppModals.tsx | 2 +
packages/dashboard/app/components/ListView.tsx | 3 +
.../dashboard/app/components/TaskDetailModal.tsx | 4 +-
.../__tests__/TaskDetailModal.create-pr.test.tsx | 171 ++++++++++++++++++++-
packages/engine/vitest.config.ts | 2 -
8 files changed, 182 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-6247
Fusion-Task-Lineage: 1321c03a-216d-4b15-bf4f-95621d68c9ae