From b99d939294a3ebd03065718cc431db046c3c8438 Mon Sep 17 00:00:00 2001 From: khaira777 <777gurkirat@gmail.com> Date: Tue, 6 Oct 2026 13:17:07 -0400 Subject: [PATCH 1/3] feat(desktop): adopt Electron 44 window state persistence and child-process error codes With Electron 44 now the runtime (PR #963), use what it provides natively instead of leaving these gaps: the main window persists size, position, maximized and fullscreen state across launches via name flo-main and windowStatePersistence, rather than always opening at the hardcoded 1400x900. child-process-gone now records details.systemErrorCode in the log and crash telemetry, so launch failures (ENOENT, permission errors, Windows error codes) are diagnosable instead of a bare non-zero exit. recoverFailedWindow destroys the failed window before recreating it, so the unique window name and GPU resources are released first. createMainWindow keeps accepting a trailing TitleBarMode string for existing call sites and now also accepts an options override object. The sleep/wake 1px repaint nudge is kept as defense-in-depth, and title-bar mode behavior is unchanged. --- main/index.ts | 22 +++++++++++++++++++--- main/window-options.ts | 17 +++++++++++++++-- tests/platform-titlebar-runtime-probe.cjs | 1 + tests/titlebar-window-options.test.ts | 2 ++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/main/index.ts b/main/index.ts index c06b5249d..86a0f64f4 100644 --- a/main/index.ts +++ b/main/index.ts @@ -426,8 +426,8 @@ function recoverFailedWindow(failedWindow: BrowserWindow): void { } windowLoadRecoveryAttempted = true; try { - createWindow(); if (!failedWindow.isDestroyed()) failedWindow.destroy(); + createWindow(); } catch (error) { log.error('[Window] Window recreation failed:', error); requestRuntimeRelaunchOnce('window-load-recovery-create-failed'); @@ -866,8 +866,23 @@ function registerPowerMonitorRecovery(): void { // Track child process crashes and trigger GPU fallback relaunch on GPU crash. function registerChildProcessCrashTelemetry(): void { app.on('child-process-gone', (_event, details) => { - log.error('[Process] Child process gone:', details.type, details.reason, details.exitCode); - console.error('[Process] Child process gone:', details.type, details.reason, details.exitCode); + const systemErrorCode = 'systemErrorCode' in details + ? (details as { systemErrorCode?: number }).systemErrorCode + : undefined; + log.error( + '[Process] Child process gone:', + details.type, + details.reason, + details.exitCode, + systemErrorCode !== undefined ? `systemErrorCode=${systemErrorCode}` : '', + ); + console.error( + '[Process] Child process gone:', + details.type, + details.reason, + details.exitCode, + systemErrorCode !== undefined ? `systemErrorCode=${systemErrorCode}` : '', + ); // Ignore normal shutdown/clean-exit signals. const isGpuFailure = details.type === 'GPU' && details.reason !== 'clean-exit' @@ -882,6 +897,7 @@ function registerChildProcessCrashTelemetry(): void { type: details.type, reason: details.reason, exitCode: details.exitCode, + systemErrorCode, serviceName: details.serviceName, consecutiveGpuCrashCount: isGpuFailure ? consecutiveGpuCrashes : undefined, }).catch((error) => { diff --git a/main/window-options.ts b/main/window-options.ts index 8e13ed69c..88102698d 100644 --- a/main/window-options.ts +++ b/main/window-options.ts @@ -43,11 +43,23 @@ export function createMainWindow( preload: string, platform: NodeJS.Platform = process.platform, isDarkOrTitleBarMode: boolean | TitleBarMode = false, - titleBarMode: TitleBarMode = typeof isDarkOrTitleBarMode === 'string' ? isDarkOrTitleBarMode : 'native-overlay', + titleBarModeOrOptions?: TitleBarMode | Partial, + options?: Partial, ): BrowserWindow { const isDark = typeof isDarkOrTitleBarMode === 'boolean' ? isDarkOrTitleBarMode : false; - const resolvedTitleBarMode = typeof isDarkOrTitleBarMode === 'string' ? isDarkOrTitleBarMode : titleBarMode; + const resolvedTitleBarMode: TitleBarMode = + typeof titleBarModeOrOptions === 'string' + ? titleBarModeOrOptions + : typeof isDarkOrTitleBarMode === 'string' + ? isDarkOrTitleBarMode + : 'native-overlay'; + const resolvedOptions = + typeof titleBarModeOrOptions === 'object' && titleBarModeOrOptions !== null + ? titleBarModeOrOptions + : options; return new BrowserWindowConstructor({ + name: 'flo-main', + windowStatePersistence: true, width: 1400, height: 900, minWidth: 1024, @@ -70,6 +82,7 @@ export function createMainWindow( sandbox: false, }, show: false, + ...resolvedOptions, }); } diff --git a/tests/platform-titlebar-runtime-probe.cjs b/tests/platform-titlebar-runtime-probe.cjs index de1ec183b..29a11b30e 100644 --- a/tests/platform-titlebar-runtime-probe.cjs +++ b/tests/platform-titlebar-runtime-probe.cjs @@ -132,6 +132,7 @@ async function main() { path.join(__dirname, '_probe-preload-noop.js'), platform, resolved, + { name: 'observed-mac-window' }, ); const trafficLightPosition = observedMacOptions?.trafficLightPosition; record( diff --git a/tests/titlebar-window-options.test.ts b/tests/titlebar-window-options.test.ts index 6f7c3e41e..1a80027f8 100644 --- a/tests/titlebar-window-options.test.ts +++ b/tests/titlebar-window-options.test.ts @@ -29,6 +29,8 @@ const windowsMainWindow = createMainWindow(FakeBrowserWindow as any, '/tmp/prelo const linuxMainWindow = createMainWindow(FakeBrowserWindow as any, '/tmp/preload.js', 'linux'); const darkMainWindow = createMainWindow(FakeBrowserWindow as any, '/tmp/preload.js', 'darwin', true); +assert.equal(macMainWindow.options.name, 'flo-main'); +assert.equal(macMainWindow.options.windowStatePersistence, true); assert.equal(macMainWindow.options.titleBarStyle, 'hiddenInset'); assert.equal(windowsMainWindow.options.titleBarStyle, 'hidden'); assert.equal(linuxMainWindow.options.titleBarStyle, 'hidden'); From 219110c336f52708fedc23e129325f998efab39a Mon Sep 17 00:00:00 2001 From: khaira777 <777gurkirat@gmail.com> Date: Tue, 6 Oct 2026 13:41:00 -0400 Subject: [PATCH 2/3] no-mistakes(document): Document persisted main-window sizing behavior --- docs/architecture/desktop-build.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/architecture/desktop-build.md b/docs/architecture/desktop-build.md index 6eb41abe6..d841b0f6c 100644 --- a/docs/architecture/desktop-build.md +++ b/docs/architecture/desktop-build.md @@ -76,8 +76,9 @@ drive-by cleanup. ## The native title bar -The main window is 1400x900 with a minimum of 1024x768, is created hidden, and is shown only once -its renderer reports ready (see the readiness contract below). +The main window defaults to 1400x900 with a minimum of 1024x768, persists its window state between +launches, is created hidden, and is shown only once its renderer reports ready (see the readiness +contract below). ### Mode resolution From 6a479f8800610d0ac35a53f29eb3624b6f7816df Mon Sep 17 00:00:00 2001 From: khaira777 <777gurkirat@gmail.com> Date: Tue, 6 Oct 2026 13:58:21 -0400 Subject: [PATCH 3/3] fix(window): keep generated webPreferences when merging option overrides createMainWindow spread the caller options over the generated window options, so an options object carrying webPreferences replaced the generated object and silently dropped the preload along with contextIsolation, nodeIntegration and sandbox. Overriding a single preference would have left the renderer without window.electronAPI. webPreferences is now destructured out of the overrides and merged ahead of the enforced preload and isolation settings, while the remaining options spread as before. Regression assertions cover both the merged preference and a non-webPreferences override. --- main/window-options.ts | 6 +++++- tests/titlebar-window-options.test.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/main/window-options.ts b/main/window-options.ts index 88102698d..5e70f18bf 100644 --- a/main/window-options.ts +++ b/main/window-options.ts @@ -57,6 +57,9 @@ export function createMainWindow( typeof titleBarModeOrOptions === 'object' && titleBarModeOrOptions !== null ? titleBarModeOrOptions : options; + // Merge webPreferences instead of replacing it, so overriding one preference + // cannot drop the generated preload or the isolation settings it enables. + const { webPreferences: webPreferencesOverrides, ...windowOptions } = resolvedOptions ?? {}; return new BrowserWindowConstructor({ name: 'flo-main', windowStatePersistence: true, @@ -76,13 +79,14 @@ export function createMainWindow( : {}), ...(platform === 'darwin' ? { trafficLightPosition: MAC_TRAFFIC_LIGHT_POSITION } : {}), webPreferences: { + ...(webPreferencesOverrides ?? {}), preload, contextIsolation: true, nodeIntegration: false, sandbox: false, }, show: false, - ...resolvedOptions, + ...windowOptions, }); } diff --git a/tests/titlebar-window-options.test.ts b/tests/titlebar-window-options.test.ts index 1a80027f8..08d065c7d 100644 --- a/tests/titlebar-window-options.test.ts +++ b/tests/titlebar-window-options.test.ts @@ -50,6 +50,22 @@ assert.equal(macMainWindow.options.webPreferences.nodeIntegration, false); assert.equal(macMainWindow.options.webPreferences.sandbox, false); assert.equal('frame' in macMainWindow.options, false, 'the native-controls design does not remove the window frame'); +// An options override merges webPreferences: it cannot replace the generated +// preload and isolation settings, and its other keys still apply. +const overriddenOptionsWindow = createMainWindow( + FakeBrowserWindow as any, + '/tmp/preload.js', + 'darwin', + false, + { width: 1200, webPreferences: { backgroundThrottling: false } }, +); +assert.equal(overriddenOptionsWindow.options.width, 1200); +assert.equal(overriddenOptionsWindow.options.webPreferences.preload, '/tmp/preload.js'); +assert.equal(overriddenOptionsWindow.options.webPreferences.contextIsolation, true); +assert.equal(overriddenOptionsWindow.options.webPreferences.nodeIntegration, false); +assert.equal(overriddenOptionsWindow.options.webPreferences.sandbox, false); +assert.equal(overriddenOptionsWindow.options.webPreferences.backgroundThrottling, false); + // macOS traffic lights are vertically centered in the 40px bar (buttons are 12px tall). assert.deepEqual(macMainWindow.options.trafficLightPosition, { x: 16, y: 14 }); assert.equal('trafficLightPosition' in windowsMainWindow.options, false, 'trafficLightPosition is macOS-only');