From 97f15dee372c6d254389f632928d657a9f89c16d Mon Sep 17 00:00:00 2001 From: NimBold Date: Thu, 27 Aug 2026 22:26:53 +0330 Subject: [PATCH] fix(startup): gate renderer IPC on document load (#37) - Issue #37: Windows 1.4.0 exited with STATUS_STACK_OVERFLOW before showing the main window. - Gate lazy entrypoint imports, Zustand persistence, logger initialization, and console forwarding until WebView2 document load. - Preserve the native Ready reveal and pending-window restore lifecycle across platforms. - Show a closeable startup error if the main entrypoint cannot load. Fixes #37. --- src-tauri/src/lib.rs | 1 - src/main.tsx | 61 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e1f0f2c..4f243e7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -19903,7 +19903,6 @@ pub fn run() { mark_main_window_startup_complete(app_handle); #[cfg(target_os = "windows")] reveal_main_window(app_handle); - #[cfg(not(target_os = "windows"))] restore_pending_main_window(app_handle); } #[cfg(target_os = "macos")] diff --git a/src/main.tsx b/src/main.tsx index b060cfe..ac58c4a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -16,7 +16,22 @@ import { invokeCommand as invoke } from './ipc'; const isPropertiesWindow = getCurrentWindow().label.startsWith('properties-'); -void initLogger(); +// WebView2 can overflow its native call stack when the renderer sends IPC +// during document bootstrapping. Keep all renderer-to-native startup work +// behind the document load boundary, not just the first logger query. This is +// also needed for Zustand persistence, whose module initialization reads the +// native database before React mounts. +const documentLoaded = new Promise((resolve) => { + if (document.readyState === 'complete') { + resolve(); + return; + } + window.addEventListener('load', () => resolve(), { once: true }); +}); + +void documentLoaded.then(() => { + void initLogger(); +}); const serializeConsoleArguments = (values: unknown[]) => values.map(value => { if (value instanceof Error) return `${value.name}: ${value.message}\n${value.stack || ''}`; @@ -36,11 +51,13 @@ const originalConsoleError = console.error.bind(console); const originalConsoleWarn = console.warn.bind(console); console.error = (...values: unknown[]) => { originalConsoleError(...values); - void logError(redactConsoleMessage(serializeConsoleArguments(values))).catch(() => undefined); + const message = redactConsoleMessage(serializeConsoleArguments(values)); + void documentLoaded.then(() => logError(message)).catch(() => undefined); }; console.warn = (...values: unknown[]) => { originalConsoleWarn(...values); - void logWarn(redactConsoleMessage(serializeConsoleArguments(values))).catch(() => undefined); + const message = redactConsoleMessage(serializeConsoleArguments(values)); + void documentLoaded.then(() => logWarn(message)).catch(() => undefined); }; const rootElement = document.getElementById("root"); @@ -75,20 +92,46 @@ const PropertiesStartupFailure = () => ( ); +const MainStartupFailure = () => ( +
+

Firelink could not be loaded.

+ +
+); + const renderMainApp = async () => { if (!rootElement) return; - // Keep the child entrypoint isolated from the main application module. App - // imports the persistent Zustand stores, whose module initialization issues - // main-window-only IPC commands. Loading it in a Properties child creates a - // second persistence owner and can race the bridge handshake. - const RootComponent = (await import('./App')).default; - renderRoot(RootComponent); + await documentLoaded; + + try { + // Keep the child entrypoint isolated from the main application module. App + // imports the persistent Zustand stores, whose module initialization issues + // main-window-only IPC commands. Loading it in a Properties child creates a + // second persistence owner and can race the bridge handshake. + const RootComponent = (await import('./App')).default; + renderRoot(RootComponent); + } catch (error) { + console.error('Failed to initialize Firelink:', error); + renderRoot(MainStartupFailure); + } }; const renderPropertiesApp = async () => { if (!rootElement) return; + await documentLoaded; + try { // Properties starts with the synchronous English catalog and changes locale // after its first paint. Waiting for a lazy locale chunk here delays the