From 28ac6686ba947a10bea62e96afdd2e48da2fd307 Mon Sep 17 00:00:00 2001 From: Ammar Date: Mon, 13 Oct 2025 15:12:08 -0500 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A4=96=20Fix=20splash=20screen=20whit?= =?UTF-8?q?e=20flash=20and=20improve=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem - Splash screen sometimes showed white flash during startup - No user-visible error messages when startup fails - Users see black screen with no feedback if app fails to start ## Solution ### 1. Fix White Flash Added `backgroundColor: '#1f1f1f'` to splash window configuration to match the splash HTML background color (hsl(0 0% 12%)). This eliminates any white flash if HTML loads slowly. ### 2. Comprehensive Error Handling Wrapped entire `app.whenReady()` in try/catch to handle all startup failures: - Close splash screen on error - Show error dialog with full error message and stack trace - Quit app gracefully after showing error - Ensures users always see startup failures instead of silent black screen ### 3. Update Documentation - Corrected service load time comment (~100ms, not ~6-13s) - Added note that spinner may freeze briefly during service loading - This is acceptable since splash still provides visual feedback ## Impact - ✅ No white flash - consistent dark background from first frame - ✅ Better error UX - users see what went wrong during startup - ✅ More accurate documentation of startup timing ## Related - Builds on PR #226 (initial splash screen) - Complements PR #230 (E2E test fixes) - See issue #231 for future tree-shaking optimization The backgroundColor fix and error handling are simple, reliable improvements that don't add complexity. Future performance gains should come from loading less code (tree-shaking) rather than moving work to worker threads. --- src/main.ts | 94 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/src/main.ts b/src/main.ts index 1bb5e09cf..dc8eafd8e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -210,6 +210,7 @@ async function showSplashScreen() { height: 300, frame: false, transparent: false, + backgroundColor: "#1f1f1f", // Match splash HTML background (hsl(0 0% 12%)) - prevents white flash alwaysOnTop: true, center: true, resizable: false, @@ -255,8 +256,9 @@ function closeSplashScreen() { /** * Load backend services (Config, IpcMain, AI SDK, tokenizer) * - * Heavy initialization (~6-13s) happens here while splash is visible. - * This is the slow part that delays app startup. + * Heavy initialization (~100ms) happens here while splash is visible. + * Note: Spinner may freeze briefly during this phase. This is acceptable since + * the splash still provides visual feedback that the app is loading. */ async function loadServices(): Promise { if (config && ipcMain && loadTokenizerModulesFn) return; // Already loaded @@ -267,7 +269,7 @@ async function loadServices(): Promise { /* eslint-disable no-restricted-syntax */ // Dynamic imports are justified here for performance: // - IpcMain transitively imports the entire AI SDK (ai, @ai-sdk/anthropic, etc.) - // - These are large modules (~6-13s load time) that would block splash from appearing + // - These are large modules (~100ms load time) that would block splash from appearing // - Loading happens once, then cached const [ { Config: ConfigClass }, @@ -357,45 +359,65 @@ function createWindow() { // Only setup app handlers if we got the lock if (gotTheLock) { void app.whenReady().then(async () => { - console.log("App ready, creating window..."); + try { + console.log("App ready, creating window..."); + + // Install React DevTools in development + if (!app.isPackaged && installExtension && REACT_DEVELOPER_TOOLS) { + try { + const extension = await installExtension(REACT_DEVELOPER_TOOLS, { + loadExtensionOptions: { allowFileAccess: true }, + }); + console.log(`✅ React DevTools installed: ${extension.name} (id: ${extension.id})`); + } catch (err) { + console.log("❌ Error installing React DevTools:", err); + } + } - // Install React DevTools in development - if (!app.isPackaged && installExtension && REACT_DEVELOPER_TOOLS) { - try { - const extension = await installExtension(REACT_DEVELOPER_TOOLS, { - loadExtensionOptions: { allowFileAccess: true }, + createMenu(); + + // Three-phase startup: + // 1. Show splash immediately (<100ms) and wait for it to load + // 2. Load services while splash visible (fast - ~100ms) + // 3. Create window and start loading content (splash stays visible) + // 4. When window ready-to-show: close splash, show main window + // + // Skip splash in E2E tests to avoid app.firstWindow() grabbing the wrong window + if (!isE2ETest) { + await showSplashScreen(); // Wait for splash to actually load + } + await loadServices(); + createWindow(); + // Note: splash closes in ready-to-show event handler + + // Start loading tokenizer modules in background after window is created + // This ensures accurate token counts for first API calls (especially in e2e tests) + // Loading happens asynchronously and won't block the UI + if (loadTokenizerModulesFn) { + void loadTokenizerModulesFn().then(() => { + console.log(`[${timestamp()}] Tokenizer modules loaded`); }); - console.log(`✅ React DevTools installed: ${extension.name} (id: ${extension.id})`); - } catch (err) { - console.log("❌ Error installing React DevTools:", err); } - } + // No need to auto-start workspaces anymore - they start on demand + } catch (error) { + console.error(`[${timestamp()}] Startup failed:`, error); - createMenu(); + closeSplashScreen(); - // Three-phase startup: - // 1. Show splash immediately (<100ms) and wait for it to load - // 2. Load services while splash visible (fast - ~100ms) - // 3. Create window and start loading content (splash stays visible) - // 4. When window ready-to-show: close splash, show main window - // - // Skip splash in E2E tests to avoid app.firstWindow() grabbing the wrong window - if (!isE2ETest) { - await showSplashScreen(); // Wait for splash to actually load - } - await loadServices(); - createWindow(); - // Note: splash closes in ready-to-show event handler - - // Start loading tokenizer modules in background after window is created - // This ensures accurate token counts for first API calls (especially in e2e tests) - // Loading happens asynchronously and won't block the UI - if (loadTokenizerModulesFn) { - void loadTokenizerModulesFn().then(() => { - console.log(`[${timestamp()}] Tokenizer modules loaded`); - }); + // Show error dialog to user + const errorMessage = + error instanceof Error + ? `${error.message}\n\n${error.stack ?? ""}` + : String(error); + + dialog.showErrorBox( + "Startup Failed", + `The application failed to start:\n\n${errorMessage}\n\nPlease check the console for details.` + ); + + // Quit after showing error + app.quit(); } - // No need to auto-start workspaces anymore - they start on demand }); app.on("window-all-closed", () => { From c4f53025536092ccf173bd0caf042cfe52c90fde Mon Sep 17 00:00:00 2001 From: Ammar Date: Mon, 13 Oct 2025 15:20:30 -0500 Subject: [PATCH 2/2] Apply prettier formatting --- src/main.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index dc8eafd8e..90c8ace49 100644 --- a/src/main.ts +++ b/src/main.ts @@ -406,9 +406,7 @@ if (gotTheLock) { // Show error dialog to user const errorMessage = - error instanceof Error - ? `${error.message}\n\n${error.stack ?? ""}` - : String(error); + error instanceof Error ? `${error.message}\n\n${error.stack ?? ""}` : String(error); dialog.showErrorBox( "Startup Failed",