Skip to content

fix(desktop): reveal main window only after first React commit - #599

Merged
Astro-Han merged 3 commits into
mainfrom
fix/desktop-show-window-after-first-commit
Jul 7, 2026
Merged

fix(desktop): reveal main window only after first React commit#599
Astro-Han merged 3 commits into
mainfrom
fix/desktop-show-window-after-first-commit

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Create the main BrowserWindow hidden and reveal it only after the renderer's first React commit (via a new window:notifyRendererReady IPC), with a 4s fallback timer. Users no longer see the .maka-preload startup skeleton card on every launch.

Why

User report: "every time I open the app there's a placeholder box — feels like a bug." The window showed immediately (show: true) while React mount waits on the onboarding snapshot prefetch (#525, up to 2.5s fallback), so every launch exposed the index.html skeleton — worst case for 2.5s on slow machines. This flips the causality: the window waits for content instead of content decorating a premature window, removing the whole class of startup-flash issues (#429, #493, #525 patched individual layers of it).

Scope

Changed:

  • main-window.ts: show: false always; reveal via shared gate on renderer-ready signal or 4s fallback; timer cleared on close.
  • window-reveal.ts (new): pure reveal gate — idempotent (isVisible() guard, no focus stealing on HMR re-signal), never reveals startHidden (visual-smoke/E2E) windows, testable without Electron.
  • main.ts / preload.ts / global.d.ts: window:notifyRendererReady handler + appWindow.notifyRendererReady().
  • app.tsx: one-shot useLayoutEffect signaling after first commit, unconditional (fires even on the fail-soft null-snapshot path).
  • New contract test locking: hidden creation, reveal-once idempotency, fallback reveal, startHidden never reveals.

Not included: removing the .maka-preload skeleton (kept as reload-time anti-flash layer); paintWhenInitiallyHidden untouched (visual-smoke capture depends on it).

Verification

  • npm run typecheck, npm run -w @maka/desktop test (2151 pass / 0 fail, +8 new), npm run build — all green.
  • Runtime check via main-process inspector: cold start reveals at ~680ms with isVisible() flipping only after the skeleton is gone and the real app frame is mounted; reveal is ~140ms later than the old show-immediately behavior.

User-facing impact

Launch no longer flashes a skeleton card; the window appears already rendered. No changelog entry needed (unreleased).

Reviewer notes

document.visibilityState is misleading for verifying this: paintWhenInitiallyHidden makes hidden windows paint, so it flips before the window is actually on screen — hence the main-process isVisible() verification. Review focus: the keepHiddenForVisualSmoke predicate must exactly mirror the old creation-time !app.isPackaged && startHidden gate.

The BrowserWindow showed immediately (show: true) while React mount
waits on the onboarding snapshot prefetch (up to 2.5s), so every launch
exposed the index.html .maka-preload skeleton — users read it as a bug.
Create the window hidden and reveal it when the renderer signals its
first React commit via window:notifyRendererReady, with a 4s fallback
timer so a wedged renderer can never leave the window invisible.
Visual-smoke / E2E startHidden windows stay hidden for their whole
life; the skeleton remains as a reload-time anti-flash layer only.
…commit
ChatGPT Pro review P2: controller focus() called mainWindow.show()
directly, so re-launching the app or clicking the dock icon during the
pre-commit window would flash the .maka-preload skeleton past the
reveal gate. Route focus() through a readiness-aware gate that defers
the request and flushes it (restore+show+focus) when the renderer
signals ready or the fallback timer fires; keepHidden (visual-smoke /
E2E) windows never show or take focus from any path.
ChatGPT Pro review P2 (round 2): BrowserWindow.maximize() reveals a
still-hidden window (verified on macOS), so restoring a persisted
isMaximized state in createWindow() bypassed the reveal gate — users
who closed the app maximized saw the skeleton again on every launch.
Route the restore through revealGate.requestMaximize(): the intent is
held until markReady, which applies it right before the reveal so the
first visible frame is already maximized. keepHidden (visual-smoke /
E2E) windows never maximize from any path.
@Astro-Han
Astro-Han merged commit 2d6218b into mainJul 7, 2026
3 checks passed
@Astro-Han
Astro-Han deleted the fix/desktop-show-window-after-first-commit branch July 7, 2026 09:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han