Proposal: PWA In-App Updates¶
Status: Shipped — device-verified 2026-07-20 · Last updated: 2026-07-20
Summary¶
An installed PlebChat updates itself: a service worker precaches the app shell, detects new deploys, and shows an "Update available" toast whose button activates the new version and reloads — no more delete-and-reinstall from the home screen. Direct owner request 2026-07-14.
Design¶
- Stack:
@vite-pwa/sveltekit(workboxgenerateSW) invite.config.ts,registerType: 'prompt'— never silently swap versions under a running session; the user updates at will. - Manifest stays ours:
manifest: false— the hand-writtenstatic/manifest.webmanifestkeeps its deliberatelytheme_color-free shape (iOS opaque-status-bar regression). - Registration (
src/lib/pwa/update.ts, dynamic-imported by the root layout):registerSW({ immediate: true, … }).immediate: trueis load-bearing — we register after hydration, and without it workbox-window can wait for a windowloadevent that already fired, silently never registering (cost a full debugging session to find). - Update checks: on launch, hourly, and on every
visibilitychangeto visible — the case that matters for an installed PWA sitting in the app switcher for days. - Toast: persistent (
duration: Infinity) sonner info with an "Update" action →updateSW(true)(skipWaiting + reload), plus a What's new link in the description to/docs/whats-new/(MkDocs page in the same Pages artifact). Docs are outside the SW precache /navigateFallbackDenylist, so a network open hits the new deploy's changelog while the old shell is still running — the interesting case. Keepdocs/whats-new.mdtopped up when shipping user-facing changes. - Workbox scope: precache
js/css/html/ico/png/svg/webmanifest/woff2(~250 entries);navigateFallbackDenylist: [/^\/docs\//]so the MkDocs site beside the SPA never gets the app shell. No runtime caching — relays are websockets and Blossom/Firecrawl fetches should never be cached. - Offline shell is a side effect of precaching, not a goal; data flows still need the network.
- Durability (added 2026-07-14):
setupPwaUpdatesalso firesnavigator.storage.persist()so the SW precache + IndexedDB are exempt from best-effort eviction — the installed PWA keeps opening even if the Pages origin is down for weeks. Chromium grants it for installed PWAs; iOS ignores the API (home-screen apps are already exempt from the 7-day script-storage cap, but storage-pressure eviction remains possible there — no contract, just practice).
Verification notes (hard-won)¶
- The full A→B cycle is verifiable locally: build twice (the
__APP_BUILT_AT__stamp differs), serve snapshot A, load (SW activates), swap in snapshot B, triggerregistration.update()→ toast → click Update → new version active. Scripted with Playwright against a static server. - The test server must resolve extensionless URLs (
/synthesize→synthesize.html) like GitHub Pages does — SvelteKit's prerendered pages are precached extensionless, and againstpython3 -m http.serverthe SW install 404s and the worker goesredundant(registration silently vanishes).npx serveworks;vite previewworks. - A worker stuck on
redundant/vanishing registration = precache install failure. Check every manifest URL actually serves.
Risks / rollback¶
A broken SW is sticky. Mitigations: precache-only (no runtime routes to go stale), prompt-mode (a bad deploy can be followed by a good one and users just update again), and if a truly wedged SW ever ships, selfDestroying: true in the plugin config is the documented kill switch — deploy it and workers unregister themselves.
Progress¶
- Plugin + workbox config, SW builds (248 precached entries)
- Update module with prompt toast + launch/hourly/foreground checks
- Full A→B update cycle verified locally (toast → update → new version)
- What's new: MkDocs
docs/whats-new.mdlinked from the update toast - On-device: install, ship a deploy, see the toast on next foreground — owner-confirmed on iPhone 2026-07-20 (installed pre-deploy, foregrounded post-deploy, toast → Update → new version)