Adds a mobile direct-chat shortcut for starting a new chat from the session switcher.
- Add a New Chat menuitem to the mobile direct-chat session dropdown using the existing NewChatDialog flow.
- Style the new mobile switcher action with touch-friendly spacing and focus handling.
- Extend mobile ChatView tests and docs, and add a changeset for the published CLI package.
Files changed:
.changeset/fn-7120-mobile-new-chat.md | 7 ++
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/ChatView.css | 31 ++++++
packages/dashboard/app/components/ChatView.tsx | 17 +++
.../components/__tests__/ChatView.mobile.test.tsx | 115 +++++++++++++++++++--
5 files changed, 162 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-7120
Fusion-Task-Lineage: 5d066667-20c1-4015-9b84-a9e558eecd48
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- Skills, Mailbox, Agents migrated to the shared ViewHeader (existing controls passed through actions; Skills icon -> Zap to match sidebar).
- Missions keeps its integrated header but adopts the shared 1.125rem title + size-20 icon.
- ChatView intentionally left as-is (height-sensitive two-pane; documented with FNXC).
- Tests updated for the new header markup (606 pass across the view suites).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align the direct and room chat mobile send controls around the same pointer/touch dedupe contract.
- Add a per-gesture touch action latch separate from the trailing synthetic-click latch.
- Apply the two-latch handling to direct send, room send, and send-to-stop transitions.
- Cover repeated iOS taps, pointer/touch/click dedupe, and stop-button swap behavior in ChatView tests.
- Document the shared mobile send dedupe behavior for direct and room chat.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/ChatView.tsx | 70 ++++++++---
.../components/__tests__/ChatView.rooms.test.tsx | 30 +++++
.../app/components/__tests__/ChatView.test.tsx | 132 +++++++++++++++++++++
4 files changed, 215 insertions(+), 19 deletions(-)
Fusion-Task-Id: FN-6576
Fusion-Task-Lineage: b8ef427f-99f0-48d8-8cb8-d72ac352bdeb
Keep tablet chat navigation available while software keyboards reduce the viewport.
- Stop auto-hiding the tablet chat sidebar when the keyboard opens.
- Bound the sidebar to its minimum width during tablet keyboard-open layout without overwriting the saved width.
- Cover visible and user-collapsed tablet sidebar behavior in mobile render tests.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/fn-6494-tablet-chat-sidebar.md | 5 ++
packages/dashboard/app/components/ChatView.tsx | 34 +++--------
.../__tests__/ChatView.mobile-render.test.tsx | 70 ++++++++++++++++++++--
3 files changed, 79 insertions(+), 30 deletions(-)
Fusion-Task-Id: FN-6494
Fusion-Task-Lineage: 618cccc3-5102-4e9c-ac0e-6c7a44742940
Two mobile send failures with a shared symptom of "nothing happens":
- Regular chat: the send button was dead to touch. The action lived only
in onClick, but iOS suppresses the trailing synthetic click after
preventDefault() in the touch sequence, so taps never sent. Fire the send
from pointerdown/touchstart with a self-clearing dedupe latch (mirroring the
QuickChat send button), keeping a single send per tap.
- Quick chat: a queued message could strand in the composer — shown locally
but never reaching the agent or the persisted session (so it also never
appeared in regular chat). A stream that dropped without onDone/onError
(e.g. mobile tab suspension) left the streaming flag stuck true, so every
later send took the "queue while streaming" branch and was never flushed.
On a queued send, detect the stale flag via the stream's connection state
and the server's generation status, then tear down the dead stream and flush.
Both paths covered by new tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 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>
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.
Limit the chat composer growth on tablet viewports while preserving desktop autosizing.
- Add a tablet-specific 200px max height for the chat input textarea.
- Apply the same cap in ChatView autosize calculations when tablet mode is active.
- Generalize chat input height helpers and cover the tablet cap in autosize tests.
Files changed:
packages/dashboard/app/components/ChatView.css | 9 +++++++++
packages/dashboard/app/components/ChatView.tsx | 20 +++++++++++++-------
packages/dashboard/app/components/QuickChatFAB.tsx | 4 ++--
.../__tests__/ChatView.chat-input-autosize.test.tsx | 14 ++++++++++++++
4 files changed, 38 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-6212
Fusion-Task-Lineage: b11cd658-8289-4795-8e39-7d03a1d51d41
Keep the chat sidebar from crowding tablet chat while the software keyboard is open.
- extend mobile keyboard detection so tablet chat can react to visual viewport keyboard changes
- hide the chat sidebar and resize handle while the tablet keyboard is open, then restore prior visibility when it closes
- add regression coverage for tablet keyboard behavior plus desktop and mobile guardrails
Files changed:
packages/dashboard/app/components/ChatView.tsx | 39 +++++-
packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx | 139 ++++++++++++++++++++-
packages/dashboard/app/hooks/useMobileKeyboard.ts | 7 +-
3 files changed, 171 insertions(+), 14 deletions(-)
Fusion-Task-Id: FN-6178
Fusion-Task-Lineage: 7acdb76a-135f-4b9c-ab50-708eec03b2c6
Restore pinned mobile chat scroll position when visibility-driven refetch hooks fire.
- re-anchor mobile chat to the bottom during visibility/pageshow refetch capture when the user was pinned
- tighten ChatView visibility restore regression tests to assert the synchronous re-anchor path directly
- remove the ChatView rooms test quarantine and restore the dashboard include list entry
Files changed:
packages/dashboard/app/components/ChatView.tsx | 6 +++++-
.../app/components/__tests__/ChatView.rooms.test.tsx | 17 ++++++++---------
.../app/components/__tests__/ChatView.test.tsx | 7 ++++---
packages/dashboard/vitest.config.ts | 2 +-
scripts/lib/test-quarantine.json | 8 +-------
5 files changed, 19 insertions(+), 21 deletions(-)
Fusion-Task-Id: FN-6142
Fusion-Task-Lineage: 2974cf0d-5512-4b24-ab0c-2d2fb63fd41a
Mount CliChatSurface in ChatView for cli-backed chat sessions (sessions carrying
cliExecutorAdapterId): the message-pane + composer region is delegated to the
surface (transcript/raw-terminal toggle for hybrid/native adapters, terminal-only
for the generic adapter), while regular sessions keep the standard composer. The
existing message list and composer JSX are captured once as render thunks and
passed through, so there is no parallel message/composer UI.
Add a narrow telemetry seam: TelemetryHub gains an optional onEvent tap (also
settable post-construction via setEventListener) invoked with each sanitized
event after routing — best-effort, a throwing listener never breaks ingest. This
is the seam the CliChatSessionRunner uses to build the durable transcript from
the same sanitized events the hook route already feeds the hub, without the hub
becoming a general subscriber bus.
Fix the stale @fusion/engine vi.mocks across dashboard tests: object-literal
mocks that fully replace the module now also return listCliAdapterDescriptors
(added by U15's cli-agent-settings route, evaluated at module load). Mocks that
spread importOriginal/importActual already pick it up.
Tests: new ChatView.cli-mount.test.tsx (cli session → CliChatSurface, regular
session → normal composer, generic → terminal-only); telemetry-hub onEvent tap
coverage. chat-attachment-routes, chat-cli-sessions, cli-agent-hooks-route,
ChatView.cli-toggle all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
carry ~5,930 keys each across common/app/errors/cli namespaces;
CLI bundles regenerated (6 locales incl. ko)
Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
literal {{placeholders}}); dashboard vitest.setup boots a minimal en
i18next instance for the same reason
Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the recurring ChatView skill-menu CI flakes (and a real UX
bug): the highlight-reset effect keyed on filteredSkills array identity,
but useDiscoveredSkillsCache (SWR) re-delivers content-identical lists
with fresh identities — cache reads re-parse JSON and revalidation
notifies a new array. A revalidation landing between a user's (or the
test's) arrow-key press and the next frame wiped the highlight back to 0.
Key the reset on the joined skill-id list instead, so only a semantic
list change resets the keyboard position. Regression test proves the
invariant: deferred revalidation with identical content lands mid-
navigation and the highlight persists (fails on the old identity-keyed
reset in all three vitest projects).
This test family needed three prior stabilization passes (FN-5864,
FN-5745, FN-5725) — this addresses the underlying race rather than the
assertions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix NewChatDialog to resolve the model from the dropdown default when the user has not
explicitly picked a model, allowing chat creation with the default model.
- Introduce `resolvedModel` that falls back to `defaultModelValue` when `selectedModel` is empty
- Use `resolvedModel` in submit handler and submit-disabled guard so the default model is accepted
- Add test for creating a session with the default model ("Use default" selected)
- Add test for creating a session with an explicitly chosen non-default model
- Update existing no-default test to verify the submit button stays disabled and no session is created
Files changed:
packages/dashboard/app/components/ChatView.tsx | 12 ++--
packages/dashboard/app/components/__tests__/ChatView.test.tsx | 69 ++++++++++++++++++++--
2 files changed, 72 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-5922
Fusion-Task-Lineage: 30b2aecb-af73-4f6d-b697-cc0b5630d5d8
Preselect the resolved default model when creating a new model-based chat.
- pass the resolved default model into the new chat dialog
- preserve and restore the default model selection when switching into model mode
- add ChatView coverage for default-model preselection, create enablement, and unresolved-default fallback
Files changed:
packages/dashboard/app/components/ChatView.tsx | 18 +++++-
packages/dashboard/app/components/__tests__/ChatView.test.tsx | 65 ++++++++++++++++++++--
2 files changed, 75 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-5854
Fusion-Task-Lineage: d1ace008-0354-44b4-90c8-e0370f38b6bf
Keep chat scroll position stable when stale top snapshots are captured during mobile message updates.
- Capture scroll anchors from the first visible message instead of always sampling the first DOM node
- Ignore stale or likely-invalid zero-position snapshots before attempting scroll restoration
- Guard bottom anchoring from interrupting user scroll unless explicitly forced and add regression coverage for stale snapshot behavior
Files changed:
packages/dashboard/app/components/ChatView.css | 11 +++++-
packages/dashboard/app/components/ChatView.tsx | 33 +++++++++++++----
packages/dashboard/app/components/__tests__/ChatView.scroll-to-top.test.tsx | 41 ++++++++++++++++++++++
3 files changed, 78 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-5757
Fusion-Task-Lineage: 4e4f6843-ba70-40da-8d16-358bdfcc7b09
Ensure Rooms scope reliably re-anchors to bottom on re-entry without breaking scroll preservation during refetch.
- reset stale thread anchor state when entering Rooms without an active room
- treat direct↔rooms transitions as entry events and anchor messages to bottom on entry
- keep existing scroll position when room messages are refetched in-place
- add regression tests covering room re-entry anchoring and refetch scroll preservation
Files changed:
packages/dashboard/app/components/ChatView.tsx | 14 +++-
.../app/components/__tests__/ChatView.test.tsx | 88 ++++++++++++++++++++++
2 files changed, 99 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-5720
Fusion-Task-Lineage: dde8818b-f2e5-4c55-8a4c-2c54241c2e38
Prevent the chat composer from showing a right-edge scrollbar line before it reaches the autosize cap.
- set chat textarea default overflow to hidden in ChatView CSS
- add resolveChatInputOverflowY to switch overflow to auto only past the 640px cap
- apply overflow mode during composer autosize updates
- extend chat input autosize tests and docs to cover overflow behavior
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/ChatView.css | 1 +
packages/dashboard/app/components/ChatView.tsx | 5 +++++
packages/dashboard/app/components/__tests__/ChatView.chat-input-autosize.test.tsx | 9 ++++++++-
4 files changed, 15 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5706
Fusion-Task-Lineage: 4619aa30-c02f-453b-bdf2-f60f360e9372
When the user clicks 'Latest' while loadMoreMessages was in-flight,
the scroll restoration (which maintains position when older messages
are prepended) was undoing the explicit jump-to-bottom.
Fix: clear scrollRestoreSnapshotRef and set isUserScrollingRef=false
before anchorToBottom so the layout effect's restoration is skipped.
- Remove fetchAllMessagesInChat/fetchAllMessages helpers
- Keep limit:50 for initial load in useChat and useQuickChat
- Add IntersectionObserver sentinel at top of ChatView message list to
trigger loadMoreMessages() when user scrolls to the top
- Keep stale-session guards (activeSessionRef checks) from original PR
- Tests: revert assertions back to { limit: 50 }
ChatView and QuickChatFAB both had an iOS-specific onTouchStart on the
textarea that called event.preventDefault() and then programmatically
re-focused the input — meant to suppress iOS's visualViewport auto-scroll
on re-focus. On Android, preventDefault on a textarea touchstart blocks
the soft keyboard from opening (programmatic focus() alone does not raise
the Android keyboard — only the default touch action does), so tapping
the main chat or quick chat composer focused the input but the keyboard
never appeared, looking like an instant dismiss.
Gate the touchstart workaround to iOS via isIOS().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added a scroll-to-top action for the ChatView with per-message scroll behavior, styled to match the mobile design, with comprehensive test coverage for scroll-to-top behavior. Also preserves the mobile copy-action CSS selector.
Fusion-Task-Id: FN-5379
FN-5389 adds dashboard resume event instrumentation: a `resumeInstrumentation` utility captures SSE resume signals, wired through `useChat`, `useChatRooms`, and `useTasks` hooks, with remount markers in `Board` and `ChatView`; diagnostics routes expose resume events for observability, documented in
Fusion-Task-Id: FN-5389
Preserve chat scroll state across view transitions in ChatView, with comprehensive test coverage. The feature adds scroll position persistence logic gated behind a debug trace flag, and a full test suite covering the scroll state behavior.
Fusion-Task-Id: FN-5380
Implements selective room composer restoration — the dashboard now classifies room send failures and gates composer restoration by error type, preventing spurious recovery on transient delivery errors while preserving composer state only for actionable failures. Covers the new classification logic i
Fusion-Task-Id: FN-5360