Skip to content

fix(startup): kill the white flash on launch - #110

Merged
oesukam merged 1 commit into
mainfrom
fix/startup-white-flash
Aug 6, 2026
Merged

fix(startup): kill the white flash on launch#110
oesukam merged 1 commit into
mainfrom
fix/startup-white-flash

Conversation

@oesukam

@oesukam oesukam commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

The app flashed on every launch — two separate causes:

1. White frame before styles load

The Tauri WebView paints its default white until styles.css applies the dark theme, so launch showed a bright flash that snapped to dark.

Fix: paint dark from frame zero —

  • tauri.conf.json: set the window backgroundColor to #1c1c20 (the native surface is dark before the WebView even paints);
  • index.html: inline background: #1c1c20 + color-scheme: dark so the first HTML paint is dark before styles.css loads.

2. Empty-state flash before tabs restore

hydrateTabsFromStorage() ran in an App useEffect, which fires after the first paint — so the initial render showed the empty state for a frame before the persisted tabs appeared.

Fix: call it in main.tsx before createRoot().render(), so the first paint already has the restored tabs. The background reconnect stays async; the rest of App's wiring (menu handlers, accelerators, persistence subscription) stays in its effect.

Tests

tsc clean, 321 unit tests, 56 e2e pass.

Two flashes on every launch:

