From b808a5dccdddd0635bab24302084217ba2303b37 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Thu, 9 Apr 2026 16:49:26 -0500 Subject: [PATCH] Centralize desktop renderer URL resolution - Add a shared helper for dev and packaged renderer URLs - Use it for main and pop-out windows to preserve query params - Update the Electron history comment for the desktop shell origin --- apps/desktop/src/main.ts | 27 +++++++----- apps/desktop/src/rendererUrl.test.ts | 61 ++++++++++++++++++++++++++++ apps/desktop/src/rendererUrl.ts | 35 ++++++++++++++++ apps/web/src/main.tsx | 2 +- 4 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 apps/desktop/src/rendererUrl.test.ts create mode 100644 apps/desktop/src/rendererUrl.ts diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 947b147a2..43492133a 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -34,6 +34,7 @@ import { RotatingFileSink } from "@okcode/shared/logging"; import { showDesktopConfirmDialog } from "./confirmDialog"; import { createEmptyTabsState } from "./preview"; import { DesktopPreviewController } from "./previewController"; +import { resolveDesktopRendererUrl } from "./rendererUrl"; import { syncShellEnvironment } from "./syncShellEnvironment"; import { getAutoUpdateDisabledReason, shouldBroadcastDownloadProgress } from "./updateState"; import { @@ -1501,13 +1502,14 @@ function registerIpcHandlers(): void { // Load the same app URL but with a query parameter so the renderer // can detect pop-out mode and render a simplified UI if needed. - if (isDevelopment && process.env.VITE_DEV_SERVER_URL) { - void popOut.loadURL(`${process.env.VITE_DEV_SERVER_URL}?popout=true`); - } else { - void popOut.loadFile(Path.join(__dirname, "../../web/dist/index.html"), { - query: { popout: "true" }, - }); - } + void popOut.loadURL( + resolveDesktopRendererUrl({ + isDevelopment, + devServerUrl: process.env.VITE_DEV_SERVER_URL, + scheme: DESKTOP_SCHEME, + query: { popout: true }, + }), + ); }); ipcMain.removeHandler(PREVIEW_POP_IN_CHANNEL); @@ -1600,11 +1602,16 @@ function createWindow(): BrowserWindow { window.show(); }); + void window.loadURL( + resolveDesktopRendererUrl({ + isDevelopment, + devServerUrl: process.env.VITE_DEV_SERVER_URL, + scheme: DESKTOP_SCHEME, + }), + ); + if (isDevelopment) { - void window.loadURL(process.env.VITE_DEV_SERVER_URL as string); window.webContents.openDevTools({ mode: "detach" }); - } else { - void window.loadURL(`${DESKTOP_SCHEME}://app/index.html`); } window.on("closed", () => { diff --git a/apps/desktop/src/rendererUrl.test.ts b/apps/desktop/src/rendererUrl.test.ts new file mode 100644 index 000000000..f2171e66f --- /dev/null +++ b/apps/desktop/src/rendererUrl.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from "vitest"; + +import { resolveDesktopRendererUrl } from "./rendererUrl"; + +describe("resolveDesktopRendererUrl", () => { + it("builds the packaged renderer URL from the desktop protocol", () => { + expect( + resolveDesktopRendererUrl({ + isDevelopment: false, + scheme: "okcode", + }), + ).toBe("okcode://app/index.html"); + }); + + it("adds query parameters for packaged pop-out windows", () => { + expect( + resolveDesktopRendererUrl({ + isDevelopment: false, + scheme: "okcode", + query: { + popout: true, + }, + }), + ).toBe("okcode://app/index.html?popout=true"); + }); + + it("adds query parameters to the dev server URL", () => { + expect( + resolveDesktopRendererUrl({ + isDevelopment: true, + devServerUrl: "http://127.0.0.1:5173/", + scheme: "okcode", + query: { + popout: true, + }, + }), + ).toBe("http://127.0.0.1:5173/?popout=true"); + }); + + it("preserves existing dev server search params", () => { + expect( + resolveDesktopRendererUrl({ + isDevelopment: true, + devServerUrl: "http://127.0.0.1:5173/?client=desktop", + scheme: "okcode", + query: { + popout: true, + }, + }), + ).toBe("http://127.0.0.1:5173/?client=desktop&popout=true"); + }); + + it("requires a dev server URL in development mode", () => { + expect(() => + resolveDesktopRendererUrl({ + isDevelopment: true, + scheme: "okcode", + }), + ).toThrow("VITE_DEV_SERVER_URL is required when resolving a development renderer URL."); + }); +}); diff --git a/apps/desktop/src/rendererUrl.ts b/apps/desktop/src/rendererUrl.ts new file mode 100644 index 000000000..be2195ed4 --- /dev/null +++ b/apps/desktop/src/rendererUrl.ts @@ -0,0 +1,35 @@ +export interface DesktopRendererUrlInput { + readonly isDevelopment: boolean; + readonly devServerUrl?: string | undefined; + readonly scheme: string; + readonly query?: Record | undefined; +} + +function applyQuery(url: URL, query: DesktopRendererUrlInput["query"]): URL { + if (!query) { + return url; + } + + for (const [key, value] of Object.entries(query)) { + if (value === null || value === undefined) { + url.searchParams.delete(key); + continue; + } + url.searchParams.set(key, String(value)); + } + + return url; +} + +export function resolveDesktopRendererUrl(input: DesktopRendererUrlInput): string { + if (input.isDevelopment) { + const devServerUrl = input.devServerUrl; + if (!devServerUrl) { + throw new Error("VITE_DEV_SERVER_URL is required when resolving a development renderer URL."); + } + + return applyQuery(new URL(devServerUrl), input.query).toString(); + } + + return applyQuery(new URL(`${input.scheme}://app/index.html`), input.query).toString(); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 4a4aa71b2..443a8b8e5 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -11,7 +11,7 @@ import { getRouter } from "./router"; import { APP_DISPLAY_NAME } from "./branding"; import { DiffWorkerPoolProvider } from "./components/DiffWorkerPoolProvider"; -// Electron loads the app from a file-backed shell, so hash history avoids path resolution issues. +// Electron loads the app from a desktop shell origin, so hash history avoids path resolution issues. const history = isElectron ? createHashHistory() : createBrowserHistory(); const router = getRouter(history);