Skip to content

The window opens blank before the app paints #225

Description

@samkeen

What you see

Launch B2 and the window appears immediately as an empty pane, then fills in with the app a moment later. It is worse under make app than in a bundled build, but it is not only a dev-mode artifact.

Why

Tauri shows a window as soon as it exists, which is well before the webview has a stylesheet, let alone a shell. ui/index.html is <div id="app"></div> and nothing else — every pixel comes from main.ts, and style.css is an import inside it. So the gap is: window shown → HTML+JS fetched → module evaluated (this is where the background colour arrives) → boot()buildShell() → first render().

Under tauri dev the dev server serves the CSS through JS, which widens the first half of that gap; a bundled build gets a <link> in <head> and paints its ground sooner, but still shows an empty window until render().

Not related to page zoom (#224). Measured on a launch with a 160% saved size: the zoom lands 2ms in and the first frame is drawn at 7ms, so no frame is ever painted at the wrong scale. The blank window predates that work.

The obvious fix, and why it isn't one

Create the window hidden ("visible": false) and have the frontend reveal it through a show_window command once render() has painted.

This was tried and reverted. WebKit throttles (or suspends) requestAnimationFrame in a window that is not on screen, so the "wait two frames for real pixels, then reveal" signal never fires — and the window only ever appeared via the safety timer that was there for the case where the frontend breaks. The result was a fixed multi-second wait on every launch: strictly worse than the flash.

Anything built on "the hidden page tells us when it has painted" has to answer that first. Some directions, none of them explored yet:

  • Signal from something that still runs while hidden — a setTimeout chain, document.visibilityState, or the load/paint events WebKit does deliver to an offscreen page — rather than rAF.
  • Reveal on a host-side signal instead: wry/WKWebView's own first-paint or navigation-finished notification, if Tauri surfaces one.
  • Don't hide the window at all. Give it a background colour so it opens as B2's ground rather than white. Cheap, and it turns a blank white window into a blank dark one — but B2 follows the system theme (system/light/dark, main.ts's THEME_KEY), and a window background is one fixed value, so it is right in one theme and wrong in the other. Possibly still a net win if the value tracks prefers-color-scheme at launch.
  • Put a minimal static shell in index.html — the ground colour and the header's outline — so the first paint is deliberate rather than empty. Costs a second place that has to know what the app looks like, which is exactly the kind of duplication this repo avoids.

Notes for whoever picks this up

  • Any reveal-on-signal design needs the host-side failsafe that the reverted attempt had: an app whose frontend fails before it can ask to be shown must not end up running with no window, because a window is the only place it could report why.
  • Measure before and after. The instrument that settled the zoom question was a probe in boot recording performance.now() for the first rAF against the moment the zoom resolved — the same shape works here for "when did the first frame actually land".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions