Use packaged runtime metadata so desktop update checks do not compare npm releases against an unresolved current version.
- Resolve @fusion/desktop package metadata as a fallback when dashboard code runs from a desktop deployment.
- Fail closed when the current version is the 0.0.0 sentinel in both update-check paths.
- Cover CLI, desktop, and unresolved-version update-check behavior with regression tests.
- Add a patch changeset for the published Fusion CLI package.
Files changed:
.changeset/fn-7471-desktop-update-version.md | 7 ++
.../src/__tests__/cli-package-version.test.ts | 59 +++++++++++++++-
.../src/__tests__/update-check-route.test.ts | 78 +++++++++++++++++++++-
.../dashboard/src/__tests__/update-check.test.ts | 15 +++++
packages/dashboard/src/cli-package-version.ts | 37 +++++++++-
packages/dashboard/src/server.ts | 12 +++-
packages/dashboard/src/update-check.ts | 15 +++++
7 files changed, 216 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7471
Fusion-Task-Lineage: ee633b8c-766b-43f3-855d-3ce7abd41550
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Fixes 48 dashboard test files failing in CI run
[28681317913](https://github.com/Runfusion/Fusion/actions/runs/28681317913)
with `Failed to resolve entry for package
"@fusion-plugin-examples/cursor-runtime"`.
## Root cause
`runtime-provider-probes.ts` imports `probeCursorBinary` from
`@fusion-plugin-examples/cursor-runtime`, but neither the dashboard nor
CLI vitest configs had source aliases for this package. The
cursor-runtime plugin was added (with `runtime-provider-probes.ts`
consuming it) without updating the vitest configs to resolve the package
to its TypeScript source entry points.
Without the aliases, Vite tries to resolve the package's
`exports.import` field (`dist/index.js`) which doesn't exist in a source
checkout, causing every dashboard test that transitively imports the
runtime provider to fail at import time.
## Fix
Added `@fusion-plugin-examples/cursor-runtime` and
`@fusion-plugin-examples/cursor-runtime/probe` source aliases to:
- `packages/dashboard/vitest.config.ts`
- `packages/cli/vitest.config.ts`
This mirrors the existing pattern for droid-runtime, hermes-runtime,
openclaw-runtime, and paperclip-runtime.
## Verification
- Previously-failing dashboard tests now pass (295+ tests across 10
sampled files)
- Merge gate (`pnpm test:gate`) passes: 319 engine-core + 63 CI-shape
tests green
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved test setup so the dashboard can load the cursor runtime
directly from source, preventing missing export errors in local
checkouts.
* Added support for resolving the cursor runtime’s probe entry during
test runs, avoiding Vite resolution failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
runtime-provider-probes.ts imports probeCursorBinary from
@fusion-plugin-examples/cursor-runtime, but neither the dashboard nor CLI
vitest configs had source aliases for this package. Without the aliases,
Vite tried to resolve the package's dist/ exports which don't exist in a
source checkout, causing every dashboard test that transitively imports
the runtime provider to fail with 'Failed to resolve entry for package'.
This broke 48 dashboard test files in CI run 28561416741 after the cursor
runtime plugin was added without updating the vitest configs.
Two first-run desktop bugs.
## 1. "Agent with this name already exists" when creating the default
CEO
First-time project setup surfaced a **blocking** error when creating the
default **CEO** agent. The default first agent can be created from more
than one first-run surface (the unified `ModelOnboardingModal` agent
step **and** the project-setup `SetupWizardModal` sub-flow), and agent
names are unique per store (`agent-store.ts` `findAgentByName`), so the
second create returns 409.
The step's goal — *a first agent exists* — is already satisfied, so
**both** onboarding surfaces now treat a name collision as success and
advance instead of blocking. The user still creates the agent; they just
aren't punished for the flow offering it twice.
- `ModelOnboardingModal.tsx` `handleCreateFirstAgent`
- `SetupWizardModal.tsx` `handleCreateFirstAgent`
## 2. Header "Desktop local mode / Switch server" button did nothing
Clicking it should open the connection menu to switch between running
locally, connecting to a remote server, or a different remote.
Root cause: main relays the click as a `shell:open-connection-manager`
IPC (`ipc.ts` → `webContents.send`), but the **preload never forwarded
it** to the `window` DOM event `ShellContext.tsx` listens for
(`window.addEventListener("shell:open-connection-manager")`) — so the
signal was dropped and `NativeShellConnectionManager` never opened.
Fix: add the preload bridge (`preload.ts`) that re-dispatches the IPC as
the window event. The modal it opens already provides the Local/Remote
toggle (`setDesktopMode`) plus remote-server profile add/switch —
exactly the requested menu.
## Tests
- `SetupWizardModal.test.tsx`: advances to completion (no error alert)
when `createAgent` rejects with "already exists".
- `preload.test.ts`: the `shell:open-connection-manager` IPC handler
dispatches the `window` event.
## Surfaces enumerated
Both onboarding agent-create surfaces handled (ModelOnboarding +
SetupWizard). The conversational `AgentOnboardingModal` is intentionally
left to surface genuine duplicate errors (it creates user-named agents,
not the first-run default).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved onboarding so if the first agent already exists, setup
continues instead of showing an error.
* Fixed the desktop “Switch-server” button so it reliably opens the
connection manager.
* **Tests**
* Added coverage for the onboarding flow when the agent already exists.
* Added coverage to verify the desktop connection-manager event is
forwarded correctly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
First-run project setup surfaced a blocking "Agent with this name already exists" error when creating
the default CEO. The default first agent can be created from more than one first-run surface (the
unified ModelOnboarding agent step and the project-setup SetupWizard sub-flow), and agent names are
unique per store, so the second create returned 409. The step's goal — a first agent exists — was
already satisfied, so both onboarding surfaces now treat a name collision as success and advance
instead of blocking. The user still creates the agent; they just aren't punished for the flow offering
it twice.
Separately, the desktop header "Desktop local mode / Switch server" button did nothing: main relays
the click as a `shell:open-connection-manager` IPC (webContents.send), but the preload never forwarded
it to the `window` DOM event ShellContext listens for, so the signal was dropped. Add the preload
bridge so the button opens NativeShellConnectionManager (Local/Remote toggle + remote-server profiles),
letting operators switch between running locally, connecting to a remote server, or a different remote.
Regression tests: SetupWizard advances on an already-exists agent; preload dispatches the window event
on the IPC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Problem
The Windows desktop installer can't package from `main`.
electron-builder's production-dependency walk rejects the staged deploy
closure:
```
⨯ Production dependency @aws-sdk/core not found for package @aws-sdk/credential-provider-env version=^3.974.27
```
**Root cause:** an incidental `pnpm.overrides` entry — `"@aws-sdk/core":
"3.974.26"` — added without rationale in an unrelated commit
(`91fb53f3c`, "add agent update tool"). The bundled Fusion agent
packages later pulled `@aws-sdk/*` versions requiring `^3.974.27`, but
the exact-version override force-held core at `.26`. Classic
stale-exact-pin trap. It only surfaced at release/local-build time
because **nothing in the merge gate validates the packageable closure**
— electron-builder's walk ran only in `workflow_dispatch`/release
workflows.
## Fix (item 1)
Remove the `@aws-sdk/core` override. The `--legacy` deploy closure then
resolves core to `3.974.27` (satisfies all consumers). The main lockfile
still resolves core to `3.974.26` for its own internally-consistent
graph, so the **published `@runfusion/fusion` closure is unchanged** (no
changeset needed).
**Verified locally:** a fresh `pnpm --filter @fusion/desktop build` +
`electron-builder` passes the dependency walk and produces the installer
with **no manual patching** (previously required hand-editing the
deploy's core version).
## Prevention (items 2 + 3)
New **advisory, path-gated `Desktop packaging`** job in `pr-checks.yml`:
- `pnpm dedupe --check` — early-warning for lockfile version drift
(non-fatal).
- `pnpm --filter @fusion/desktop build` + `electron-builder --dir` —
reproduces the authoritative production-dependency walk (skips
NSIS/signing for speed; the walk that catches skew is
platform-independent, so ubuntu suffices).
- Path-gated to `pnpm-lock.yaml` / `package.json` /
`pnpm-workspace.yaml` / `packages/{desktop,dashboard,engine,core}` /
`plugins/` so it only runs when the closure can change.
So any future dependency skew — for **any** dependency — fails at PR
time instead of at release.
### Why not required / not in `full-suite.yml`
Kept **out of the required set** so the thin merge gate stays exactly
`[Lint, Typecheck, Build, Gate]` and branch protection is untouched (per
the gate's design docs). It reports a status on PRs; promote to
merge-blocking by adding "Desktop packaging" to the repo's required
checks. Placed in `pr-checks.yml` (not `full-suite.yml`) so it runs at
**PR time** rather than only post-merge.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Added an extra, PR-only non-blocking desktop packaging validation when
relevant project files change.
* The check builds the desktop package and verifies production
dependency consistency earlier in the review process.
* Updated dependency override settings by removing an outdated version
pin while keeping other pinned packages unchanged.
* Improved workflow comments to clarify how merge-blocking status checks
are handled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The desktop-pack job broke the CI-shape invariant (ci-workflow.test.ts) that
pr-checks.yml contains exactly [build, gate, lint, typecheck] — the gate's
job set maps 1:1 to branch-protection required checks. Extract the advisory
desktop-packaging validation into desktop-packaging.yml, still PR-triggered
and non-required, so the thin gate stays pure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows desktop installer failed to package: electron-builder's production-dependency walk rejected
`@aws-sdk/core@3.974.26` because `@aws-sdk/credential-provider-env` (resolved in the `--legacy` deploy
closure) requires `^3.974.27`. Root cause: an incidental `pnpm.overrides` entry pinning
`@aws-sdk/core` to the exact version `3.974.26` (added without rationale in an unrelated commit) which
force-held core below what its consumers now demand — the classic stale-exact-pin trap.
Fixes / prevention:
- Remove the `@aws-sdk/core` override so the deploy closure resolves core to 3.974.27 (satisfies all
consumers). The main lockfile still resolves core to 3.974.26 for its own consistent graph, so the
published @runfusion/fusion closure is unchanged (no changeset needed). Verified locally: a fresh
`@fusion/desktop build` + `electron-builder --dir` now passes the dependency walk with no manual patch.
- Add an advisory, path-gated `Desktop packaging` job to pr-checks.yml that reproduces electron-builder's
production-dependency walk (`--dir`, no NSIS/signing) plus a `pnpm dedupe --check` early-warning. This
is the only check that validates the packageable closure, which previously ran only in release/manual
workflows — so any future dependency skew now fails at PR time, for ANY dependency, instead of at
release/local-build time. Kept OUT of the required set so the thin merge gate [Lint, Typecheck, Build,
Gate] and branch protection are untouched; promote to blocking by adding it to required checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace real-timer waits with a deterministic bounded microtask flush for
the fire-and-forget task lifecycle hook chain (FN-5048: no slow tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to #1878 (merged). While building the Windows desktop
installer locally, `pnpm deploy`'s final temp-dir rename hits `EPERM` on
the orca-managed Windows workspace mount and litters deeply-nested
`deploy_tmp_*` dirs that are painful to remove.
This lets local Windows installer builds redirect the staged production
closure to an external, plain-NTFS path via a new
`FUSION_DESKTOP_DEPLOY_DIR` env var. **CI and the default in-tree
`packages/desktop/deploy` path are unchanged** — the override is opt-in.
Verified locally: with
`FUSION_DESKTOP_DEPLOY_DIR=C:\Users\...\fusion-build\deploy`, `pnpm
--filter @fusion/desktop build` stages the full closure externally
without the EPERM race, and `electron-builder --projectDir <that dir>
--win nsis --x64` produces a signed installer.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Desktop deployment staging can now be configured with an environment
variable, making it easier to customize where build files are prepared.
* **Documentation**
* Added notes about updated desktop deployment behavior on Windows,
including common rename and cleanup issues.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This PR is a field report from testing the Fusion 0.52.0 Windows desktop
release. It does not claim to be a final fix; instead it documents every
regression we hit, what we tried, and what we think the Fusion team
should do.
## Issues covered
1. `fusion desktop` fails to launch on Windows because the published
`@runfusion/fusion` package lists `electron` as a devDependency, so
`require('electron')` fails after a global install.
2. The native desktop launcher walks ancestor directories looking for a
workspace and crashes on unrelated JSON.
3. **Manage Projects** opens Settings instead of the project overview
(also fixed in #1882).
4. Windows Terminal native `Help` version dialogs (`Windows Terminal
1.24.11321.0`) appear on dashboard load / Settings page (also fixed in
#1882).
5. Packaged desktop release can be missing `preload.cjs` in the unpacked
Electron resources path.
6. Dashboard/gateway ports drift on Windows and collide with other
instances.
7. GPU/sandbox rendering instability on Windows Electron.
8. Desktop user-data is not isolated under `~/.fusion/`.
9. `fusion desktop` does not reuse an already-running dashboard server.
## Files
- `reports/desktop-release-issues-2026-07-03.md` — full report with
symptoms, root causes, what we tried, and proposed fixes.
- `packages/cli/src/commands/desktop.ts` — experimental Windows
GPU/sandbox Electron flags we used locally.
- `packages/desktop/src/main.ts` — experimental `FUSION_SERVER_PORT`
reuse path we used locally.
## Context
Tested on Windows 11 (build 26200) with Fusion 0.51.0 (last known good)
and 0.52.0 (current release with regressions). We also built a
standalone wrapper at
`https://github.com/Automata-intelligentsia/fusion-desktop-windows/releases/tag/v1.1.0`
that works around the native desktop problems by running `fn dashboard
--no-auth` and loading it in Electron.
## Recommendations
- Move `electron` to runtime dependencies in the published CLI package.
- Add Windows CI that launches the packaged desktop and asserts the
window is visible/responsive.
- Verify the packaged `release/win-unpacked` layout includes
`preload.cjs` before publishing.
- Consider the wrapper’s CLI-server + Electron-shell model as an
official fallback for Windows until the native desktop build is stable.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed Fusion Desktop startup for published installs by bundling the
Electron runtime dependency.
* Improved Windows stability (including Windows-only GPU/sandbox-related
Electron flags and skipping frontend terminal auto-creation).
* Prevented desktop/server startup conflicts by reusing the existing
dashboard via `FUSION_SERVER_PORT` and gating embedded local startup.
* Added one-time desktop profile migration to a dedicated user-data
location and improved task view routing consistency.
* Added safeguards to avoid shipping incomplete Electron desktop
packages (required assets must be present).
* **Documentation**
* Added/updated a Windows desktop regression field report with
resolution status.
* **Tests**
* Added coverage for the new user-data migration behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Add a one-time copy of the previous default Electron profile into
~/.fusion/desktop-user-data (user-data-migration.ts) so upgrading operators
keep window geometry/session instead of starting fresh (report Issue 8).
- Verify main.js/preload.js/client assets in scripts/build.ts (pre-package
staging) and in the packaged app.asar via desktop-windows.yml, so an
incomplete package fails the build/CI instead of dead-ending at runtime with
a silent missing preload (report Issue 5, recommendation #2).
- Fix two pre-existing stale workflow-assertion tests that expected a bare
'electron-builder --win' after the scripts moved to '--projectDir deploy'.
- Update the field report with a per-issue resolution matrix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Regenerate pnpm-lock.yaml for the new electron@^33.4.11 CLI dependency so
--frozen-lockfile stops failing every PR-check job.
- Gate the GPU/sandbox-disabling Electron flags (--no-sandbox, --disable-gpu,
etc.) to Windows only via os.platform(); applying them on macOS/Linux was a
security and rendering regression. This also resolves the unused os import
that failed lint.
- Add FNXC comments for the Windows GPU flags, desktop user-data isolation, and
CLI dashboard-server reuse per project comment convention.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pnpm deploy's final temp-dir rename hits EPERM on the orca-managed Windows workspace mount and litters
deeply-nested deploy_tmp_* dirs. Let local Windows installer builds redirect the staged closure to an
external plain-NTFS path via FUSION_DESKTOP_DEPLOY_DIR; CI and the default in-tree path are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mobile direct-chat dropdowns now identify sessions by conversation title instead of model-name text.
- Render the mobile session trigger with the conversation title or Untitled while preserving the provider logo.
- Remove mobile trigger model-tag styling and assert the model badge stays out of the compact header.
- Document the mobile Chat dropdown behavior and add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-7462-mobile-chat-dropdown-title.md | 7 ++++
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/ChatView.css | 8 -----
packages/dashboard/app/components/ChatView.tsx | 8 +++--
.../__tests__/ChatView.core-contracts.test.tsx | 25 +++++++++++---
.../components/__tests__/ChatView.mobile.test.tsx | 39 +++++++++++++++++++++-
6 files changed, 72 insertions(+), 17 deletions(-)
Fusion-Task-Id: FN-7462
Fusion-Task-Lineage: 6a6e2ac3-ecca-4076-81c5-b4e4a65eb286
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add reports/desktop-release-issues-2026-07-03.md documenting the
regressions observed in the Fusion 0.52.0 Windows desktop release,
including:
- fusion desktop fails to launch because electron is a devDependency
- native launcher walks ancestor dirs and fails on unrelated workspace JSON
- Manage Projects opens Settings instead of overview
- Windows Terminal Help version dialogs on dashboard load
- packaged preload.cjs missing in unpacked release layout
- port collisions and dashboard/gateway drift on Windows
- GPU/sandbox rendering instability on Windows Electron
- isolated user-data path needed
- CLI desktop command not reusing an already-running dashboard server
Also include the two experimental mitigations we applied locally:
- disable GPU/sandbox Electron flags in desktop.ts
- skip embedded local runtime when FUSION_SERVER_PORT is already set
These changes are intended as supporting evidence and starting points
for the Fusion team, not as a final fix.
## Windows: local runtime hung at "Starting local Fusion runtime…"
### Root cause
Desktop startup had two independent persisted sources of truth that
could disagree:
- `desktop-launch-mode.json` — decides whether **main** *starts* the
embedded local runtime
- `shell-connections.json` (`desktopMode`) — decides whether the
renderer **launch gate** *waits* for it
`shell:setDesktopMode` persists shell settings **before** the fallible
`startLocalRuntimeOnce()` / `saveDesktopLaunchMode()`. So a first
"local" selection whose runtime start threw or was interrupted left
`shell=local` / `launch-mode=choose` **permanently**. Every later launch
then sat at "Starting local Fusion runtime…" polling a runtime nobody
started → 30s timeout.
### Fix (defense in depth) — `78f0bc31`
- `initializeApp` reconciles: a completed shell `local` selection is
authoritative → heals the launch-mode file and starts the runtime.
- `onDesktopModeChange` / `onDesktopLaunchModeChange` persist
launch-mode **before** the fallible start so it can't re-desync.
- `DesktopLaunchGate` no longer assumes main started the runtime — if
it's not running/starting it actively `setDesktopMode("local")` before
polling.
- Env-gated startup trace (`FUSION_STARTUP_TRACE`) so packaged builds
(which log nothing) are diagnosable.
- Regression tests: split-brain → runtime starts + file heals;
agreement-on-choose → no start.
**Verified end-to-end under real Electron 35 / Node 22.16**: from the
exact split-brain state the runtime now reaches `RUNNING` and the
launch-mode file heals.
### Also: Windows root-build breakages — `bd24bd4c8`
- `scripts/build-workspace.mjs` "run as main" guard compared
`import.meta.url` to `` `file://${process.argv[1]}` ``, which never
matches on Windows → root `pnpm build` silently no-opped (exit 0, no
dist). Now uses `pathToFileURL(process.argv[1]).href`.
- `spawn('pnpm', …)` without `shell:true` (ENOENT on Windows) in
`build-workspace.mjs` and `packages/cli/tsup.config.ts` → pass `shell`
on win32.
### Notes
- `@fusion/desktop` and `@fusion/dashboard` are private → no changeset.
- Build the Windows installer via the `desktop-windows` workflow
(`electron-builder --projectDir deploy`); local `pnpm deploy` staging
hits an unrelated directory-rename race on managed-workspace
filesystems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added optional desktop runtime startup tracing (enabled via
environment variable).
* **Bug Fixes**
* Improved local desktop handoff to prevent reload loops and navigate
directly to the embedded local runtime.
* Added “split-brain” healing between persisted launch mode and shell
settings.
* Prevented auto-registration of runtime root/CWD during
desktop/dashboard startup.
* Improved Windows compatibility for CLI/workspace command spawning and
npm install process handling.
* **Tests**
* Expanded local/Electron integration, navigation, and onboarding
regression coverage; improved async flushing for more reliable
initialization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
Users reported the dashboard running out of browser `localStorage`
space. Root cause: stale SWR hydration entries were **never
garbage-collected**.
- `readCache` returned `null` for stale entries but left the bytes on
disk.
- No sweep existed for abandoned caches — per-chat-session and
per-chat-room message caches (up to 500 KB each, capped by
`DEFAULT_MAX_BYTES`) from old conversations accumulated indefinitely.
- No user-facing escape hatch to clear browser data.
With a typical ~5 MB origin quota, ~10 full chat-session caches could
exhaust storage.
## Fix — three layers
**1. Lazy GC in `readCache`** (`swrCache.ts`)
When a reader passes `maxAgeMs` and the entry is stale, `readCache` now
deletes the key instead of just returning `null`. Behavior-preserving —
every reader already treats a stale entry as a miss and re-fetches — so
the only observable change is freed quota on active paths.
**2. Boot sweep `pruneStaleCacheEntries()`** (`swrCache.ts` →
`DashboardLoader.tsx`)
Called once from `DashboardLoader` on mount, before hydration hooks read
their caches. Iterates all `kb-dashboard-*` keys and removes any
envelope older than 24 h (`SWR_LONG_MAX_AGE_MS`). Since 24 h is the
longest TTL any consumer uses, a pruned entry was already a miss for
every reader. This catches the real space hogs: abandoned
per-session/per-room message caches that are never read again.
**3. Settings → General "Clear local data"** (`GeneralSection.tsx`)
A new **Browser Data** panel with a button that confirms, then calls
`clearAllLocalCache()` — wipes all Fusion-owned browser data (`kb-*`,
`kb:*`, `fn-agent-log-*`, `fusion*`) while **preserving `fn.authToken`**
so the session survives the reload. Tasks and project settings live
server-side and are unaffected.
## Supporting fix
The test environment's `localStorage` mock (`vitest.setup.ts`) was
incomplete — it lacked `length` and `key()`, so any iteration-based
storage code silently no-op'd in tests. Added both to make it a faithful
`Storage` implementation.
## Verification
| Check | Result |
|---|---|
| swrCache tests | 14/14 ✅ |
| DashboardLoader tests | 6/6 ✅ |
| settings-sections tests | 15/15 ✅ |
| auth + useCurrentProject + useChat (regression) | 148/148 ✅ |
| Typecheck (`tsc --noEmit`) | clean ✅ |
| Lint (ESLint, 5 changed files) | clean ✅ |
| Changeset format (`pnpm check:changesets`) | valid ✅ |
## Files
- `packages/dashboard/app/utils/swrCache.ts` — lazy GC +
`pruneStaleCacheEntries` + `clearAllLocalCache`
- `packages/dashboard/app/components/DashboardLoader.tsx` — boot sweep
call
-
`packages/dashboard/app/components/settings/sections/GeneralSection.tsx`
— Clear local data button
- `packages/dashboard/app/utils/__tests__/swrCache.test.ts` — updated +
new tests
- `packages/dashboard/vitest.setup.ts` — complete the localStorage mock
- `.changeset/fix-dashboard-localstorage-quota-exhaustion.md`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a GitLab integration enable/disable toggle in Settings, with
collapsible GitLab sections and preserved saved credentials for
re-enable.
* Added a “Clear local data” option in Settings to remove cached browser
data while keeping sign-in state.
* Added bundled support for Linear Import in the plugin manager.
* **Bug Fixes**
* Prevented dashboard storage quota issues by trimming stale cached data
automatically.
* Improved mobile chat header and iOS terminal behavior for better
spacing and keyboard handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Two user-facing bugs on Windows:
1. Windows Terminal version dialogs on dashboard load/settings
- The embedded terminal auto-create flow was spawning a PTY on Windows,
which could invoke wt.exe and trigger its native "Help" version
message boxes (Windows Terminal 1.24.11321.0).
- Fix: skip auto-creation of the first terminal tab on Windows. Users
can still create a terminal explicitly; the failure no longer recurs
automatically on every dashboard session.
2. Manage Projects opens Settings instead of project overview
- handleViewAllProjects only reset viewMode; if the previous taskView was
"settings", MainContent rendered the Settings page before the overview
branch because the settings check precedes the overview branch.
- Fix: also reset taskView to "command-center" when leaving a project so
the overview surface (ProjectOverview) renders.
- Thread setTaskView through useProjectActions so the action has access to
the view router.
Tested on Windows 11 with Fusion 0.52.0 dashboard.
- Right dock now defaults to HIDDEN when the operator has no stored preference (readStoredRightDockOpen),
so first-run/onboarding lands on an uncluttered board; users opt in via the Header toggle (persists
"true"). Combined with the existing taskView="board" default and viewMode->"project" transition, first
run lands on the board. Updated the design comments and controller Harness tests (seed the open flag)
and added a focused default-closed assertion.
- useDeepLink deferred the "Project 'X' not found" toast behind a 3s grace window. A project selected
during onboarding is deep-linked via ?project=<id> before the projects list revalidates to include it,
so the eager toast fired even though the project loads a beat later. The deferred toast is cancelled as
soon as the project appears in the list; only a genuinely-absent project past the window still errors.
Added symptom-verification coverage (project appears within window -> no toast) and updated the
StrictMode/unknown-project/App-level tests to advance fake timers.
- Removed the stale ModelOnboardingModal test asserting the research-runs note that was intentionally
removed from onboarding earlier.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stale SWR hydration entries (per-chat-session/per-room message caches up to
500KB each) were never garbage-collected — readCache returned null for stale
entries but left the bytes on disk, and nothing swept abandoned caches. This
caused localStorage quota exhaustion for users with many projects and chat
sessions.
Three fixes:
- readCache now lazily deletes stale entries (behavior-preserving; every
reader already treats stale as a miss and re-fetches)
- Boot sweep pruneStaleCacheEntries() in DashboardLoader removes any
kb-dashboard-* entry older than 24h before hydration hooks read caches
- Settings > General "Browser Data" panel with a Clear local data button
that wipes all Fusion-owned browser data while preserving the auth token
Also completes the vitest localStorage mock (was missing length/key).
Two false-positive first-run banners:
1. "Select Default Model" recommendation appeared even after choosing a model in onboarding.
The picked model lived only in local state until completeOnboarding ran on the final step, so
selecting a model then exiting/skipping before the last step lost the choice — and
defaultProvider/defaultModelId stayed unset, tripping the recommendation. Now the pick is persisted
immediately in handleModelSelect, and completeOnboarding falls back to the first available model
when the operator connected a provider but never opened the dropdown. Either path leaves a durable
global default so the recommendation no longer misfires.
2. "Install the Fusion CLI" banner showed immediately on first run. Added a 3-day grace period keyed
off the first time the banner becomes eligible; Settings -> General -> CLI Binary still installs on
demand meanwhile, so this only defers the passive nudge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
useDashboardHealth fetched /api/health once on mount and never again. Right after a project is
created the engine is still starting, so that single fetch reports engine.available=false and the
"AI engine is not running" banner shows — and because health was never refreshed, the banner stayed
up even after the engine came online (the user reported the banner while the engine was in fact
running). Poll every 15s and preserve the last value on transient errors so the banner clears on its
own once the engine reports available.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The project name auto-derives from the chosen directory, so present the folder/directory picker
before the Project Name field — picking the folder first pre-fills a sensible name instead of
asking for a name before there is a folder to base it on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On the GitHub onboarding step, when GitHub is authenticated via the gh CLI (no OAuth provider),
the "Continue with gh CLI auth" button called setStep("project-setup") directly, skipping the
completion bookkeeping — so "github" was never added to completedSteps and step 2 never checked
off in the progress indicator. Advance via handleNext when ready via gh CLI (marks the step
completed) and via handleSkip when GitHub isn't connected (marks it skipped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove the "Research runs require provider credentials and an enabled Research View…" helper note
from the AI-setup step.
- Show the "Connect remote Fusion server" card only when not already connected to a remote server
(no active remote profile) — on any host including web — and never in LOCAL desktop mode, where the
local runtime is already the backend and prompting for a remote URL just confused first-run setup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createServer's modelRegistry is optional and, when absent, the /api/models endpoint returns an
empty list — so after connecting a provider (e.g. Anthropic) the onboarding model picker showed
"no models". The desktop wired authStorage but no ModelRegistry.
Add a shared createFusionModelRegistry(authStorage) factory to @fusion/engine (which already depends
on @earendil-works/pi-coding-agent) so non-CLI hosts don't need that dep directly, and pass its
result as modelRegistry from both desktop server paths. Verified against a clean embedded server:
/api/models now returns 24 Claude models instead of an empty list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
local-server now imports createFusionAuthStorage from @fusion/engine; add it to the test's engine
mock so the startup path resolves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop embedded server called createServer WITHOUT an authStorage, so GET /api/auth/status
returned 500 "Authentication is not configured". The dashboard's first-run onboarding hook
(useAuthOnboarding -> fetchAuthStatus) catches that failure silently and never opens the model
onboarding wizard — so the desktop skipped straight to project creation with no AI/GitHub setup,
regardless of onboarding-completion flags (which is why clearing them did nothing). Providers also
couldn't be authenticated at all.
Wire createFusionAuthStorage() (the same storage the CLI dashboard/serve commands use) into both the
primary (local-runtime) and legacy (local-server) desktop server paths. Verified against a clean
embedded server: /api/auth/status now returns 200 with the provider list instead of 500. (Full
API-key provider wrapping stays CLI-only for now; OAuth + CLI providers are available on desktop.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POST /api/projects registered a project but only started its engine lazily on the first scoped
store access, so a freshly-created project — especially the operator's FIRST project on the
desktop, where no other engine is running — left engineManager with no running engine. The
dashboard's project view then showed "AI engine is not running" (EngineUnavailableBanner keys off
/api/health engine.available). Proactively warm the engine via getOrCreateProjectStore (fires the
onProjectFirstAccessed hook -> engineManager.onProjectAccessed -> ensureEngine) right after the
project is activated, fire-and-forget so it never blocks/fails the registration response.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DesktopLaunchGate: the chooser onPick("local") path still called the removed
applyServerBaseUrl helper (broke Typecheck and the desktop local chooser flow) —
call navigateToLocalRuntimeOrigin. Also re-read shell.getState() immediately before the
self-healing start check so a stale mount snapshot doesn't fire a redundant
setDesktopMode("local") on normal boots (Greptile).
- register-fn-binary-routes: on Windows the npm install runs under a shell, so a timeout's
child.kill only stopped cmd.exe and left npm.cmd/node running — kill the whole process tree
via taskkill /T on win32 (CodeRabbit).
- local-server.test: align with the new resolver contract — assert the runtime never
auto-registers the root project and starts engine-less when no projects exist (CodeRabbit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
useAuthOnboarding gated its first-run check on `providers.length > 0`, so a brand-new install
(notably the desktop app on first launch, which can report zero configured providers) skipped
onboarding entirely and landed the operator on an empty dashboard with no AI/GitHub setup.
Evaluate first-run onboarding from completion state regardless of the provider list, so brand-new
users are guided through the model-onboarding wizard (AI -> GitHub -> Project) before project
creation. Existing branches (authenticated provider, completed onboarding, local completion,
setup-wizard deferral, one-shot guard) are unchanged. Adds an empty-provider-list regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop embedded runtime auto-registered its runtime root (the user's HOME directory) as a
project on first launch, and bare `fusion` / `fn` / `fn dashboard` / `fusion dashboard`
auto-registered the CWD as a project. Both silently created a "cwd-mode" project the operator
never chose and dropped them onto a board for it.
- Desktop: replace ensureDesktopRuntimeProject (which registered home) with
resolveDesktopRuntimePrimaryProject, which only PICKS an already-registered project as the
primary engine target and registers nothing. With no projects the server starts engine-less
(createServer's engine is optional) and the dashboard shows its onboarding empty state.
Applied to both the primary (local-runtime) and legacy (local-server) desktop server paths.
- CLI dashboard command: ensureCwdProjectRegistered now runs with autoRegister:false, so it uses
the CWD project only if already registered, else starts with none and the dashboard onboards.
(serve/daemon keep their existing --no-auto-register flag; the CLI `desktop` launcher unchanged.)
Verified: with zero projects the embedded server starts, /api/health -> 200, /api/projects -> [],
/ serves the client. Unit test asserts resolveDesktopRuntimePrimaryProject registers nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The published @runfusion/fusion package ships the desktop runtime assets
in dist/desktop/, but the [title-id-drift] db.ts migration normalized 0 active titles
[done-paused-backfill] db.ts migration repaired 0 done task rows
[title-id-drift] archive-db normalized 0 archived titles
[desktop] Auto-registered project "Fusion" at C:\Users\drewd\Tools\fusion-latest
[server:terminal] WebSocket server mounted { path: '/api/terminal/ws' }
[ai-session-store] Cleanup removed stale sessions {
terminalDeleted: 0,
orphanedDeleted: 0,
totalDeleted: 0,
maxAgeMs: 604800000,
operation: 'cleanup-stale-sessions',
_emittedAt: '2026-07-03T03:03:31.683Z',
_diagnosticsId: 'bc57bc09-d320-46b1-8717-a139f95bb9b7'
}
[server] AI session cleanup summary {
message: 'Removed stale AI sessions',
source: 'initial',
ttlMs: 604800000,
terminalDeleted: 0,
orphanedDeleted: 0,
totalDeleted: 0
} command calls require('electron')
to find the Electron binary. Electron was only declared as a built
dependency in pnpm-workspace.yaml, which makes it available during source
builds but does not include it in the published npm package. As a result,
[title-id-drift] archive-db normalized 0 archived titles
[server:terminal] WebSocket server mounted { path: '/api/terminal/ws' }
[ai-session-store] Cleanup removed stale sessions {
terminalDeleted: 0,
orphanedDeleted: 0,
totalDeleted: 0,
maxAgeMs: 604800000,
operation: 'cleanup-stale-sessions',
_emittedAt: '2026-07-03T03:03:37.491Z',
_diagnosticsId: 'a0c428a8-133d-4673-ab93-82a092646b13'
}
[server] AI session cleanup summary {
message: 'Removed stale AI sessions',
source: 'initial',
ttlMs: 604800000,
terminalDeleted: 0,
orphanedDeleted: 0,
totalDeleted: 0
} fails or hangs for npm consumers.
Fix: move electron to the dependencies of @runfusion/fusion so it is
installed alongside the CLI, and remove it from onlyBuiltDependencies.
Fixes: the desktop launcher regression where [title-id-drift] archive-db normalized 0 archived titles
[server:terminal] WebSocket server mounted { path: '/api/terminal/ws' }
[ai-session-store] Cleanup removed stale sessions {
terminalDeleted: 0,
orphanedDeleted: 0,
totalDeleted: 0,
maxAgeMs: 604800000,
operation: 'cleanup-stale-sessions',
_emittedAt: '2026-07-03T03:03:43.460Z',
_diagnosticsId: '325b9f28-e5d3-4937-855e-c5d1b95420b8'
}
[server] AI session cleanup summary {
message: 'Removed stale AI sessions',
source: 'initial',
ttlMs: 604800000,
terminalDeleted: 0,
orphanedDeleted: 0,
totalDeleted: 0
} cannot
resolve the Electron binary from a normal npm install.
The CLI-binary panel's "Install with npm" button (POST /system/fn-binary/install) ran
`spawn("npm", ["install","-g","runfusion.ai"], { shell: false })`. On Windows npm resolves to
npm.cmd, which Node refuses to spawn without a shell (spawn npm ENOENT / EINVAL, CVE-2024-27980),
so the button failed with "spawn npm ENOENT". Use shell on win32; the command/args are fixed
constants with no caller input, so shell quoting is safe. (The npx spawns in cli skills/extension
already set shell:true.)
Verified on Windows: spawn("npm",["--version"],{shell:false}) -> ENOENT; {shell:true} -> ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After the runtime starts, the packaged renderer was left on its file:// page and only had
?serverBaseUrl=… appended. But the dashboard client issues RELATIVE /api requests, so on a
file:// origin they resolve to file:///api/… and fail ("Can't reach the Fusion backend /
Failed to fetch"); the embedded server also sends no CORS header, so a cross-origin fetch
would be blocked as well. (The server itself is fine — verified it serves both /api/health
and the client HTML at /.)
Fix: once the runtime is running, navigate the window to the runtime's OWN origin
(http://127.0.0.1:<port>/), which the embedded server serves — making /api same-origin.
This mirrors how remote mode already navigates to its server URL. The gate now treats "page
served over http(s)" as the ready signal (protocol check) instead of a serverBaseUrl URL
param; that also supersedes the previous cached-context handoff check (04fd91f6) and keeps
the reload loop closed, since bootstrapShellHostContext() strips shell query params at load.
Regression tests: navigates to the runtime origin exactly once from file://; renders the app
without navigating when already served over http; starts the runtime first when it isn't
running, then navigates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After the runtime starts, DesktopLaunchGate calls applyServerBaseUrl() which reloads the
page with ?serverBaseUrl=… so the shell-host bootstrap can route API calls to the embedded
server. But main.tsx runs bootstrapShellHostContext() at module load — BEFORE the gate's
effect — and it strips every shell query param from the URL via history.replaceState. The
gate then checked window.location.search for serverBaseUrl (after an await), always missed
it, and re-ran the handoff → window.location.replace → reload → strip → an infinite reload
loop that renders as rapid "Starting local Fusion runtime…" flashing that never connects.
This was latent until the split-brain fix (78f0bc31) let the runtime actually start and
reach the handoff.
Fix: detect the completed handoff from the CACHED shell-host context
(getShellHostContext().serverUrl), which the bootstrap preserves, instead of the stripped
URL (URL param kept only as a fallback).
Adds a DesktopLaunchGate regression test: with serverUrl present in the cached context but
stripped from the URL, the gate renders children and does NOT reload; on first load it
performs the handoff exactly once. Verified the test fails against the pre-fix gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>