Fix white/inverted flash when windows open
Three layers: windows are created hidden and revealed on the webview's first dom-ready (800ms fallback); the bun preload bakes the persisted theme data-attributes so the first paint is already themed; and index.html carries an inline background style covering the pre-stylesheet paint (values mirrored from styles.css tokens). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+48
-3
@@ -1,4 +1,5 @@
|
||||
import { BrowserWindow } from "electrobun/bun";
|
||||
import type { AppSettings } from "../shared/types";
|
||||
|
||||
const isMac = process.platform === "darwin";
|
||||
|
||||
@@ -6,20 +7,61 @@ const isMac = process.platform === "darwin";
|
||||
* The preload globals every window gets. Single composition point: the UI
|
||||
* reads them in ui/src/app/platform.ts before first paint (window chrome and
|
||||
* root-component selection both depend on them).
|
||||
*
|
||||
* The persisted theme attributes are baked in here — mirroring the
|
||||
* delete/set logic of SettingsService.applyX — so the FIRST paint is already
|
||||
* themed (the inline <style> in ui/src/index.html consumes them). Without
|
||||
* this, forced themes only apply after the async getSettings RPC and new
|
||||
* windows flash the wrong background.
|
||||
*/
|
||||
export function buildPreload(mode: "main" | "editor" | "manager", ctx?: unknown): string {
|
||||
export function buildPreload(
|
||||
mode: "main" | "editor" | "manager",
|
||||
settings: AppSettings,
|
||||
ctx?: unknown,
|
||||
): string {
|
||||
let script =
|
||||
`window.__APP_PLATFORM=${JSON.stringify(process.platform)};` +
|
||||
`window.__APP_MODE=${JSON.stringify(mode)};`;
|
||||
if (ctx !== undefined) script += `window.__APP_CTX=${JSON.stringify(ctx)};`;
|
||||
|
||||
const dataset: Record<string, string> = {};
|
||||
if (settings.theme !== "system") dataset["theme"] = settings.theme;
|
||||
if (settings.darkTone !== "dark") dataset["darkTone"] = settings.darkTone;
|
||||
if (settings.accent !== "graphite") dataset["accent"] = settings.accent;
|
||||
if (settings.textSize !== "default") dataset["text"] = settings.textSize;
|
||||
for (const [key, value] of Object.entries(dataset)) {
|
||||
script += `document.documentElement.dataset[${JSON.stringify(key)}]=${JSON.stringify(value)};`;
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows are created hidden and revealed on the webview's first dom-ready —
|
||||
* showing them earlier paints the native (white) surface before the themed
|
||||
* page exists. The fallback timer guarantees a missed event can never leave
|
||||
* a window invisible.
|
||||
*/
|
||||
export function showWhenReady(win: BrowserWindow): void {
|
||||
let shown = false;
|
||||
const show = () => {
|
||||
if (shown) return;
|
||||
shown = true;
|
||||
win.show();
|
||||
};
|
||||
const fallback = setTimeout(show, 800);
|
||||
win.webview.on("dom-ready", () => {
|
||||
clearTimeout(fallback);
|
||||
show();
|
||||
});
|
||||
}
|
||||
|
||||
export interface SecondaryWindowOptions {
|
||||
title: string;
|
||||
/** The once-resolved dev/prod url (see getMainViewUrl in index.ts). */
|
||||
url: string;
|
||||
mode: "editor" | "manager";
|
||||
/** Current persisted settings — baked into the preload for flash-free paint. */
|
||||
settings: AppSettings;
|
||||
ctx?: unknown;
|
||||
frame: { x: number; y: number; width: number; height: number };
|
||||
/** Per-window rpc instance. NEVER share one across windows — each
|
||||
@@ -36,16 +78,19 @@ export interface SecondaryWindowOptions {
|
||||
* native titlebar and the UI renders a compact non-drag header instead.
|
||||
*/
|
||||
export function openSecondaryWindow(opts: SecondaryWindowOptions): BrowserWindow {
|
||||
return new BrowserWindow({
|
||||
const win = new BrowserWindow({
|
||||
title: opts.title,
|
||||
url: opts.url,
|
||||
frame: opts.frame,
|
||||
...(isMac
|
||||
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } }
|
||||
: {}),
|
||||
preload: buildPreload(opts.mode, opts.ctx),
|
||||
preload: buildPreload(opts.mode, opts.settings, opts.ctx),
|
||||
rpc: opts.rpc,
|
||||
hidden: true,
|
||||
});
|
||||
showWhenReady(win);
|
||||
return win;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user