1. Blank white frame before styles load. The WebView paints its default white
   until styles.css applies the dark theme. Paint dark from frame zero: set the
   Tauri window backgroundColor (#1c1c20) and inline the dark bg + color-scheme
   in index.html, so the first paint is already dark.

2. Empty-state flash before tabs restore. hydrateTabsFromStorage() ran in an App
   useEffect (fires after paint), so the first render showed the empty state for
   a frame before the persisted tabs appeared. Hydrate in main.tsx BEFORE
   createRoot().render() so the initial paint already has the restored tabs. The
   background reconnect stays async; the rest of App's wiring stays in its effect.
@oesukam
oesukam merged commit 7a639d7 into main Aug 6, 2026
0 of 6 checks passed
@oesukam
oesukam deleted the fix/startup-white-flash branch August 6, 2026 16:13
@oesukam oesukam mentioned this pull request Aug 6, 2026
oesukam added a commit that referenced this pull request Aug 6, 2026
Version bump for the **v0.4.0** release: `0.3.0 → 0.4.0` in
`tauri.conf.json`, `Cargo.toml`, and `Cargo.lock`.

## What's in v0.4.0 (since v0.3.0)

**Features**
- MongoDB support: JS scripting, table-data browse/edit/insert/delete,
connection URL, TLS handling (#101, #103, #105)
- Engine registry — per-engine metadata drives the connection form,
queries, display (#103)
- Rail sessions behave like tabs: click selects, right-click to close,
dim inactive, reconnect on click (#104)
- Row count + query timer moved to a footer below the grid, for both
table-data and SQL tabs (#108, #109)

**Fixes**
- Grid fills the pane for few-column tables; no open blink (#106)
- Edit-connection opens the real connection; Mongo honors its pinned
database (#107)
- Startup white-flash eliminated (dark window bg + pre-render tab
hydration) (#110)
- Mongo defaults to TLS-off so a local Mongo connects (#105)
- Actionable commitlint PR-title error (#102); release gated on
lint/typecheck/tests (#100)

## Gate

lint (0 errors), typecheck (CI-style clean), 321 unit tests, frontend
build — all green locally.

After merge, the Release workflow is triggered with `publish=false` to
produce a **draft** release for review.
oesukam added a commit that referenced this pull request Aug 13, 2026
…link (#127)

## The blink

The window opened **visible immediately**, so on launch you saw the
dark-but-**empty** shell for a beat, then the rail + tabs popped in.
This is a *content-render* blink — distinct from the white flash that
#110 already fixed (dark background is correct from frame zero).

## Fix

- **Create the window hidden** (`visible: false` in tauri.conf.json).
- **Reveal it after the first paint** — the frontend calls
`getCurrentWindow().show()` after two `requestAnimationFrame`s (so the
populated frame has landed), Tauri-only. The OS then reveals an
already-rendered window instead of a blank one that fills in.
- **Rust safety net** — a background thread shows the window after 3s if
the webview never loads (JS crash / blank bundle), so a broken frontend
can't leave an invisible, un-closable window.

## Tests

- Rust `cargo check` clean.
- tsc clean, lint 0 errors, 321 unit tests pass.
- e2e runs in the browser (`isTauri = false`), so `.show()` is skipped
and the window config doesn't apply — verified a spec still passes.

## Note

Best experienced on a fresh app build; the browser/dev path is
unchanged.
oesukam added a commit that referenced this pull request Aug 22, 2026
The app flashed on every launch — two separate causes:

## 1. White frame before styles load
The Tauri WebView paints its default **white** until `styles.css`
applies the dark theme, so launch showed a bright flash that snapped to
dark.

**Fix:** paint dark from frame zero —
- `tauri.conf.json`: set the window `backgroundColor` to `#1c1c20` (the
native surface is dark before the WebView even paints);
- `index.html`: inline `background: #1c1c20` + `color-scheme: dark` so
the first HTML paint is dark before `styles.css` loads.

## 2. Empty-state flash before tabs restore
`hydrateTabsFromStorage()` ran in an App `useEffect`, which fires
**after** the first paint — so the initial render showed the empty state
for a frame before the persisted tabs appeared.

**Fix:** call it in `main.tsx` **before** `createRoot().render()`, so
the first paint already has the restored tabs. The background reconnect
stays async; the rest of App's wiring (menu handlers, accelerators,
persistence subscription) stays in its effect.

## Tests
tsc clean, 321 unit tests, 56 e2e pass.
oesukam added a commit that referenced this pull request Aug 22, 2026
Version bump for the **v0.4.0** release: `0.3.0 → 0.4.0` in
`tauri.conf.json`, `Cargo.toml`, and `Cargo.lock`.

## What's in v0.4.0 (since v0.3.0)

**Features**
- MongoDB support: JS scripting, table-data browse/edit/insert/delete,
connection URL, TLS handling (#101, #103, #105)
- Engine registry — per-engine metadata drives the connection form,
queries, display (#103)
- Rail sessions behave like tabs: click selects, right-click to close,
dim inactive, reconnect on click (#104)
- Row count + query timer moved to a footer below the grid, for both
table-data and SQL tabs (#108, #109)

**Fixes**
- Grid fills the pane for few-column tables; no open blink (#106)
- Edit-connection opens the real connection; Mongo honors its pinned
database (#107)
- Startup white-flash eliminated (dark window bg + pre-render tab
hydration) (#110)
- Mongo defaults to TLS-off so a local Mongo connects (#105)
- Actionable commitlint PR-title error (#102); release gated on
lint/typecheck/tests (#100)

## Gate

lint (0 errors), typecheck (CI-style clean), 321 unit tests, frontend
build — all green locally.

After merge, the Release workflow is triggered with `publish=false` to
produce a **draft** release for review.
oesukam added a commit that referenced this pull request Aug 22, 2026
…link (#127)

## The blink

The window opened **visible immediately**, so on launch you saw the
dark-but-**empty** shell for a beat, then the rail + tabs popped in.
This is a *content-render* blink — distinct from the white flash that
#110 already fixed (dark background is correct from frame zero).

## Fix

- **Create the window hidden** (`visible: false` in tauri.conf.json).
- **Reveal it after the first paint** — the frontend calls
`getCurrentWindow().show()` after two `requestAnimationFrame`s (so the
populated frame has landed), Tauri-only. The OS then reveals an
already-rendered window instead of a blank one that fills in.
- **Rust safety net** — a background thread shows the window after 3s if
the webview never loads (JS crash / blank bundle), so a broken frontend
can't leave an invisible, un-closable window.

## Tests

- Rust `cargo check` clean.
- tsc clean, lint 0 errors, 321 unit tests pass.
- e2e runs in the browser (`isTauri = false`), so `.show()` is skipped
and the window config doesn't apply — verified a spec still passes.

## Note

Best experienced on a fresh app build; the browser/dev path is
unchanged.
Sign up for free to 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