docs(desktop): Windows desktop release issues field report (#1883)

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 -->
This commit is contained in:
gsxdsm
2026-07-03 09:28:59 -07:00
committed by GitHub
17 changed files with 551 additions and 80 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix `fusion desktop` on Windows and published npm installs (Electron dependency, GPU/sandbox flags, dashboard reuse).
category: fix
dev: `packages/cli/package.json` now depends on `electron` at runtime; previously the desktop launcher called `require("electron")`, which is only available inside the source checkout (via `pnpm-workspace.yaml` `onlyBuiltDependencies`) and is missing for npm consumers, causing `fusion desktop` to hang or fail silently. The launcher now applies GPU/sandbox-disabling Electron flags only on Windows (`os.platform() === "win32"`), keeps hardware acceleration and the Chromium sandbox on macOS/Linux, exports `FUSION_SERVER_PORT` so the desktop reuses the CLI-started dashboard instead of double-binding ports, and isolates desktop user-data under `~/.fusion/desktop-user-data`. Relocating the profile performs a one-time copy of the previous default Electron profile (`user-data-migration.ts`) so upgrading operators keep window geometry/session. `packages/desktop/scripts/build.ts` now fails the build if `main.js`/`preload.js`/`client/index.html` are missing from `dist/` or the staged `deploy/dist/`, preventing shipping an incomplete `app.asar`.

View File

@@ -89,6 +89,34 @@ jobs:
if ($nsis.Count -eq 0) { Write-Error "No NSIS installer artifact produced"; exit 1 }
if ($portable.Count -eq 0) { Write-Error "No portable EXE artifact produced"; exit 1 }
- name: Verify packaged app.asar assets
shell: pwsh
run: |
# FNXC:WindowsDesktopPackaging 2026-07-03-15:40:
# Field report Issue 5: the packaged desktop shipped without preload.js and
# dead-ended on "can't reach the Fusion backend" (preload absence is silent —
# the contextBridge never installs window.fusionShell/fusionAPI). scripts/build.ts
# verifies the pre-package staging tree; this asserts the SHIPPED app.asar itself
# contains the Electron main/preload/renderer entrypoints, since only the packed
# asar reflects what a user installs.
$required = @('dist/main.js', 'dist/preload.js', 'dist/client/index.html')
$unpackedRoots = Get-ChildItem packages/desktop/dist-electron -Directory -Filter 'win*-unpacked'
if ($unpackedRoots.Count -eq 0) { Write-Error "No win-unpacked directory produced"; exit 1 }
foreach ($root in $unpackedRoots) {
$asar = Join-Path $root.FullName 'resources/app.asar'
if (!(Test-Path $asar)) { Write-Error "Missing packaged app.asar: $asar"; exit 1 }
$entries = npx --yes @electron/asar list $asar
if ($LASTEXITCODE -ne 0) { Write-Error "Failed to list app.asar: $asar"; exit 1 }
$normalized = $entries | ForEach-Object { $_.TrimStart('/','\').Replace('\','/') }
foreach ($asset in $required) {
if ($normalized -notcontains $asset) {
Write-Error "app.asar is missing required Electron asset '$asset' in $($root.Name); refusing to ship an incomplete package"
exit 1
}
}
Write-Host "$($root.Name)/resources/app.asar contains all required Electron assets"
}
# Automated publish is intentionally deferred to FN-5593.
# Keep a single artifact; filenames include -x64 / -arm64 so both arches are captured.
- name: Upload Windows artifacts

View File

@@ -63,6 +63,7 @@
"@earendil-works/pi-coding-agent": "^0.80.3",
"dockerode": "^4.0.12",
"express": "^5.1.0",
"electron": "^33.4.11",
"i18next": "^26.3.1",
"ink": "^7.0.5",
"ink-spinner": "^5.0.0",

View File

@@ -5,6 +5,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
import type { AddressInfo } from "node:net";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import * as os from "node:os";
import { CentralCore, TaskStore } from "@fusion/core";
import { createServer } from "@fusion/dashboard";
import { ProjectEngineManager } from "@fusion/engine";
@@ -177,7 +178,27 @@ export async function runDesktop(options: RunDesktopOptions = {}): Promise<void>
const runtime = await startDashboardRuntime(rootDir, Boolean(options.paused), Boolean(options.noAuth));
const electronBinary = resolveElectronBinary();
const electronArgs = ["--enable-source-maps", desktopEntry, ...(options.dev ? ["--dev"] : [])];
/*
FNXC:DesktopWindowsGpuFlags 2026-07-03-14:40:
Windows Electron renderers observed blank/flickering GPU output and sandbox-related launch instability on some GPUs during the 0.52.0 desktop release (field report Issue 7). Disable GPU acceleration and the Chromium sandbox ONLY on Windows so macOS/Linux keep hardware acceleration and the security sandbox. Applying `--no-sandbox`/`--disable-gpu` on every platform would be a needless security and rendering regression off Windows.
*/
const isWindows = os.platform() === "win32";
const windowsGpuFlags = isWindows
? [
"--disable-gpu",
"--disable-gpu-compositing",
"--disable-gpu-sandbox",
"--disable-software-rasterizer",
"--no-sandbox",
]
: [];
const electronArgs = [
"--enable-source-maps",
desktopEntry,
...windowsGpuFlags,
...(options.dev ? ["--dev"] : []),
];
// Build environment for Electron process
const electronEnv: NodeJS.ProcessEnv = {

View File

@@ -337,7 +337,7 @@ function AppInner() {
const { pushNav, replaceCurrent, removeNav } = useNavigationHistory({ enabled: true });
// View state must be defined before useTasks since useTasks depends on taskView for SSE gating
const { viewMode, setViewMode, taskView, handleChangeTaskView } = useViewState({
const { viewMode, setViewMode, taskView, setTaskView, handleChangeTaskView } = useViewState({
projectsLoading,
projectsError,
currentProjectLoading,
@@ -718,6 +718,7 @@ function AppInner() {
setCurrentProject,
clearCurrentProject,
setViewMode,
setTaskView,
currentProject,
refreshProjects,
toggleFavoriteProvider,

View File

@@ -3,13 +3,14 @@ import { useTranslation } from "react-i18next";
import { pauseProject, resumeProject, unregisterProject } from "../api";
import type { ProjectInfo } from "../api";
import { replaceProjectIdInUrl } from "../utils/projectUrlState";
import type { ViewMode } from "./useViewState";
import type { ViewMode, TaskView } from "./useViewState";
import type { ToastType } from "./useToast";
interface UseProjectActionsOptions {
setCurrentProject: (project: ProjectInfo) => void;
clearCurrentProject: () => void;
setViewMode: (mode: ViewMode) => void;
setTaskView: (view: TaskView) => void;
currentProject: ProjectInfo | null;
refreshProjects: () => Promise<void>;
toggleFavoriteProvider: (provider: string) => Promise<void>;
@@ -41,6 +42,7 @@ export function useProjectActions(options: UseProjectActionsOptions): UseProject
setCurrentProject,
clearCurrentProject,
setViewMode,
setTaskView,
currentProject,
refreshProjects,
toggleFavoriteProvider,
@@ -62,7 +64,8 @@ export function useProjectActions(options: UseProjectActionsOptions): UseProject
replaceProjectIdInUrl(null);
clearCurrentProject();
setViewMode("overview");
}, [clearCurrentProject, setViewMode]);
setTaskView("command-center");
}, [clearCurrentProject, setViewMode, setTaskView]);
const handleOpenSettings = useCallback(() => {
openSettings();

View File

@@ -241,7 +241,14 @@ export function useTerminalSessions(projectId?: string): UseTerminalSessionsRetu
}, [projectId]); // Re-run when project scope changes
// Auto-create first tab if no tabs exist after validation
// On Windows, do NOT auto-create because the embedded shell may invoke Windows Terminal
// (wt.exe) and produce native "Help" version dialogs. Users can still create a terminal
// explicitly from the UI.
useEffect(() => {
if (typeof window !== "undefined" && window.navigator.userAgent.includes("Windows")) {
setIsReady(true);
return;
}
if (tabs.length === 0 && isReady && serverAvailable && !bootstrapError) {
// Capture current generation so only this attempt's result is accepted
const gen = generationRef.current;

View File

@@ -1,7 +1,7 @@
import { build } from "esbuild";
import { cp, mkdir, rm, stat } from "node:fs/promises";
import { join } from "node:path";
import { buildCore, buildDashboard, buildDashboardClient, buildEngine, packageRoot, stageDesktopDeploy, workspaceRoot } from "./workspace-tools";
import { buildCore, buildDashboard, buildDashboardClient, buildEngine, desktopDeployDir, packageRoot, stageDesktopDeploy, workspaceRoot } from "./workspace-tools";
const dashboardRoot = join(workspaceRoot, "packages", "dashboard");
const dashboardClientDir = join(dashboardRoot, "dist", "client");
const dashboardRegistryManifestSource = join(dashboardRoot, "src", "registry-manifest.json");
@@ -113,6 +113,42 @@ async function ensureEmbeddedRuntimeBuild(): Promise<void> {
await buildEngine();
}
// FNXC:DesktopPackaging 2026-07-03-15:25:
// Guard the packaged Electron closure so a missing preload/main/renderer asset
// fails the build instead of shipping and crashing on a user's machine (field
// report Issue 5: preload was missing from the packaged unpacked layout).
// `preload.js` in particular is silent when absent — the contextBridge never
// installs window.fusionShell/fusionAPI and the app dead-ends on "can't reach
// the Fusion backend". Assert the required files in BOTH the source `dist/`
// (esbuild output) and the staged `deploy/dist/` (what electron-builder packs
// into app.asar), since only the latter reflects what ships.
const REQUIRED_PACKAGED_ASSETS = ["main.js", "preload.js", join("client", "index.html")];
async function verifyPackagedArtifacts(): Promise<void> {
console.log("[desktop:build] Verifying required packaged assets are present...");
const stagedDistDir = join(desktopDeployDir, "dist");
const roots: Array<{ label: string; dir: string }> = [
{ label: "dist", dir: desktopDistDir },
{ label: "deploy/dist", dir: stagedDistDir },
];
const missing: string[] = [];
for (const { label, dir } of roots) {
for (const asset of REQUIRED_PACKAGED_ASSETS) {
try {
await stat(join(dir, asset));
} catch {
missing.push(`${label}/${asset}`);
}
}
}
if (missing.length > 0) {
throw new Error(
`Desktop packaging is missing required Electron assets: ${missing.join(", ")}. ` +
`Refusing to ship an incomplete app.asar.`,
);
}
}
async function main(): Promise<void> {
await rm(desktopDistDir, { recursive: true, force: true });
await mkdir(desktopDistDir, { recursive: true });
@@ -128,6 +164,8 @@ async function main(): Promise<void> {
// which drops `deduped` subtrees and left the embedded runtime missing deps.
await stageDesktopDeploy();
await verifyPackagedArtifacts();
console.log("[desktop:build] Desktop build complete");
}

View File

@@ -62,9 +62,12 @@ describe("electron-builder desktop config", () => {
const packageJsonRaw = await readDesktopFile("package.json");
const packageJson = JSON.parse(packageJsonRaw) as { scripts?: Record<string, string> };
expect(packageJson.scripts?.["dist:win"]).toBe("electron-builder --win");
expect(packageJson.scripts?.["dist:mac"]).toBe("electron-builder --mac");
expect(packageJson.scripts?.["dist:linux"]).toBe("electron-builder --linux");
// Scripts package the staged production closure via `--projectDir deploy`
// (see scripts/workspace-tools.ts stageDesktopDeploy), so assert both the
// deploy projectDir and the platform flag rather than a bare invocation.
expect(packageJson.scripts?.["dist:win"]).toBe("electron-builder --projectDir deploy --win");
expect(packageJson.scripts?.["dist:mac"]).toBe("electron-builder --projectDir deploy --mac");
expect(packageJson.scripts?.["dist:linux"]).toBe("electron-builder --projectDir deploy --linux");
});
it("keeps required mac and linux targets", async () => {

View File

@@ -328,7 +328,10 @@ describe("main process", () => {
const { resolveLocalRuntimeRoot } = await importMainModule();
expect(resolveLocalRuntimeRoot()).toBe("/custom/fusion-home");
expect(mocks.app.getPath).not.toHaveBeenCalled();
// FUSION_HOME must satisfy the root without falling back to getPath("home").
// (Module load calls getPath("userData") for the profile-relocation guard, so
// assert the specific "home" lookup is skipped rather than getPath overall.)
expect(mocks.app.getPath).not.toHaveBeenCalledWith("home");
});
it("initializeApp does not start local runtime for remembered choose mode", async () => {

View File

@@ -19,19 +19,19 @@ describe("desktop release workflow wiring", () => {
for (const workflow of [release, testRelease]) {
expect(workflow).toContain("build-desktop-windows:");
expect(workflow).toContain("runs-on: windows-latest");
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:win|electron-builder --win/);
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:win|electron-builder[^\n]*--win/);
expect(workflow).toContain("name: fusion-desktop-windows");
expect(workflow).toContain("packages/desktop/dist-electron/latest.yml");
expect(workflow).toContain("build-desktop-macos:");
expect(workflow).toContain("runs-on: macos-latest");
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:mac|electron-builder --mac/);
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:mac|electron-builder[^\n]*--mac/);
expect(workflow).toContain("name: fusion-desktop-macos");
expect(workflow).toContain("packages/desktop/dist-electron/latest-mac.yml");
expect(workflow).toContain("build-desktop-linux:");
expect(workflow).toContain("runs-on: ubuntu-latest");
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:linux|electron-builder --linux/);
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:linux|electron-builder[^\n]*--linux/);
expect(workflow).toContain("--x64");
expect(workflow).toContain("--arm64");
expect(workflow).toContain("Fusion-*-linux-arm64.AppImage");

View File

@@ -0,0 +1,84 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { migratePreviousUserData } from "../user-data-migration.js";
/*
FNXC:DesktopUserDataMigration 2026-07-03-15:10:
Verify the one-time profile copy that keeps an upgrading operator's window
geometry/session when userData relocates to ~/.fusion (field report Issue 8):
it copies a populated previous profile into an absent/empty new dir exactly once,
and never overwrites an already-migrated profile, an empty source, or the same path.
*/
describe("migratePreviousUserData", () => {
let root: string;
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), "fusion-userdata-mig-"));
});
afterEach(() => {
rmSync(root, { recursive: true, force: true });
});
const seed = (dir: string, file: string, contents: string): void => {
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, file), contents);
};
it("copies a populated previous profile into an absent new dir", () => {
const previous = join(root, "old");
const next = join(root, "new");
seed(previous, "window-state.json", "{\"x\":10}");
expect(migratePreviousUserData(previous, next)).toBe(true);
expect(readFileSync(join(next, "window-state.json"), "utf-8")).toBe("{\"x\":10}");
// Copy, not move: the source is left intact for downgrades/partial failures.
expect(readFileSync(join(previous, "window-state.json"), "utf-8")).toBe("{\"x\":10}");
});
it("does not overwrite a new dir that already has data", () => {
const previous = join(root, "old");
const next = join(root, "new");
seed(previous, "session.json", "old");
seed(next, "session.json", "already-migrated");
expect(migratePreviousUserData(previous, next)).toBe(false);
expect(readFileSync(join(next, "session.json"), "utf-8")).toBe("already-migrated");
});
it("treats an empty new dir as eligible for migration", () => {
const previous = join(root, "old");
const next = join(root, "new");
seed(previous, "session.json", "restore-me");
mkdirSync(next, { recursive: true }); // exists but empty
expect(migratePreviousUserData(previous, next)).toBe(true);
expect(readFileSync(join(next, "session.json"), "utf-8")).toBe("restore-me");
});
it("no-ops when there is no previous profile", () => {
const previous = join(root, "missing");
const next = join(root, "new");
expect(migratePreviousUserData(previous, next)).toBe(false);
});
it("no-ops when the previous profile exists but is empty", () => {
const previous = join(root, "old");
const next = join(root, "new");
mkdirSync(previous, { recursive: true });
expect(migratePreviousUserData(previous, next)).toBe(false);
});
it("no-ops when previous and new paths are identical", () => {
const same = join(root, "same");
seed(same, "session.json", "keep");
expect(migratePreviousUserData(same, same)).toBe(false);
expect(readFileSync(join(same, "session.json"), "utf-8")).toBe("keep");
});
});

View File

@@ -1,6 +1,32 @@
import { app, BrowserWindow, nativeImage, screen, Tray } from "electron";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import os from "node:os";
import { migratePreviousUserData } from "./user-data-migration.js";
/*
FNXC:DesktopUserDataIsolation 2026-07-03-14:40:
Isolate Electron desktop user-data/cache/crash artifacts under ~/.fusion/desktop-user-data so the packaged desktop does not share (or collide with) the default per-app Chromium profile across installs and Fusion versions (field report Issue 8). setPath must run before app "ready"; once locked it throws, which is non-fatal here.
FNXC:DesktopUserDataMigration 2026-07-03-15:10:
Migrate the previous default profile into the new location BEFORE `setPath` overrides it, so upgrading operators keep their window geometry/session. `app.getPath("userData")` returns the default `<appData>/<productName>` path until overridden, so capture it first. See user-data-migration.ts for the one-time-copy gating.
*/
const fusionUserDataDir = join(os.homedir(), ".fusion", "desktop-user-data");
const migratedUserData = migratePreviousUserData(app.getPath("userData"), fusionUserDataDir);
if (migratedUserData) {
console.log(`[desktop/main] Migrated previous desktop profile into ${fusionUserDataDir}`);
}
try {
app.commandLine.appendSwitch("user-data-dir", fusionUserDataDir);
app.setPath("userData", fusionUserDataDir);
app.setPath("cache", join(fusionUserDataDir, "cache"));
app.setPath("crashDumps", join(fusionUserDataDir, "crashes"));
} catch {
// Path already locked after app is ready; not a fatal error.
}
import { setupDeepLinkHandler, registerDeepLinkProtocol } from "./deep-link.js";
import { registerIpcHandlers } from "./ipc.js";
import { buildAppMenu } from "./menu.js";
@@ -225,7 +251,7 @@ export async function initializeApp(): Promise<void> {
}
}
if (rememberedLaunchMode === "local") {
if (rememberedLaunchMode === "local" && !process.env.FUSION_SERVER_PORT) {
try {
await startLocalRuntimeOnce();
} catch (error) {
@@ -242,6 +268,16 @@ export async function initializeApp(): Promise<void> {
currentDesktopLaunchMode = "local";
}
/*
FNXC:DesktopReuseCliServer 2026-07-03-14:40:
When `fusion desktop` launches Electron it exports FUSION_SERVER_PORT for the dashboard the CLI already started. In that case the desktop must NOT spin up its own embedded local runtime (which would double-bind ports and conflict on Windows, field report Issue 9): skip startLocalRuntimeOnce() when the port is set, and treat a "choose" mode as already-local so the shell attaches to the external CLI server.
*/
if (currentDesktopLaunchMode === "choose" && process.env.FUSION_SERVER_PORT) {
// The CLI already started a dashboard server; use it without spawning an
// embedded local runtime. The shell state will report external-cli running.
currentDesktopLaunchMode = "local";
}
const windowState = state
? clampWindowStateToVisibleDisplay(
state,

View File

@@ -0,0 +1,32 @@
import { cpSync, existsSync, readdirSync } from "node:fs";
/*
FNXC:DesktopUserDataMigration 2026-07-03-15:10:
Relocating the Electron desktop profile to ~/.fusion/desktop-user-data (field report Issue 8) would silently orphan an existing operator's window geometry, sign-in session, and local storage under the OLD default Chromium profile (`<appData>/<productName>`). This performs a one-time COPY of the previous default profile into the new location, gated so it runs only on the first launch after upgrade: the new dir must be absent/empty and the old dir must exist, be non-empty, and be distinct. Copy (not move) so a failed/partial migration or a downgrade still finds the original profile intact. Best-effort: any failure returns false and the caller falls back to a fresh profile rather than blocking startup.
*/
function isMissingOrEmptyDir(dir: string): boolean {
try {
return readdirSync(dir).length === 0;
} catch {
// ENOENT (or unreadable) → treat as missing so we do not block on it.
return true;
}
}
/**
* One-time copy of a previous Electron userData profile into `newDir`.
* @returns `true` if a migration copy was performed, `false` otherwise (already
* migrated, nothing to migrate, same path, or the copy failed).
*/
export function migratePreviousUserData(previousDir: string, newDir: string): boolean {
try {
if (previousDir === newDir) return false;
if (!isMissingOrEmptyDir(newDir)) return false; // already migrated / has its own data
if (!existsSync(previousDir) || isMissingOrEmptyDir(previousDir)) return false;
cpSync(previousDir, newDir, { recursive: true });
return true;
} catch {
return false;
}
}

142
pnpm-lock.yaml generated
View File

@@ -55,6 +55,9 @@ importers:
dockerode:
specifier: ^4.0.12
version: 4.0.12
electron:
specifier: ^33.4.11
version: 33.4.11
express:
specifier: ^5.1.0
version: 5.2.1
@@ -1228,8 +1231,8 @@ packages:
resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/core@3.974.15':
resolution: {integrity: sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw==}
'@aws-sdk/core@3.974.26':
resolution: {integrity: sha512-wRj7Pthvjk3anees97pUWlxlTa0DUjeGrEQU5fKDZVdWZV0ekaprbof0df2uaE9g8u67t035v2j+ne2AW2UMkA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-env@3.972.41':
@@ -1292,6 +1295,10 @@ packages:
resolution: {integrity: sha512-81duvlltQlsfn5K+o8zILcystBRdbT1G2JJYVCML5NZHBz4CL/zf+sAemCtBh/uh6RQUMyInGeZLQ7/8igZhbA==}
engines: {node: '>=20.0.0'}
'@aws-sdk/types@3.973.15':
resolution: {integrity: sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/types@3.973.9':
resolution: {integrity: sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg==}
engines: {node: '>=20.0.0'}
@@ -1300,8 +1307,8 @@ packages:
resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==}
engines: {node: '>=20.0.0'}
'@aws-sdk/xml-builder@3.972.26':
resolution: {integrity: sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g==}
'@aws-sdk/xml-builder@3.972.33':
resolution: {integrity: sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A==}
engines: {node: '>=20.0.0'}
'@aws/lambda-invoke-store@0.2.4':
@@ -2603,9 +2610,6 @@ packages:
'@cfworker/json-schema':
optional: true
'@nodable/entities@2.1.0':
resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -2844,6 +2848,10 @@ packages:
resolution: {integrity: sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==}
engines: {node: '>=18.0.0'}
'@smithy/core@3.29.1':
resolution: {integrity: sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A==}
engines: {node: '>=18.0.0'}
'@smithy/credential-provider-imds@4.3.5':
resolution: {integrity: sha512-yiF8xHpdkaTfzLVqFzsP6WvNghEK+qZzLYWFD13L2SsFhbXwBGlxdocKF95qjr7s5lE5NRage+EJFK4mAsx88Q==}
engines: {node: '>=18.0.0'}
@@ -2868,10 +2876,18 @@ packages:
resolution: {integrity: sha512-QBJKWGqIknH0dc9LWpfH1mkdokAx6iXYN3UcQ3eY6uIEyScuoQAhfl94ge7ozUy9WgFUdE8xsvwBjaYBbWmPNA==}
engines: {node: '>=18.0.0'}
'@smithy/signature-v4@5.6.2':
resolution: {integrity: sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g==}
engines: {node: '>=18.0.0'}
'@smithy/types@4.14.2':
resolution: {integrity: sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==}
engines: {node: '>=18.0.0'}
'@smithy/types@4.15.1':
resolution: {integrity: sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q==}
engines: {node: '>=18.0.0'}
'@smithy/util-buffer-from@2.2.0':
resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
engines: {node: '>=14.0.0'}
@@ -4535,6 +4551,11 @@ packages:
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
engines: {node: '>=8.0.0'}
electron@33.4.11:
resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==}
engines: {node: '>= 12.20.55'}
hasBin: true
electron@35.7.5:
resolution: {integrity: sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw==}
engines: {node: '>= 12.20.55'}
@@ -4793,13 +4814,6 @@ packages:
fast-wrap-ansi@0.2.2:
resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
fast-xml-builder@1.2.0:
resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
fast-xml-parser@5.7.3:
resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==}
hasBin: true
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -6226,10 +6240,6 @@ packages:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-expression-matcher@1.5.0:
resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
engines: {node: '>=14.0.0'}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
@@ -7004,9 +7014,6 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
strnum@2.2.3:
resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==}
strtok3@6.3.0:
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
engines: {node: '>=10'}
@@ -7570,10 +7577,6 @@ packages:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
xml-naming@0.1.0:
resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==}
engines: {node: '>=16.0.0'}
xml2js@0.6.2:
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
engines: {node: '>=4.0.0'}
@@ -7771,7 +7774,7 @@ snapshots:
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/credential-provider-node': 3.972.46
'@aws-sdk/eventstream-handler-node': 3.972.18
'@aws-sdk/middleware-eventstream': 3.972.14
@@ -7784,20 +7787,20 @@ snapshots:
'@smithy/types': 4.14.2
tslib: 2.8.1
'@aws-sdk/core@3.974.15':
'@aws-sdk/core@3.974.26':
dependencies:
'@aws-sdk/types': 3.973.9
'@aws-sdk/xml-builder': 3.972.26
'@aws-sdk/types': 3.973.15
'@aws-sdk/xml-builder': 3.972.33
'@aws/lambda-invoke-store': 0.2.4
'@smithy/core': 3.24.5
'@smithy/signature-v4': 5.4.5
'@smithy/types': 4.14.2
'@smithy/core': 3.29.1
'@smithy/signature-v4': 5.6.2
'@smithy/types': 4.15.1
bowser: 2.14.1
tslib: 2.8.1
'@aws-sdk/credential-provider-env@3.972.41':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
'@smithy/types': 4.14.2
@@ -7805,7 +7808,7 @@ snapshots:
'@aws-sdk/credential-provider-http@3.972.43':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
'@smithy/fetch-http-handler': 5.4.5
@@ -7815,7 +7818,7 @@ snapshots:
'@aws-sdk/credential-provider-ini@3.972.45':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/credential-provider-env': 3.972.41
'@aws-sdk/credential-provider-http': 3.972.43
'@aws-sdk/credential-provider-login': 3.972.45
@@ -7831,7 +7834,7 @@ snapshots:
'@aws-sdk/credential-provider-login@3.972.45':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/nested-clients': 3.997.13
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
@@ -7854,7 +7857,7 @@ snapshots:
'@aws-sdk/credential-provider-process@3.972.41':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
'@smithy/types': 4.14.2
@@ -7862,7 +7865,7 @@ snapshots:
'@aws-sdk/credential-provider-sso@3.972.45':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/nested-clients': 3.997.13
'@aws-sdk/token-providers': 3.1056.0
'@aws-sdk/types': 3.973.9
@@ -7872,7 +7875,7 @@ snapshots:
'@aws-sdk/credential-provider-web-identity@3.972.45':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/nested-clients': 3.997.13
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
@@ -7895,7 +7898,7 @@ snapshots:
'@aws-sdk/middleware-websocket@3.972.23':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
'@smithy/fetch-http-handler': 5.4.5
@@ -7907,7 +7910,7 @@ snapshots:
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/signature-v4-multi-region': 3.996.30
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
@@ -7925,7 +7928,7 @@ snapshots:
'@aws-sdk/token-providers@3.1048.0':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/nested-clients': 3.997.13
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
@@ -7934,13 +7937,18 @@ snapshots:
'@aws-sdk/token-providers@3.1056.0':
dependencies:
'@aws-sdk/core': 3.974.15
'@aws-sdk/core': 3.974.26
'@aws-sdk/nested-clients': 3.997.13
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.5
'@smithy/types': 4.14.2
tslib: 2.8.1
'@aws-sdk/types@3.973.15':
dependencies:
'@smithy/types': 4.15.1
tslib: 2.8.1
'@aws-sdk/types@3.973.9':
dependencies:
'@smithy/types': 4.14.2
@@ -7950,10 +7958,9 @@ snapshots:
dependencies:
tslib: 2.8.1
'@aws-sdk/xml-builder@3.972.26':
'@aws-sdk/xml-builder@3.972.33':
dependencies:
'@smithy/types': 4.14.2
fast-xml-parser: 5.7.3
'@smithy/types': 4.15.1
tslib: 2.8.1
'@aws/lambda-invoke-store@0.2.4': {}
@@ -9802,8 +9809,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@nodable/entities@2.1.0': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -9967,6 +9972,11 @@ snapshots:
'@smithy/types': 4.14.2
tslib: 2.8.1
'@smithy/core@3.29.1':
dependencies:
'@smithy/types': 4.15.1
tslib: 2.8.1
'@smithy/credential-provider-imds@4.3.5':
dependencies:
'@smithy/core': 3.24.5
@@ -10001,10 +10011,20 @@ snapshots:
'@smithy/types': 4.14.2
tslib: 2.8.1
'@smithy/signature-v4@5.6.2':
dependencies:
'@smithy/core': 3.29.1
'@smithy/types': 4.15.1
tslib: 2.8.1
'@smithy/types@4.14.2':
dependencies:
tslib: 2.8.1
'@smithy/types@4.15.1':
dependencies:
tslib: 2.8.1
'@smithy/util-buffer-from@2.2.0':
dependencies:
'@smithy/is-array-buffer': 2.2.0
@@ -11858,6 +11878,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
electron@33.4.11:
dependencies:
'@electron/get': 2.0.3
'@types/node': 25.5.2
extract-zip: 2.0.1
transitivePeerDependencies:
- supports-color
electron@35.7.5:
dependencies:
'@electron/get': 2.0.3
@@ -12197,18 +12225,6 @@ snapshots:
dependencies:
fast-string-width: 3.0.2
fast-xml-builder@1.2.0:
dependencies:
path-expression-matcher: 1.5.0
xml-naming: 0.1.0
fast-xml-parser@5.7.3:
dependencies:
'@nodable/entities': 2.1.0
fast-xml-builder: 1.2.0
path-expression-matcher: 1.5.0
strnum: 2.2.3
fastq@1.20.1:
dependencies:
reusify: 1.1.0
@@ -13981,8 +13997,6 @@ snapshots:
path-exists@4.0.0: {}
path-expression-matcher@1.5.0: {}
path-is-absolute@1.0.1: {}
path-key@3.1.1: {}
@@ -14859,8 +14873,6 @@ snapshots:
strip-json-comments@3.1.1: {}
strnum@2.2.3: {}
strtok3@6.3.0:
dependencies:
'@tokenizer/token': 0.3.0
@@ -15446,8 +15458,6 @@ snapshots:
xml-name-validator@5.0.0: {}
xml-naming@0.1.0: {}
xml2js@0.6.2:
dependencies:
sax: 1.6.0

View File

@@ -8,7 +8,6 @@ ignoredBuiltDependencies:
- ssh2
onlyBuiltDependencies:
- '@homebridge/node-pty-prebuilt-multiarch'
- electron
- esbuild
- koffi
- protobufjs

View File

@@ -0,0 +1,198 @@
# Fusion Desktop Release Issues — Field Report
**Date:** 2026-07-03
**Reporter:** Hermes Ouroboros / Automata Intelligentsia
**Host:** Windows 11 (build 26200)
**Fusion versions observed:** 0.51.0 (last known good), 0.52.0 (current desktop release with regressions)
**Test environment:** `C:\Users\drewd\Tools\fusion-latest` (local source checkout), `npm install -g @runfusion/fusion` published package, and the v1.1.0 standalone desktop wrapper.
This document collects the issues we found while trying to run the official Fusion desktop build on Windows, what we attempted, and what we believe the Fusion team needs to address.
---
## Resolution status (PR #1883)
| # | Issue | Status |
|---|-------|--------|
| 1 | `electron` only a devDependency | **Fixed** — `electron` added to `@runfusion/fusion` runtime deps; lockfile synced. |
| 2 | Ancestor-dir walk crashes on unrelated JSON | **Already handled** — the desktop launcher uses `process.cwd()` (CLI) / `$HOME` (Electron main), never an ancestor walk (`desktop.ts:175`, `main.ts` `resolveLocalRuntimeRoot`), and every JSON parse on the shared discovery path is already `try/catch`-guarded, so unrelated JSON no longer throws. |
| 3 | Manage Projects opens Settings | **Fixed** — `handleViewAllProjects` now resets `taskView` to `command-center`. |
| 4 | Windows Terminal "Help" dialogs | **Fixed** — frontend auto-create of the first terminal tab is skipped on Windows. |
| 5 | Packaged build can miss `preload`/assets | **Fixed** — `scripts/build.ts` now verifies `main.js`, `preload.js`, and `client/index.html` exist in both `dist/` and the staged `deploy/dist/` before packaging, failing the build otherwise. (In this repo the preload ships as `dist/preload.js` inside `app.asar`, not `preload.cjs`.) |
| 6 | Desktop port drift / collision | **Already handled** — the embedded runtime binds an ephemeral port (`app.listen(0)`, `desktop.ts:78`), so fixed-port collision is structurally impossible; the CLI passes it via `FUSION_SERVER_PORT` and the desktop reuses it instead of double-binding (Issue 9), and a single-instance lock (`deep-link.ts`) quits a duplicate window. A fixed 9119/8643 would *reintroduce* collisions, so we deliberately did not pin one. |
| 7 | GPU/sandbox instability on Windows | **Fixed** — GPU/sandbox-disabling flags applied on Windows only (`os.platform() === "win32"`); macOS/Linux keep hardware acceleration and the sandbox. |
| 8 | User-data not isolated | **Fixed** — desktop profile relocated under `~/.fusion/desktop-user-data`, with a one-time copy migrating an existing operator's previous profile (window geometry/session) so upgrades don't lose state. |
| 9 | Desktop doesn't reuse a running server | **Fixed** — when `FUSION_SERVER_PORT` is set the desktop attaches to the CLI's dashboard instead of spawning an embedded runtime. |
Recommendation #2 (verify packaged Windows layout before publishing) is now **enforced in CI**: `desktop-windows.yml` asserts the shipped `app.asar` contains `dist/main.js`, `dist/preload.js`, and `dist/client/index.html`, on top of `scripts/build.ts`'s pre-package staging check.
Remaining as future team work (deliberately not attempted here): recommendation #1's full GUI-launch smoke (launch `Fusion.exe` and assert the window is visible/responsive) — reliably asserting a rendered Electron window on a CI runner is flaky, which the project's standing anti-flaky rule forbids adding; and the port/process auditing docs for Issue 6.
---
## Issue 1: `fusion desktop` fails to launch on Windows 0.52.0
### Symptom
Running `fusion desktop` from the published npm package immediately errors out with a dynamic require / Electron binary resolution failure. The CLI cannot find `electron` because the published `@runfusion/fusion` package lists `electron` only as a `devDependency`, not a runtime dependency.
### What we tried
1. Installed `@runfusion/fusion@latest` globally.
2. Ran `fusion desktop --no-auth` from a project directory.
3. Observed that `require('electron')` fails because Electron is not installed alongside the published CLI.
4. Checked `packages/cli/package.json` in the source repo: `electron` is under `devDependencies`.
### Proposed fix
Add `electron` to the `dependencies` (or `optionalDependencies`) of `@runfusion/fusion` so that a global npm install pulls in the Electron binary required by `packages/cli/src/commands/desktop.ts`.
---
## Issue 2: Native desktop build walks ancestor directories and fails on unrelated workspace JSON
### Symptom
When `fusion desktop` does manage to start the native Electron build, the launcher walks up the directory tree looking for a Fusion workspace. It can land on an unrelated ancestor directory (e.g., `C:\Users\drewd\Tools`) and fail because it parses JSON files in sibling or parent workspaces that are not valid Fusion project assets.
### What we tried
1. Ran `fusion desktop` from `C:\Users\drewd\Tools\fusion-latest`.
2. The launcher searched ancestor directories instead of using the current working directory as the project root.
3. It then choked on invalid JSON in assets that were never intended to be loaded as Fusion metadata.
### Proposed fix
- Restrict workspace discovery to the current working directory or a user-selected/project-configured root.
- Treat missing/invalid JSON as non-fatal during workspace discovery; log and continue rather than crashing the launcher.
- Add a CLI flag or config key to pin the project root explicitly.
---
## Issue 3: Manage Projects button opens Settings instead of the project overview
### Symptom
In the dashboard header, clicking **Manage Projects** lands on the **Settings** page instead of the project list/overview.
### Root cause
`handleViewAllProjects` in `packages/dashboard/app/hooks/useProjectActions.ts` resets `viewMode` to `"overview"` and clears the current project, but it leaves `taskView` unchanged. `MainContent` checks `taskView === "settings"` before the `viewMode === "overview"` branch, so any previously selected settings view is rendered instead of `ProjectOverview`.
### What we tried
- Traced the routing through `App.tsx`, `useProjectActions.ts`, `useViewState.ts`, and `MainContent.tsx`.
- Threaded `setTaskView` into `useProjectActions` and reset `taskView` to `"command-center"` when leaving a project.
### Proposed fix
Apply the same fix as PR #1882: make `handleViewAllProjects` reset `taskView` to the overview landing view so the settings branch cannot shadow the overview branch.
---
## Issue 4: Windows Terminal native "Help" version dialogs on dashboard load / Settings
### Symptom
On Windows, opening the dashboard (and especially the Settings page) produces two native Windows message boxes titled **Help**, showing:
```
Windows Terminal
1.24.11321.0
```
### Root cause
The dashboard’s `useTerminalSessions` hook auto-creates the first terminal tab once session validation completes. On Windows, spawning a PTY can end up invoking `wt.exe` (Windows Terminal) or otherwise triggering its built-in version/help dialog. The backend `terminal-service.ts` already has an FNXC guard (FNXC:WindowsTerminalStartup) to avoid probing `wt.exe`, but the frontend auto-create path still triggers a PTY spawn on Windows before the user has asked for a terminal.
### What we tried
- Confirmed `terminal-service.ts` skips Windows Terminal for `SHELL` on `win32`.
- Confirmed tests already assert `wt.exe` should not be selected.
- Disabled the auto-create path on Windows in `useTerminalSessions.ts` so the failure cannot recur automatically. Manual terminal creation still works and surfaces the inline error UI.
### Proposed fix
Merge the frontend guard from PR #1882, or move the platform check server-side so no terminal session is auto-created for Windows users unless the platform has a verified embedded shell.
---
## Issue 5: `fusion desktop` native build uses the wrong working directory for `preload.cjs` and other Electron assets
### Symptom
The packaged native desktop build can fail to load `preload.cjs` because the packaged app looks under `release/win-unpacked/resources/app.asar.unpacked/electron/`, but that path may be incomplete after `npm run dist`.
### What we tried
- Extracted the published v1.0.0 wrapper source and compared it to the source tree.
- Found that `apps/desktop/electron/preload.cjs` exists in source but is missing in the packaged layout on some installs.
- Confirmed the workaround: copy `preload.cjs` into the missing unpacked location.
### Proposed fix
- Add a packaging verification step that asserts `preload.cjs` is present in the expected unpacked path before publishing.
- Consider bundling the preload script into the main asar so the path is deterministic.
- Document the Windows packaging layout and the required Electron files.
---
## Issue 6: Native desktop build runs on port 9119/8643 but conflicts with dashboard and other instances
### Symptom
On Windows, the dashboard backend can end up on a non-standard port or collide with another running dashboard instance (e.g., 9120, 7380–7385). The packaged desktop also expects gateway 8643 and dashboard 9119 per the docs, but the actual port can drift.
### What we tried
- Used `Get-NetTCPConnection` to map ports to process names because `ps`/`netstat` in MSYS mis-enumerate Electron/pythonw/WSL processes.
- Found that duplicate dashboard processes can occur when system Python and venv Python both try to start on the same port.
### Proposed fix
- Lock down the desktop build to deterministic ports with a port-file lock or named mutex on Windows.
- Show a clear error when another Fusion desktop/dashboard is already running instead of silently binding elsewhere.
- Document the canonical Windows ports and how to audit them.
---
## Issue 7: GPU/sandbox rendering issues on Windows Electron
### Symptom
The native desktop window can be blank, flicker, or fail to render on some Windows GPUs. We observed this both with the wrapper and the native desktop build.
### What we tried
- Added Electron flags to disable GPU and sandbox in the wrapper and native launch path:
- `--disable-gpu`
- `--disable-gpu-compositing`
- `--disable-gpu-sandbox`
- `--disable-software-rasterizer`
- `--no-sandbox`
- These flags improved stability in the wrapper.
### Proposed fix
- Expose these flags as the default on Windows, or make them configurable in the dashboard settings.
- Detect GPU process crashes and automatically fall back to software rendering with a toast notification.
---
## Issue 8: Desktop user-data path is not isolated / collides with other Electron apps
### Symptom
Crash dumps, caches, and local storage from the Fusion desktop can end up in a generic Electron user-data directory or collide with other Electron apps using the same defaults.
### What we tried
- Set `app.setPath("userData", "...")` to `~/.fusion/desktop-user-data` and sub-paths for cache/crashes in the wrapper.
### Proposed fix
- Apply the same isolation in the native desktop build so sessions, logs, and crash data live under `~/.fusion/` and are easy to inspect or reset without affecting other Electron apps.
---
## Issue 9: CLI desktop command does not reuse an already-running dashboard server
### Symptom
Running `fusion desktop` while `fusion dashboard` (or the wrapper) is already serving on 4040 starts a second process rather than connecting to the existing one.
### What we tried
- Modified `packages/desktop/src/main.ts` to detect `FUSION_SERVER_PORT` and skip `startLocalRuntimeOnce` when the CLI already started a server.
### Proposed fix
- Add a stable port probe / heartbeat before starting the Electron runtime.
- If a dashboard is already running on the expected port, load that URL instead of spawning another engine.
---
## General recommendations for the Fusion team
1. **Windows CI:** Add a Windows build step that runs `fusion desktop` in a clean VM and asserts the window title is visible and responsive.
2. **Release tests:** Before tagging a desktop release, verify the packaged `release/win-unpacked` layout has all required Electron assets (`preload.cjs`, etc.).
3. **Dependency audit:** Move `electron` out of `devDependencies` in the published CLI package, or document that users must install it separately.
4. **Field test with wrapper users:** The wrapper at `https://github.com/Automata-intelligentsia/fusion-desktop-windows/releases/tag/v1.1.0` is a proven workaround; consider adopting its launch model (CLI server + Electron shell) as an official fallback until the native desktop build is stabilized on Windows.
---
## Related PRs
- `Runfusion/Fusion#1882` — dashboard routing and Windows Terminal popup fix.
- `Automata-intelligentsia/fusion-desktop-windows#v1.1.0` — standalone Windows wrapper that works around the native desktop regressions.