PWA & Offline Shell
Installable Danvas shell with safe offline recovery
Danvas is an installable Progressive Web App. It provides a static offline shell and local draft recovery, while live reads, authenticated screens, and submissions remain server-authoritative.
Installation
Danvas provides the metadata required for installation:
- HTTPS in deployed environments
- A manifest with stable
id: "/", icons, scope/, and standalone display - A compiled service worker at
/serwist/sw.jswith scope/
Users can install from the browser's Add to Home Screen or Install prompt. Installation does not make business data available offline.
Offline contract
Danvas follows the online-authoritative PWA contract in ADR-0103:
- Only generated hashed Next static assets and explicitly approved public shell assets are precached.
- API, authenticated HTML/RSC, mutations, sensitive URLs, and cross-origin requests are network-only.
- A failed same-origin document navigation falls back to the generic
/offlinepage. - The worker never queues or replays business mutations.
- Existing operational drafts remain in identity-scoped localStorage and are not copied into Cache Storage.
The offline page intentionally has no business-route links. It explains that live data and submissions need a connection and reloads automatically when the browser reports that it is online.
Worker build
The worker is not generated by a request-time route. The production app build runs these steps:
next buildcreates the application assets.apps/app/scripts/build-pwa-worker.tsgenerates the explicit Serwist manifest with@serwist/build, injects it, and bundlessrc/app/sw.tswith esbuild.apps/app/scripts/write-pwa-build-report.tsvalidates the allowlist, enforcesapps/app/pwa-budget.json, and writes.next/pwa-build-report.json.
The artifact is emitted to apps/app/public/serwist/sw.js. It is served with Cache-Control: no-cache, Service-Worker-Allowed: /, and the documented worker CSP baseline default-src 'self'; script-src 'self'.
Updates and recovery
The client registers only /serwist/sw.js with updateViaCache: "none". Workers use skipWaiting: false and clientsClaim: true.
Before a waiting worker activates, dirty draft participants get up to five seconds to persist locally. A failed persistence attempt leaves the worker waiting and shows an update prompt. A clean update activates and reloads once.
Emergency recovery is deliberately narrow. It handles known stale chunk or dynamic-module load errors only, performs one reload per session, and removes only Danvas-owned caches plus named legacy Serwist caches. It never unregisters unrelated origin service workers or deletes unrelated Cache Storage entries.
Push notifications
Push settings are driven by the browser's service-worker support, Push API support, Notification permission, VAPID configuration, and current subscription. Permission is requested only from the explicit Enable Notifications button. Test notifications are available only after the server accepts the serialized subscription.
The worker accepts only bounded payloads with a same-origin root-relative destination. It rejects external, protocol-relative, backslash, scheme, and malformed destinations. Push and notification-click handling uses event.waitUntil; clicks prefer an existing focused or visible Danvas client and otherwise open a same-origin window. Payloads are never logged. Browser display and navigation failures are contained and represented by aggregate fixed-message observability events.
On iPhone and iPad, Web Push requires the site to be installed from Safari to the Home Screen. If browser unsubscribe succeeds but server deletion fails, the settings UI reports the two outcomes separately for retry and cleanup.
Freshness
PwaSnapshot renders only a timestamp supplied by a successful query/fetch (dataUpdatedAt). Connectivity probes can describe reachability, but they do not create a data freshness timestamp.
The build report also records raw, gzip, and Brotli worker sizes, per-entry sizes, type distribution, largest entries, unknown sizes, worker digest, cache-policy schema, and package versions. The CI Build job uploads it as a bounded artifact; update the committed budget baseline only from a measured production-equivalent build.
Validation
Run the focused checks from apps/app:
bun run test -- src/lib/pwa/cache-policy.test.ts src/lib/pwa/push-worker.test.ts src/lib/pwa/push-state.test.ts src/lib/pwa/identity-storage-registry.test.ts src/components/pwa-recovery-state.test.ts src/components/pwa-recovery-registry.test.ts src/components/__tests__/pwa-snapshot.test.tsx
bun run typecheck
bun run buildProduction Chromium E2E covers the worker URL/headers and generic offline navigation. Verify Vercel preview behavior manually on Safari/iOS because service-worker update and install UI details vary by browser.
Push notification behavior remains an online application concern and is not used as an offline mutation or cache mechanism.