fix(router): sync route in navigate() — kill boot lobby-flash + offline e2e flake #210

Merged
developer merged 1 commits from fix/boot-route-lag-e2e-flake into development 2026-07-06 23:51:21 +00:00
Owner

Root-cause fix for the offline webkit/chromium e2e flake (owner-picked: fix the app root cause, not just the test).

Root cause

navigate() wrote location.hash and left router.route to the async hashchange. bootstrap flips app.ready in the same tick right after navigate('/login') on an unauthenticated cold start → one frame with app.ready=true + the stale route ('lobby', empty hash) → the lobby shell (tab bar + a doomed games.list) briefly mounts under the login screen. enterLobby could latch that transient tab-bar instead of clicking through login, then the Settings-tab click fought the tab detaching into the login slide (~7%, both engines).

Diagnosed with a synchronous DOM/route probe: hash was #/login at 25 ms while the lobby (tabs=3, Ann) was still in the DOM; after the fix the boot goes splash → login directly (tabs=0).

Fix

  • navigate() sets router.route synchronously; the later hashchange re-parses to the same value. Removes the prod boot lobby-flash + its spurious 401.
  • offline.spec enterLobby: wait-and-click the guest button (not a point-in-time count()).
  • New router.spec pins the sync-route property (RED on old code → 'login'; GREEN → 'settings') via a mock-only __router seam (tree-shaken from prod).

Verify

check 0 / unit 490 / build / bundle 114.3-115 / full e2e 200 (both engines) / offline+router stress 40x both engines 160/160 (was 6/80 failing).

Root-cause fix for the offline webkit/chromium e2e flake (owner-picked: fix the app root cause, not just the test). ## Root cause `navigate()` wrote `location.hash` and left `router.route` to the async `hashchange`. `bootstrap` flips `app.ready` in the same tick right after `navigate('/login')` on an unauthenticated cold start → one frame with `app.ready=true` + the **stale** route ('lobby', empty hash) → the lobby shell (tab bar + a doomed `games.list`) briefly mounts under the login screen. `enterLobby` could latch that transient tab-bar instead of clicking through login, then the Settings-tab click fought the tab detaching into the login slide (~7%, both engines). Diagnosed with a synchronous DOM/route probe: hash was `#/login` at 25 ms while the lobby (tabs=3, Ann) was still in the DOM; after the fix the boot goes splash → login directly (tabs=0). ## Fix - `navigate()` sets `router.route` synchronously; the later `hashchange` re-parses to the same value. Removes the prod boot lobby-flash + its spurious 401. - `offline.spec` `enterLobby`: wait-and-click the guest button (not a point-in-time `count()`). - New `router.spec` pins the sync-route property (RED on old code → 'login'; GREEN → 'settings') via a mock-only `__router` seam (tree-shaken from prod). ## Verify check 0 / unit 490 / build / bundle 114.3-115 / full e2e 200 (both engines) / offline+router stress 40x both engines **160/160** (was 6/80 failing).
developer added 1 commit 2026-07-06 23:44:34 +00:00
fix(router): update route rune synchronously in navigate()
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
16cd3d0411
navigate() wrote location.hash and left router.route to the asynchronous
hashchange event. bootstrap flips app.ready in the same tick right after
navigate('/login') on an unauthenticated cold start, so for one frame the app
rendered with app.ready=true and the stale route ('lobby', from the empty hash)
— briefly mounting the lobby shell (tab bar + a doomed games.list) under the new
login screen before hashchange settled it.

Update router.route synchronously inside navigate(); the later hashchange
re-parses to the same value. This removes the boot lobby-flash (and its spurious
games.list 401) and, as the root cause, the offline e2e flake: enterLobby could
latch that transient lobby tab-bar instead of clicking through login, then the
Settings-tab click at line 44 fought the tab detaching into the login slide
(~7% of runs, both engines).

Also:
- offline.spec enterLobby: wait for the guest button and click it, instead of a
  point-in-time count() that a pre-login splash frame sampled as 0 (skipping the
  click and hanging / latching the transient).
- New e2e router.spec pins the synchronous-route property (RED on the old code,
  which returned the previous route; GREEN now) via a mock-only __router seam.

Verify: check 0 / unit 490 / build / bundle 114.3/115 / full e2e 200 / the
offline+router stress at 40x on both engines 160/160 (was 6/80 failing).
owner approved these changes 2026-07-06 23:51:08 +00:00
developer merged commit d7f3d93c6c into development 2026-07-06 23:51:21 +00:00
developer deleted branch fix/boot-route-lag-e2e-flake 2026-07-06 23:51:21 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#210