Skip to content

Commit 83852f8

Browse files
ammar-agentammario
andauthored
🤖 Fix splash screen white flash and improve startup error handling (#232)
## Problem While the splash screen from PR #226 provides instant visual feedback, two issues remain: 1. **White flash** - Splash window sometimes shows white background briefly before HTML loads 2. **Silent startup failures** - If the app fails to start, users see nothing (no window, no error) ## Solution ### Fix White Flash Set `backgroundColor: '#1f1f1f'` on splash window to match the HTML background color. This ensures consistent dark background even if HTML hasn't rendered yet. ### Comprehensive Error Handling Wrap entire `app.whenReady()` in try/catch to handle all startup failures: - Close splash screen if startup fails - Show error dialog with full error message and stack trace - Quit app gracefully after showing error - Users always see what went wrong instead of silent black screen ### Documentation Updates - Corrected service load time (~100ms, not ~6-13s) in code comments - Added note that spinner may freeze briefly during service loading - This is acceptable since splash still provides visual feedback ## Testing - ✅ TypeScript checks pass - ✅ ESLint passes (only pre-existing warnings) - ✅ Manual testing confirms no white flash - ✅ Error handling tested by introducing deliberate startup error ## Impact - ✅ No white flash - consistent dark background from first frame - ✅ Better error UX - users see what went wrong during startup - ✅ More accurate documentation ## Related - Builds on PR #226 (splash screen) - Complements PR #230 (E2E test fixes) - See issue #231 for future tree-shaking optimization Simple, reliable improvements without added complexity. _Generated with `cmux`_ --------- Co-authored-by: Ammar Bandukwala <[email protected]>
1 parent d6d8368 commit 83852f8

File tree

1 file changed

+56
-36
lines changed

1 file changed

+56
-36
lines changed

src/main.ts

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ async function showSplashScreen() {
221221
height: 300,
222222
frame: false,
223223
transparent: false,
224+
backgroundColor: "#1f1f1f", // Match splash HTML background (hsl(0 0% 12%)) - prevents white flash
224225
alwaysOnTop: true,
225226
center: true,
226227
resizable: false,
@@ -266,8 +267,9 @@ function closeSplashScreen() {
266267
/**
267268
* Load backend services (Config, IpcMain, AI SDK, tokenizer)
268269
*
269-
* Heavy initialization (~6-13s) happens here while splash is visible.
270-
* This is the slow part that delays app startup.
270+
* Heavy initialization (~100ms) happens here while splash is visible.
271+
* Note: Spinner may freeze briefly during this phase. This is acceptable since
272+
* the splash still provides visual feedback that the app is loading.
271273
*/
272274
async function loadServices(): Promise<void> {
273275
if (config && ipcMain && loadTokenizerModulesFn) return; // Already loaded
@@ -278,7 +280,7 @@ async function loadServices(): Promise<void> {
278280
/* eslint-disable no-restricted-syntax */
279281
// Dynamic imports are justified here for performance:
280282
// - IpcMain transitively imports the entire AI SDK (ai, @ai-sdk/anthropic, etc.)
281-
// - These are large modules (~6-13s load time) that would block splash from appearing
283+
// - These are large modules (~100ms load time) that would block splash from appearing
282284
// - Loading happens once, then cached
283285
const [
284286
{ Config: ConfigClass },
@@ -368,45 +370,63 @@ function createWindow() {
368370
// Only setup app handlers if we got the lock
369371
if (gotTheLock) {
370372
void app.whenReady().then(async () => {
371-
console.log("App ready, creating window...");
373+
try {
374+
console.log("App ready, creating window...");
375+
376+
// Install React DevTools in development
377+
if (!app.isPackaged && installExtension && REACT_DEVELOPER_TOOLS) {
378+
try {
379+
const extension = await installExtension(REACT_DEVELOPER_TOOLS, {
380+
loadExtensionOptions: { allowFileAccess: true },
381+
});
382+
console.log(`✅ React DevTools installed: ${extension.name} (id: ${extension.id})`);
383+
} catch (err) {
384+
console.log("❌ Error installing React DevTools:", err);
385+
}
386+
}
372387

373-
// Install React DevTools in development
374-
if (!app.isPackaged && installExtension && REACT_DEVELOPER_TOOLS) {
375-
try {
376-
const extension = await installExtension(REACT_DEVELOPER_TOOLS, {
377-
loadExtensionOptions: { allowFileAccess: true },
388+
createMenu();
389+
390+
// Three-phase startup:
391+
// 1. Show splash immediately (<100ms) and wait for it to load
392+
// 2. Load services while splash visible (fast - ~100ms)
393+
// 3. Create window and start loading content (splash stays visible)
394+
// 4. When window ready-to-show: close splash, show main window
395+
//
396+
// Skip splash in E2E tests to avoid app.firstWindow() grabbing the wrong window
397+
if (!isE2ETest) {
398+
await showSplashScreen(); // Wait for splash to actually load
399+
}
400+
await loadServices();
401+
createWindow();
402+
// Note: splash closes in ready-to-show event handler
403+
404+
// Start loading tokenizer modules in background after window is created
405+
// This ensures accurate token counts for first API calls (especially in e2e tests)
406+
// Loading happens asynchronously and won't block the UI
407+
if (loadTokenizerModulesFn) {
408+
void loadTokenizerModulesFn().then(() => {
409+
console.log(`[${timestamp()}] Tokenizer modules loaded`);
378410
});
379-
console.log(`✅ React DevTools installed: ${extension.name} (id: ${extension.id})`);
380-
} catch (err) {
381-
console.log("❌ Error installing React DevTools:", err);
382411
}
383-
}
412+
// No need to auto-start workspaces anymore - they start on demand
413+
} catch (error) {
414+
console.error(`[${timestamp()}] Startup failed:`, error);
384415

385-
createMenu();
416+
closeSplashScreen();
386417

387-
// Three-phase startup:
388-
// 1. Show splash immediately (<100ms) and wait for it to load
389-
// 2. Load services while splash visible (fast - ~100ms)
390-
// 3. Create window and start loading content (splash stays visible)
391-
// 4. When window ready-to-show: close splash, show main window
392-
//
393-
// Skip splash in E2E tests to avoid app.firstWindow() grabbing the wrong window
394-
if (!isE2ETest) {
395-
await showSplashScreen(); // Wait for splash to actually load
396-
}
397-
await loadServices();
398-
createWindow();
399-
// Note: splash closes in ready-to-show event handler
400-
401-
// Start loading tokenizer modules in background after window is created
402-
// This ensures accurate token counts for first API calls (especially in e2e tests)
403-
// Loading happens asynchronously and won't block the UI
404-
if (loadTokenizerModulesFn) {
405-
void loadTokenizerModulesFn().then(() => {
406-
console.log(`[${timestamp()}] Tokenizer modules loaded`);
407-
});
418+
// Show error dialog to user
419+
const errorMessage =
420+
error instanceof Error ? `${error.message}\n\n${error.stack ?? ""}` : String(error);
421+
422+
dialog.showErrorBox(
423+
"Startup Failed",
424+
`The application failed to start:\n\n${errorMessage}\n\nPlease check the console for details.`
425+
);
426+
427+
// Quit after showing error
428+
app.quit();
408429
}
409-
// No need to auto-start workspaces anymore - they start on demand
410430
});
411431

412432
app.on("window-all-closed", () => {

0 commit comments

Comments
 (0)