Skip to content

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 (workbox generateSW) in vite.config.ts, registerType: 'prompt' — never silently swap versions under a running session; the user updates at will.
  • Manifest stays ours: manifest: false — the hand-written static/manifest.webmanifest keeps its deliberately theme_color-free shape (iOS opaque-status-bar regression).
  • Registration (src/lib/pwa/update.ts, dynamic-imported by the root layout): registerSW({ immediate: true, … }). immediate: true is load-bearing — we register after hydration, and without it workbox-window can wait for a window load event that already fired, silently never registering (cost a full debugging session to find).
  • Update checks: on launch, hourly, and on every visibilitychange to 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. Keep docs/whats-new.md topped 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): setupPwaUpdates also fires navigator.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, trigger registration.update() → toast → click Update → new version active. Scripted with Playwright against a static server.
  • The test server must resolve extensionless URLs (/synthesizesynthesize.html) like GitHub Pages does — SvelteKit's prerendered pages are precached extensionless, and against python3 -m http.server the SW install 404s and the worker goes redundant (registration silently vanishes). npx serve works; vite preview works.
  • 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.md linked 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)