M3: PWA, service worker and browser notifications (ENG-262) #25

Merged demi.dev
targeting main from stoopkidddd/eng-262-m3-pwa-service-worker-and-browser-notifications
Merged opened by and assigned to demi.dev
targeting main from stoopkidddd/eng-262-m3-pwa-service-worker-and-browser-notifications

Linear: https://linear.app/engineereddev/issue/ENG-262

What's in this PR (24 files, all under apps/web/ plus pnpm-lock.yaml):

  • Manifest (Pneumo, brand icons incl. maskable, theme/background from tokens, display: standalone, start_url: /); vite-plugin-pwa 2.0.0 in injectManifest mode with src/sw.ts; app-shell precache (22 entries) and offline navigation to the shell.
  • sw.ts + pure sw-logic.ts: push handler for inline push and ref payloads (token and contentKey from the IndexedDB key store), §10 notification options (title default, body, tag = push id, brand icon/badge, open link action), generic "New push" fallback without keys; notificationclick routing (open → openWindow; default → focus + show-push message, else /?push=<id>); pushsubscriptionchange re-subscribe + PUT.
  • Settings "Browser notifications" toggle (subscribe + PUT /api/devices/self/webpush, unsubscribe + DELETE, denied/unsupported states); sign-out unsubscribes; one-time Home prompt "Get notified in this browser"; "Reload to update" toast for a waiting worker.
  • Tests: vitest for sw-logic and the toggle; Playwright for manifest/SW control, a real encrypted payload through the SW push handler with notifications asserted, toggle persistence, the update flow, axe and screenshots.

Verify (lead, rebased on eca40d7):

  • mise run check:web → exit 0 (105 tests, build OK, dist/sw.js generated)
  • mise run e2e:web → exit 0 (27 passed)
  • Implementer: CDP Page.getInstallabilityErrors → [] (Lighthouse 12 has no PWA category).

Deviations: PWA tests use temporary persistent full-Chromium profiles (headless shell and incognito block the native APIs); synthetic notification clicks stand in for activation-gated focus/open. Real push-service delivery stays the M5 check on the owner's browser.

🤖 Generated with Claude Code

Commits 2

M3: Cover offline Settings accessibility (ENG-262)
M3: Add PWA service worker and browser notifications (ENG-262)