# PWA strategy The web client is an installable, offline-tolerant PWA. It uses SvelteKit's native service worker (no Workbox) so there is no extra build dependency and the cache logic stays explicit. The single-URL app-shell (see [`navigation.md`](navigation.md)) makes the offline story simpler: the whole game UI lives at one route (`${base}/`, i.e. `/game/` under the single-origin deployment), so there is exactly one navigation target to precache and fall back to — no per-screen routes to enumerate. The service-worker scope and the manifest are unchanged by that refactor; both were already base-aware (the SW keys everything off `$service-worker`'s `base`, and the manifest uses relative `./` `start_url` / `scope`). ## Pieces - [`src/service-worker.ts`](../frontend/src/service-worker.ts) — the worker. SvelteKit registers it automatically in the production build. It precaches the app shell (`${base}/`), the build artefacts (JS/CSS + `core.wasm`), and the static files under a **version-keyed** cache (`galaxy-cache-`, `version` from `$service-worker`). On `activate` it deletes every other cache, so a new deploy never serves stale code. Strategy: cache-first for the version-keyed build/files; network-first with cache fallback for everything else; the cached shell answers navigations when fully offline. The gateway (cross- origin) is never intercepted — it is always live network. - [`static/manifest.webmanifest`](../frontend/static/manifest.webmanifest) — name, `standalone` display, relative `./` `start_url`/`scope` (so it resolves under whatever `base` the build is deployed at), dark `theme_color`/`background_color`, and the icon set. - [`static/icons/`](../frontend/static/icons/) — `192`/`512` (`any`), a `512` `maskable`, and a `180` apple-touch icon. They are placeholder artwork generated from `static/favicon.svg` by [`scripts/gen-pwa-icons.mjs`](../frontend/scripts/gen-pwa-icons.mjs) (a dependency-free pure-Node PNG encoder); swap in real artwork at the same paths and the manifest is unchanged. - [`src/app.html`](../frontend/src/app.html) — the manifest link, the apple-touch-icon link, and light/dark `theme-color` metas matching the design tokens. ## Testing PWA behaviour is gated by Playwright against a **production preview** build ([`playwright.pwa.config.ts`](../frontend/playwright.pwa.config.ts) + [`tests/pwa/pwa.spec.ts`](../frontend/tests/pwa/pwa.spec.ts), run by `pnpm test:pwa` in CI): the manifest is linked with installable icons, the service worker registers and controls the page under exactly one version-keyed cache, and the app shell loads offline from that cache. The spec needs a real build because `$service-worker`'s `build` list is empty under `vite dev`. Lighthouse is intentionally **not** used: its PWA category was removed in Lighthouse 12 (the current line is 13.x), so "Lighthouse PWA ≥ 90" is no longer a meaningful gate. The Playwright checks above verify the same install/offline behaviour directly.