fix(pwa): auto-reload on iOS WebKit render freeze (Paint Timing self-heal)#221
Conversation
…heal) On-device evidence resolved the real nature of the blank-on-cold-start: on a blank load, neither the consent overlay nor the (separate) inline diagnostic overlay paints — two unrelated DOM elements both invisible. The whole page fails to paint (white) on ~5/9 cold loads in Edge/iOS (iOS 26 / WKWebView). DOM is complete, bundle runs (bundleRan=true), SW uninvolved (swController=no). User confirms only a full refresh clears it — scroll/tap/rotate do not — and the browser chrome is fine. That's a hard iOS-WebKit render-process freeze: content is never painted and won't be until a fresh navigation. So none of the content- level theories (service worker, consent timing) could have fixed it. Since a reload reliably clears it, self-heal: ~3s after load, check the Paint Timing API for a 'first-contentful-paint' entry; if absent, the page never painted → reload once (sessionStorage-guarded against loops). Runs in the inline script, independent of the bundle, so it works through the freeze. Complements the boot-watchdog (which covers a bundle that never runs). If Paint Timing is unavailable, treat as painted so we never risk a reload loop. Also revert the requestAnimationFrame consent-overlay mount (#219): it targeted the wrong layer and 5/9-vs-3/7 suggested it made things slightly worse. Closes #220 Refs #218 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code Review — PR #221OverviewThis PR fixes an iOS WebKit render-freeze (blank cold-start) by detecting the absence of a The diagnosis is solid: two unrelated DOM elements both invisible, DOM complete, bundle ran, SW uninvolved — that's a renderer freeze only a reload can clear. The self-heal approach is the right level to address it.
|
Prevents an infinite reload loop when sessionStorage is fully unavailable (private mode / storage disabled): only reload if the guard key actually persisted, mirroring the boot-watchdog. Addresses review feedback on #221. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code ReviewGood diagnosis and a clean, minimal fix. Here are my observations:
|
…e worker The Edge/iOS blank-on-cold-start was traced to an iOS-26 WKWebView whole-page render freeze — not the app, the service worker, the consent modal, or first-paint compositing (see #224 for the full record). Remove the investigation scaffolding now that it's documented: - index.html: drop the on-screen error/blank-state diagnostic overlay (#207/#208) and the inert Paint-Timing render-reload (#221). Keep the boot-watchdog (#206) — a legit, general recovery for a bundle that never executes (stale SW serving a 404'd chunk), unrelated to this saga. - src/sw.ts: remove the SW-side diagnostics (recordSwError + webmap-sw-diag cache) and the empty-body navigation check (it was probing the disproven "SW returns an empty navigation" theory). The SW keeps its sound shape: precache, network-first navigation with an install-verified precache offline fallback, tile StaleWhileRevalidate with purgeOnQuotaError, geocode NetworkOnly, manual SKIP_WAITING + clientsClaim. - src/main.ts: remove surfaceSwDiagnostics() and its call/import. - src/sw-constants.ts: remove the SW_DIAG_* constants. Net -190 lines. Keeps the injectManifest architecture (working + deployed); a full revert to generateSW is a possible future simplification but not worth destabilizing a working SW here. Refs #224 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
The blank-on-cold-start is a whole-page render freeze in iOS WebKit (iOS 26 / WKWebView), not any content-level bug. On a blank load, neither the consent overlay nor the separate inline diagnostic overlay paints — two unrelated DOM elements both invisible — so the entire page fails to paint white. DOM is complete, bundle runs (
bundleRan=true), service worker uninvolved (swController=no). User confirmed: only a full refresh clears it (scroll/tap/rotate do not), browser chrome normal. Content is never painted and won't be until a fresh navigation — which is why none of the prior content-level fixes could have worked.Fix: Paint-Timing self-heal
Since a reload reliably clears it, detect and reload:
index.htmlinline script: ~3s after load, check the Paint Timing API for afirst-contentful-paintentry. Absent ⇒ the page never painted ⇒location.reload()once (sessionStorage-guarded). Runs independent of the bundle, so it works through the freeze. If Paint Timing is unavailable, treated as painted (never risk a loop). Complements the boot-watchdog (bundle-never-runs case).consent.ts— revert therequestAnimationFramemount from fix(consent): mount consent overlay after first paint to fix iOS blank screen #219 (wrong layer; 5/9-vs-3/7 suggested it slightly regressed).This doesn't prevent the WKWebView freeze (un-patchable from JS), but the app now auto-recovers (brief white flash → reload → working) instead of a stuck dead page.
Test plan
type-check(app + worker),lint,test(96),build— all green; self-heal verified in builtindex.htmlNotes
Once confirmed in the field, the temporary diagnostics (#207/#208 + SW-diag) can be removed and the SW simplified (it was never the cause).
Closes #220
Refs #218
🤖 Generated with Claude Code