Adds a new `useShellContext` hook with tests to the dashboard, integrated into `App.tsx` and `Header.tsx` to provide shell context plumbing throughout the UI.
Fusion-Task-Id: FN-3406
Merges node discovery onboarding (FN-3506) with two steps: a pre-registration discovery contract wired into the AddNodeModal UI, and full remote discovery integration with documentation. Also adds a local embedded runtime manager to the desktop app (FN-3404) and wires permanent-agent approval contex
Fusion-Task-Id: FN-3506
- Add onboarding project-mapping API contracts and dashboard UI wiring in AddNodeModal/NodesView
- Persist node-to-project mappings with rollback-safe failure handling and expanded hook/API test coverage
- Document node onboarding mapping behavior in architecture and multi-project docs
- Add tsup bundle asset gates for openclaw bridge and droid runtime, with changesets and bundle output tests
Fusion-Task-Id: FN-3505
Merges shell multi-profile support for desktop (FN-3403) — spanning new connection-manager flows, shell onboarding interoperability, mobile connection profiles, desktop IPC/shell-settings plumbing, and tokenized titlebar styles — with a secondary migration of the WhatsApp plugin to the Baileys pairi
Fusion-Task-Id: FN-3403
Adds a full evals dashboard view (FN-3394) with a client hook, server-side read routes, and navigation integration across the Header and mobile nav, backed by comprehensive tests. Wires Hermes chat message tools with mailbox behavior documentation (FN-3620), and extends the agent heartbeat and tools
Fusion-Task-Id: FN-3394
Adds documentation for graph navigation canonicalization (FN-3080) covering plugin authoring and dashboard usage, along with a changeset for the `@runfusion/fusion` package release.
Fusion-Task-Id: FN-3080
Codex returns weekly window reset_at in milliseconds in some cases,
which was being multiplied by 1000 and producing nonsensical reset
countdowns and pace calculations. Route through _parseResetTimestamp
so both formats work, and add a regression test.
Also tightens comments in useMobileKeyboard / ChatView around why
visualViewport scroll events skip offsetTop updates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When ChatView remounted (e.g. tab switch with keyboard still up), the
hook started with keyboardOpen=false and corrected itself only after
the effect ran. That single stale-state render briefly unhid the
executor status bar, which appeared as a blank pane covering half the
input box before the next state update settled it.
useState initializers now call getKeyboardMetrics() lazily on first
render so the very first paint already reflects the live keyboard
state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tab/page switches with the keyboard up sometimes still left the
chat-thread anchored wrong because the timed re-reads (50/200/500ms)
missed the actual settlement frame on slower devices.
updateWithTail now also kicks off a rAF poll that re-reads metrics
each frame until offsetTop is stable for 2 consecutive frames or 1.5s
elapses. Combined with the extended timed tail (now adds 1000/1500ms),
this should catch both fast and slow iOS settlement paths without
indefinite polling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes Step 3 of FN-3618 by surfacing AI provider icons throughout the Settings modal and model dropdowns, updating both the component styles and adding integration and unit tests to cover the new icon display behavior.
Fusion-Task-Id: FN-3618
Switching away from the chat view and back left the hook with stale
metrics — keyboardOpen was false at remount even though the system
keyboard was still visible, so the .chat-thread transform never
re-applied and the message list anchored too high. focusin and
synchronous reads also captured offsetTop mid-transition.
Adds a tail of delayed re-reads (50ms, 200ms, 500ms) on focusin,
visibilitychange, and pageshow so iOS has time to settle before the
final snapshot is taken.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iOS fires visualViewport scroll events at 60fps during a pan with the
keyboard up. Routing those through React state and into the .chat-thread
translateY(--vv-offset-top) transform amplified the pan into a visible
judder + ~300px shift + body background exposure.
useMobileKeyboard now uses two listeners: a full update (resize +
focusin/focusout) that re-snapshots all metrics including offsetTop, and
a scroll-only update that updates only height/keyboardOpen. offsetTop
is therefore frozen between keyboard open/close events — the transform
correctly compensates for iOS's initial visualViewport shift on focus
without following pan-time movement.
Restores the translateY anchor (so the thread isn't off-screen on
first focus) while keeping the swipe-jitter fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge splits the monolithic TaskDetailModal test file into six focused test suites (rendering, attachments/tabs, definition/actions, inline-editing/integrations, models/progress/workflow, responsive/dependencies), adds a changeset for the test isolation baseline, fixes chat SSE optimistic echo
Fusion-Task-Id: FN-3609
Two cuts to wasted work in the merge verification loop:
1. After the in-merge fix agent runs, fingerprint the working tree
(`git diff HEAD` + `git status --porcelain`, sha256). If the post-fix
fingerprint matches pre-fix and is non-empty, the agent didn't actually
change anything — re-running the same failing command can only yield
the same failure, so log and report the attempt as unsuccessful without
paying the test/build cost. Empty fingerprints (snapshot tooling failed)
fall through to the existing re-run path so we never silently swallow a
real fix.
2. Inside `syncDependenciesForMerge`, hash the active lockfile and compare
against `node_modules/.fusion-install-marker` (written after each
successful install). When they match, skip `pnpm install
--frozen-lockfile` even if `package.json` is staged. Covers the common
case where `package.json` changes but the lockfile doesn't, and
amortizes install across auto-recovery re-enqueues that hit the same
worktree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /_perf/dashboard-load server route no longer exists, so every
reportDashboardPerf() call was a silently-swallowed 404. Remove the helper
in legacy.ts plus its five call sites in App.tsx / useProjects.ts (the
companion console.log lines stay), and drop the dashboard-perf.log entry
from the test-isolation runtime ignore list since nothing writes that file
anymore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactored plugin-loader tests and implementation to isolate plugin test contamination, improving test independence in `@fusion/core`.
Fusion-Task-Id: FN-3564
Creating a new terminal tab caused every pty data chunk (including
keystroke echo) to render twice in xterm. The connect-effect's
`contextChanged` dep flips true→false in the same render cycle as
the new connection: the effect re-runs, React calls cleanup which
closed the still-CONNECTING WS without nulling its handlers, then
connect() opens a fresh WS. The ghost socket's onmessage continued
firing on the shared `onDataCallbacksRef` Set, delivering each chunk
twice (and producing the "WebSocket is closed before the connection
is established" warning). Null onopen/onmessage/onclose/onerror in
both cleanup() and connect()'s pre-close branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six new tests covering the three bugs fixed in the previous commit:
- Retry limit: stops showing toasts after 3 consecutive failures,
resets counter on success
- Skip flag: startFreshSession sets/clears skipNextSessionInitRef,
creates new session even when existing one exists
- Ref stability: switchSession reads activeSession from ref so it
correctly detects "same session" and skips re-initialization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three interacting bugs caused "Failed to initialize chat" toast spam and
prevented fresh session creation:
1. Race condition in handleCreateFreshSession — setting chatMode/
selectedAgentId before startFreshSession triggered the session-init
useEffect, which called switchSession and resumed the old session
instead of creating a new one. Fixed with skipNextSessionInitRef.
2. Infinite retry loop — shouldRetrySessionInit retried indefinitely
when initializeSession failed, producing unbounded toast spam.
Fixed with a 3-retry cap (initRetryCountRef).
3. Cascading re-renders — switchSession depended on activeSession in
its closure, getting a new identity on every state change and
re-triggering the consumer's useEffect. Fixed by reading
activeSession from a ref instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the back button left the dashboard page entirely because
useNavigationHistory was only enabled for mobile viewports. Now it's
enabled for all viewports — desktop and mobile — so the browser back
button dismisses modals and reverts view changes within the SPA.
Changes:
- Enable useNavigationHistory for desktop (enabled: true instead of enabled: isMobile)
- Remove all isMobile ? historyAwareHandler : plainHandler ternaries
- Always use navigation-aware handlers (openDetailTask, openSettingsWithNav, etc.)
- Update JSDoc to reflect desktop support
This merge introduces an eval automation domain store with persistence schema and a plugin dashboard view registry (FN-3512/FN-3513), adds scheduled eval batch architecture documentation (FN-3388), and includes substantial test coverage for Research routes and hooks (FN-3368 steps 1-3). The branch a
Fusion-Task-Id: FN-3368
Merged FN-3157 to add a plugin dashboard views system, including a plugin view registry with lazy loading, navigation integration for Header and MobileNavBar, a usePluginDashboardViews hook with cache and refetch support, and tests covering the no-loader path. Also added documentation in `docs/PLUGI
Fusion-Task-Id: FN-3157
Backend
- chat.ts now routes both regular chat and QuickChat through
createResolvedAgentSession instead of branching to createFnAgent for
the no-runtime-hint case. This removes the divergent path where
pi-ai's cleanupSessionResources(sessionId) could tear down resources
the next generation depends on.
- sendMessage's finally only disposes the agent if it still owns the
activeGenerations slot. A newer generation that has pre-empted us
cleans up its own agent in its own finally — disposing here would
yank the underlying CLI process out from under it.
- __setCreateFnAgent test helper now mirrors its mock into the
createResolvedAgentSession slot so existing test setups still work
after the unification.
Frontend
- Extract createChatStreamHandlers (RAF coalescing, accumulators,
tool-call dedup, fallback handling) — useChat and useQuickChat were
duplicating ~85 LOC each. Both now compose the shared factory.
- Move shared chat types into chatTypes.ts. The hooks re-export them
for backward compatibility with existing consumers.
- Removed per-message Markdown/plain-text eye toggles. A single
thread-level toggle in the chat header now flips every assistant
bubble (including the streaming one) between rendered Markdown and
plain text.
- Model-only chats hide the per-message agent identity row entirely;
the model name is already in the thread header.
Tests
- Updated ChatView tests to reflect the new render-toggle contract
(single header toggle drives all bubbles) and the model-only avatar
suppression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related bugs left the Planning Mode modal stuck at partial height on
mobile. useModalResizePersist replayed desktop-saved pixel dimensions
that overrode the mobile 100dvh rule, and React reconciled removed CSS
custom properties to empty string rather than calling removeProperty(),
which on iOS Safari defeated the var() fallback after keyboard dismiss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge adds done-payload snapshot handling across the chat system (FN-3430), normalizing how final assistant messages are preserved and consumed in the dashboard hooks, plus it normalizes dashboard mailbox and user identity for inter-agent messaging (FN-3484) and introduces plugin workflow step
Fusion-Task-Id: FN-3430
Adds deterministic split-pane refresh behavior to the Agents views (AgentsView and AgentDetailView), with comprehensive test coverage for the mutation refreshes in the detail pane and the agents hook.
Fusion-Task-Id: FN-3466
Adds test coverage for archived insights across the core store, `InsightsView` component, and `useInsights` hook — 156 lines of new tests covering FN-3315.
Fusion-Task-Id: FN-3315
The merge lands Step 7 of FN-3398, adding documentation and delivery artifacts across the mobile and desktop packages with updated READMEs, mobile-specific docs, and architecture references.
Fusion-Task-Id: FN-3398
This merge enables heartbeat agents to use coding sessions with tool access (FN-3459). It adds heartbeat session prompt tests, updates the heartbeat executor to support tool mode, modifies dashboard node components and hooks, and includes a fix for a typecheck regression. A changeset is included for
Fusion-Task-Id: FN-3459
Adds readable skill badges to the agent detail view with an interactive detail panel, along with corresponding tests and utility functions. A typecheck fix for the dashboard docker config is included.
Fusion-Task-Id: FN-3381
Documents the static slot-host rendering contract in `docs/PLUGIN_AUTHORING.md`, clarifying how plugins should interact with the rendering system.
Fusion-Task-Id: FN-3260
This merge delivers three focused changes: fixes the Git Manager modal mobile overflow (FN-3367) with new CSS, stabilizes the Docker config modal recreate indicator tests (FN-3114) with comprehensive new test coverage, and adds hook baseline cleanup tests (FN-3362). The changes span the core databas
Fusion-Task-Id: FN-3362
The merge stabilizes a test case in the NodeDetailModal test file related to the docker config modal's recreate indicator, correcting an assertion that was causing intermittent failures.
Fusion-Task-Id: FN-3114
This merge adds Agent Detail view improvements including model favoriting, wires in the droid bundled plugin, updates mobile split layout with proper safe-area padding, and adds research task-view persistence with associated test coverage across Header, MobileNavBar, App, and view state hooks.
Fusion-Task-Id: FN-3264
Documents research recovery semantics in the CLI reference and settings reference, adding 9 lines of documentation across both files.
Fusion-Task-Id: FN-3014
useTasks.deleteTask had a single-arg signature and dropped the options
object, so the modal's "remove dependency references" confirmation
flag never reached the API and deletion still failed with
TASK_HAS_DEPENDENTS. Forward options through the hook and widen the
matching taskOperations.deleteTask type in AppModals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes FN-3112: Updates NodesView CSS to use tokenized border-radius and transition values from the design system instead of hardcoded pixel values, ensuring consistency with the dashboard's established token variables.
Fusion-Task-Id: FN-3112
Fixes deep-link close to preserve browser history state and adds comprehensive integration tests for the navigation history feature, covering App component and useDeepLink hook behavior.
Fusion-Task-Id: FN-3344
Merged test(FN-3344): added comprehensive unit and integration tests for the navigation history hook, covering 470 lines of test coverage across navigation state management, history tracking, and edge cases.
Fusion-Task-Id: FN-3344
When App-level activation triggers the lock from an input gaining focus,
iOS may have already scrolled the document by the time the lock effect
runs. Capturing that already-shifted scrollY and restoring it on release
left the dashboard pushed up after the keyboard dismissed (the inline-
edit case). Always snap to 0 instead — the dashboard has body{overflow:
hidden} so user-initiated scroll is always 0 anyway, and any non-zero
value at lock time is iOS-forced and should be reset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useMobileKeyboard now requires a focused input for keyboardOpen=true (both
the chrome-overlap and iOS-gap paths). The moment an input blurs, the hook
reports keyboardOpen=false instead of waiting hundreds of ms for iOS's
visualViewport dismissal animation to settle.
This makes App-level mobileKeyboardOpen flip false instantly on blur, so
MobileNavBar reappears and project-content regains nav-bar padding in the
same frame. The ChatView composer (and TodoModal/PlanningModeModal) snap
to their post-keyboard layout in one move instead of crawling down with
iOS's keyboard slide.
Replaces the per-component 450ms suppress hack in ChatView (also dropped
in this commit) which couldn't reach the parent layout's nav padding
state and produced "below tab bar then snap up" jitter.
Adds a regression test and updates four existing tests that assumed
"vv shrinks → keyboard up" without focus (focus is now required).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add useMobileScrollLock hook using position:fixed body lock (Bootstrap/
Headless UI/Stripe pattern) to prevent iOS Safari from shifting the
document and visualViewport when an input inside a fixed-position modal
is focused. Wire into 15 input-bearing modals plus ChatView, replacing
ChatView's inline body-overflow effect.
- Widen computeBuildVersion in vite.config.ts to hash the entire app/ tree
so the version-check poll actually notices rebuilds (FN-3333 follow-up;
previously only main.tsx and package.json were hashed).
- ChatView: on input blur, suppress keyboard-aware sizing for 450ms while
reserving mobile-nav-bar space, so the composer snaps to its final
height in one move instead of crawling down with iOS's keyboard slide
and then jumping again when the nav bar reappears.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merges FN-3336 to recover chat streaming state on page reload — adds `isGenerating()` and `getGeneratingSessionIds()` to `ChatManager`, enriches the session API to surface active-streaming sessions, and makes `useChat` and `useQuickChat` hooks restore the `isGenerating` flag when loading existing se
Fusion-Task-Id: FN-3336
Merged two features: FN-3334 introduces a new `autoReloadOnVersionChange` setting with a toggle in the Settings modal, version-check logic in the dashboard, and corresponding tests; FN-3335 fixes worktree project root resolution in `createFnAgent` and the skill resolver, with tests for both paths. S
Fusion-Task-Id: FN-3334