fix(desktop): Change Launch Mode menu performs reset entirely in main
Replace the renderer-event approach (which silently failed when the renderer-side listener wasn't yet registered) with an onChangeLaunchMode callback wired through AppMenuOptions. The callback runs resetLaunchModeAndReload in main: writes shell settings to clear the chosen mode, stops the embedded runtime, then navigates the window directly to the renderer entrypoint with no cached query params so the launch gate re-prompts. Also surface the same flow from the dashboard's BackendConnectionErrorPage: when running inside the desktop shell, the "Can't reach the Fusion backend" page now offers a "Change Launch Mode…" button alongside Retry, so a user who chose a broken backend isn't stuck without the Electron menubar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,12 +7,31 @@ interface BackendConnectionErrorPageProps {
|
|||||||
onManageConnection?: () => void;
|
onManageConnection?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDesktopShell(): boolean {
|
||||||
|
if (typeof window === "undefined") return false;
|
||||||
|
return typeof window.fusionShell?.resetDesktopMode === "function";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeLaunchMode(): Promise<void> {
|
||||||
|
const shell = typeof window !== "undefined" ? window.fusionShell : undefined;
|
||||||
|
try {
|
||||||
|
await shell?.resetDesktopMode?.();
|
||||||
|
} catch {
|
||||||
|
// Best-effort; still strip query params and reload.
|
||||||
|
}
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete("serverBaseUrl");
|
||||||
|
url.searchParams.delete("shellMode");
|
||||||
|
window.location.replace(url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
export function BackendConnectionErrorPage({
|
export function BackendConnectionErrorPage({
|
||||||
errorMessage,
|
errorMessage,
|
||||||
isRetrying,
|
isRetrying,
|
||||||
onRetry,
|
onRetry,
|
||||||
onManageConnection,
|
onManageConnection,
|
||||||
}: BackendConnectionErrorPageProps) {
|
}: BackendConnectionErrorPageProps) {
|
||||||
|
const showChangeLaunchMode = isDesktopShell();
|
||||||
return (
|
return (
|
||||||
<div className="project-overview-empty" role="alert" aria-live="polite">
|
<div className="project-overview-empty" role="alert" aria-live="polite">
|
||||||
<h2>Can't reach the Fusion backend</h2>
|
<h2>Can't reach the Fusion backend</h2>
|
||||||
@@ -24,6 +43,11 @@ export function BackendConnectionErrorPage({
|
|||||||
<button type="button" className="btn btn-primary" onClick={onRetry} disabled={isRetrying}>
|
<button type="button" className="btn btn-primary" onClick={onRetry} disabled={isRetrying}>
|
||||||
{isRetrying ? "Retrying…" : "Retry Connection"}
|
{isRetrying ? "Retrying…" : "Retry Connection"}
|
||||||
</button>
|
</button>
|
||||||
|
{showChangeLaunchMode && (
|
||||||
|
<button type="button" className="btn" onClick={() => void changeLaunchMode()}>
|
||||||
|
Change Launch Mode…
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{onManageConnection && (
|
{onManageConnection && (
|
||||||
<button type="button" className="btn" onClick={onManageConnection}>
|
<button type="button" className="btn" onClick={onManageConnection}>
|
||||||
Manage Connection
|
Manage Connection
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
import { setupTray } from "./tray.js";
|
import { setupTray } from "./tray.js";
|
||||||
import { getRendererUrl, getRendererFilePath, isUrlRenderer } from "./renderer.js";
|
import { getRendererUrl, getRendererFilePath, isUrlRenderer } from "./renderer.js";
|
||||||
import { LocalRuntimeManager } from "./local-runtime.js";
|
import { LocalRuntimeManager } from "./local-runtime.js";
|
||||||
import { readShellSettings } from "./shell-settings.js";
|
import { readShellSettings, writeShellSettings } from "./shell-settings.js";
|
||||||
|
|
||||||
// Re-export for backward compatibility
|
// Re-export for backward compatibility
|
||||||
export { IS_DEVELOPMENT } from "./renderer.js";
|
export { IS_DEVELOPMENT } from "./renderer.js";
|
||||||
@@ -70,6 +70,44 @@ export function getCurrentDesktopLaunchMode(): DesktopLaunchMode {
|
|||||||
return currentDesktopLaunchMode;
|
return currentDesktopLaunchMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resetLaunchModeAndReload(window: BrowserWindow): Promise<void> {
|
||||||
|
console.log("[desktop/main] resetLaunchModeAndReload start");
|
||||||
|
try {
|
||||||
|
const settings = await readShellSettings();
|
||||||
|
settings.desktopMode = null;
|
||||||
|
settings.hasCompletedModeSelection = false;
|
||||||
|
await writeShellSettings(settings);
|
||||||
|
await saveDesktopLaunchMode("choose");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[desktop/main] Failed to reset shell settings", error);
|
||||||
|
}
|
||||||
|
if (localRuntimeManager) {
|
||||||
|
try {
|
||||||
|
await localRuntimeManager.stopLocal();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[desktop/main] Failed to stop local runtime during reset", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentDesktopLaunchMode = "choose";
|
||||||
|
currentRemoteLaunch = null;
|
||||||
|
localRuntimeStartupAttempted = false;
|
||||||
|
|
||||||
|
// Force a clean reload to the renderer entrypoint without any cached
|
||||||
|
// serverBaseUrl / shellMode query params so the gate re-prompts.
|
||||||
|
try {
|
||||||
|
if (isUrlRenderer()) {
|
||||||
|
console.log("[desktop/main] reloading URL renderer", getRendererUrl());
|
||||||
|
await window.loadURL(getRendererUrl());
|
||||||
|
} else {
|
||||||
|
console.log("[desktop/main] reloading file renderer", getRendererFilePath());
|
||||||
|
await window.loadFile(getRendererFilePath());
|
||||||
|
}
|
||||||
|
console.log("[desktop/main] resetLaunchModeAndReload complete");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[desktop/main] reload failed", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createMainWindow(state?: WindowState, launchTargetUrl?: string): BrowserWindow {
|
export function createMainWindow(state?: WindowState, launchTargetUrl?: string): BrowserWindow {
|
||||||
const hasValidPosition = typeof state?.x === "number" && typeof state?.y === "number";
|
const hasValidPosition = typeof state?.x === "number" && typeof state?.y === "number";
|
||||||
|
|
||||||
@@ -179,6 +217,9 @@ export async function initializeApp(): Promise<void> {
|
|||||||
buildAppMenu({
|
buildAppMenu({
|
||||||
mainWindow: createdWindow,
|
mainWindow: createdWindow,
|
||||||
appName: "Fusion",
|
appName: "Fusion",
|
||||||
|
onChangeLaunchMode: async () => {
|
||||||
|
await resetLaunchModeAndReload(createdWindow);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
tray = new Tray(nativeImage.createEmpty());
|
tray = new Tray(nativeImage.createEmpty());
|
||||||
|
|||||||
@@ -8,20 +8,24 @@ import {
|
|||||||
export interface AppMenuOptions {
|
export interface AppMenuOptions {
|
||||||
mainWindow: BrowserWindow;
|
mainWindow: BrowserWindow;
|
||||||
appName: string;
|
appName: string;
|
||||||
|
onChangeLaunchMode?: () => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildConnectionSubmenu(options: AppMenuOptions): MenuItemConstructorOptions {
|
function buildConnectionSubmenu(options: AppMenuOptions): MenuItemConstructorOptions {
|
||||||
const { mainWindow } = options;
|
|
||||||
return {
|
return {
|
||||||
label: "Connection",
|
label: "Connection",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: "Change Launch Mode…",
|
label: "Change Launch Mode…",
|
||||||
click: () => {
|
click: () => {
|
||||||
// The renderer-side gate listens for this and calls
|
console.log("[desktop/menu] Change Launch Mode clicked");
|
||||||
// shell.resetDesktopMode() before reloading without the cached
|
if (!options.onChangeLaunchMode) {
|
||||||
// serverBaseUrl query param.
|
console.warn("[desktop/menu] onChangeLaunchMode callback not provided");
|
||||||
mainWindow.webContents.send("shell:reset-desktop-mode-request");
|
return;
|
||||||
|
}
|
||||||
|
void Promise.resolve(options.onChangeLaunchMode()).catch((error: unknown) => {
|
||||||
|
console.error("[desktop/menu] onChangeLaunchMode failed", error);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user