Summary
public/sw.js calls self.clients.claim() on activate, and register-sw.ts's flushLatestStateThenReload() unconditionally calls window.location.reload() whenever navigator.serviceWorker.controllerchange fires while the page is visible.
clients.claim() claims already-open clients immediately, not just future navigations. On a completely fresh browser context (no prior service worker), the very first page load's register() → install → activate → clients.claim() sequence therefore fires controllerchange on that same first-ever page, not only on a version update for a returning visitor. The result: every first-time visitor, and every fresh E2E browser context, undergoes one automatic, unprompted reload shortly after the initial page load — a "double boot" that appears to be an accepted-but-undocumented side effect of the "always fresh, no waiting" SW update strategy, not something anyone deliberately decided should also apply to first-ever installs.
Why this matters
Surfaced during PR #583's investigation of #532 (WelcomePortal startup/navigation E2E nondeterminism). This reload is asynchronous and can land at an arbitrary point during page interaction, which is a plausible (not yet proven) contributor to some historical #532-class flakiness, independent of the addInitScript-ordering and factory-reset-connection races #532/#583 already fixed.
Scope for a fix
Two independent directions, not mutually exclusive:
- Production behavior: decide whether a first-ever install should reload at all. If the intent was only to ensure a returning visitor picks up a new version promptly,
clients.claim()/the reload logic could distinguish first-install from update (e.g. skip the reload path when there was no prior controller before this activation).
- E2E robustness: independent of whether (1) changes,
tests/e2e/helpers.ts's startup helpers (waitForSpaReady, resolveStartupState, ensureWelcomePortalEntry) should be verified robust to an unprompted navigation landing mid-action, not just mid-idle.
Non-goals
Not a #532 regression by itself — #532's fixed root causes (in #583) are independent of this. This should not be used to reopen #532; it's tracked here so it isn't lost, and can be referenced from #532 for context.
Summary
public/sw.jscallsself.clients.claim()onactivate, andregister-sw.ts'sflushLatestStateThenReload()unconditionally callswindow.location.reload()whenevernavigator.serviceWorker.controllerchangefires while the page is visible.clients.claim()claims already-open clients immediately, not just future navigations. On a completely fresh browser context (no prior service worker), the very first page load'sregister()→ install → activate →clients.claim()sequence therefore firescontrollerchangeon that same first-ever page, not only on a version update for a returning visitor. The result: every first-time visitor, and every fresh E2E browser context, undergoes one automatic, unprompted reload shortly after the initial page load — a "double boot" that appears to be an accepted-but-undocumented side effect of the "always fresh, no waiting" SW update strategy, not something anyone deliberately decided should also apply to first-ever installs.Why this matters
Surfaced during PR #583's investigation of #532 (WelcomePortal startup/navigation E2E nondeterminism). This reload is asynchronous and can land at an arbitrary point during page interaction, which is a plausible (not yet proven) contributor to some historical #532-class flakiness, independent of the addInitScript-ordering and factory-reset-connection races #532/#583 already fixed.
Scope for a fix
Two independent directions, not mutually exclusive:
clients.claim()/the reload logic could distinguish first-install from update (e.g. skip the reload path when there was no prior controller before this activation).tests/e2e/helpers.ts's startup helpers (waitForSpaReady,resolveStartupState,ensureWelcomePortalEntry) should be verified robust to an unprompted navigation landing mid-action, not just mid-idle.Non-goals
Not a #532 regression by itself — #532's fixed root causes (in #583) are independent of this. This should not be used to reopen #532; it's tracked here so it isn't lost, and can be referenced from #532 for context.