diff --git a/src/main/ipc.js b/src/main/ipc.js index d5733fa..20c53ad 100644 --- a/src/main/ipc.js +++ b/src/main/ipc.js @@ -16,7 +16,7 @@ const fs = require('fs'); const path = require('path'); const { - ipcMain, dialog, shell, BrowserWindow, + ipcMain, dialog, shell, BrowserWindow, app, } = require('electron'); const os = require('os'); const { spawn } = require('child_process'); @@ -492,6 +492,22 @@ function registerIpc({ openCscopeWindow, getInitialFolder, getStartupLogs }) { ipcMain.handle('app:getCliFolder', () => (typeof getInitialFolder === 'function' ? (getInitialFolder() || null) : null)); ipcMain.handle('app:getStartupLogs', () => (typeof getStartupLogs === 'function' ? getStartupLogs() : [])); + // Renderer caches the current theme background here whenever a theme is + // applied. The main process reads it on the next cold start (see + // startupBackground() in main.js) to paint the window with the right color + // immediately. Only valid hex colors are accepted; everything is wrapped so a + // bad value or a write failure returns false instead of crashing the app. + ipcMain.handle('app:setStartupBg', (_e, color) => { + try { + if (typeof color !== 'string' || !/^#[0-9a-fA-F]{3,8}$/.test(color)) return false; + const cacheFile = path.join(app.getPath('userData'), 'startup.json'); + fs.writeFileSync(cacheFile, JSON.stringify({ bg: color })); + return true; + } catch (_err) { + return false; + } + }); + // ---- tool updates (ripgrep / fd) ---- ipcMain.handle('tool:checkUpdate', async (_e, params) => { try { return await toolUpdate.checkUpdate(params || {}); } catch (err) { return { ok: false, error: String((err && err.message) || err) }; } diff --git a/src/main/main.js b/src/main/main.js index 9aa8be8..66a904b 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -61,6 +61,29 @@ let mainWindow = null; // a race with the window's did-finish-load event. let initialFolder = null; +// Default theme background (DAYLIGHT `--bg` in src/renderer/js/themes.js). Used +// as the window backgroundColor when no valid startup cache is available. +const DEFAULT_STARTUP_BG = '#f4f4f4'; +const HEX_COLOR_RE = /^#[0-9a-fA-F]{3,8}$/; + +// Read the cached theme background written by the renderer on the previous run +// (userData/startup.json -> { "bg": "#rrggbb" }). Showing the window with the +// right backgroundColor avoids a white flash before the renderer paints, which +// matters most for dark-theme users. Falls back to the light default theme bg +// when the cache is missing (first launch) or invalid. Wrapped in try/catch so +// a missing file or bad JSON never throws during boot. +function startupBackground() { + try { + const cacheFile = path.join(app.getPath('userData'), 'startup.json'); + const raw = fs.readFileSync(cacheFile, 'utf8'); + const bg = JSON.parse(raw).bg; + if (typeof bg === 'string' && HEX_COLOR_RE.test(bg)) return bg; + } catch (_e) { + /* no cache yet / unreadable / invalid JSON - use the default */ + } + return DEFAULT_STARTUP_BG; +} + function findIcon() { const candidates = [ // Primary location: LOGO/M2_SCOUT.ico next to the app. @@ -87,7 +110,12 @@ function createMainWindow() { title: `M2_SCOUT v${APP_VERSION}`, icon: findIcon(), autoHideMenuBar: true, - backgroundColor: '#f4f4f4', + // Show the window immediately on creation (no `show: false` + + // `ready-to-show` wait) so it appears as early as possible; the renderer + // content fills in right after. backgroundColor is seeded from the cached + // theme bg so dark-theme users don't see a white flash first. + show: true, + backgroundColor: startupBackground(), webPreferences: { preload: path.join(__dirname, '..', 'preload', 'preload.js'), contextIsolation: true, diff --git a/src/preload/preload.js b/src/preload/preload.js index 98b669c..991a304 100644 --- a/src/preload/preload.js +++ b/src/preload/preload.js @@ -115,6 +115,9 @@ contextBridge.exposeInMainWorld('m2scout', { // app events getCliFolder: () => ipcRenderer.invoke('app:getCliFolder'), getStartupLogs: () => ipcRenderer.invoke('app:getStartupLogs'), + // Cache the current theme background so the next cold start can paint the + // window with the correct color immediately (avoids a white flash). + setStartupBg: (color) => ipcRenderer.invoke('app:setStartupBg', color), onCliFolder: (cb) => { const listener = (_e, payload) => cb(payload); ipcRenderer.on('app:cliFolder', listener); diff --git a/src/renderer/js/themes.js b/src/renderer/js/themes.js index 91403ff..de961bf 100644 --- a/src/renderer/js/themes.js +++ b/src/renderer/js/themes.js @@ -293,6 +293,13 @@ for (const k of Object.keys(vars)) root.style.setProperty(k, vars[k]); root.setAttribute('data-theme', themeId); try { localStorage.setItem('appTheme', themeId); } catch (_e) { /* ignore */ } + // Cache the current theme background in the main process so the next cold + // start paints the window with the correct color immediately (no flash). + try { + if (window.m2scout && typeof window.m2scout.setStartupBg === 'function') { + window.m2scout.setStartupBg(vars['--bg']); + } + } catch (_e) { /* ignore */ } try { window.dispatchEvent(new CustomEvent('m2-theme-changed', { detail: { theme: themeId } })); } catch (_e) { /* ignore */ }