# Plan: PWA / Offline-Fähigkeit ## Goal The app already processes everything locally — but the browser still has to fetch the app shell from the server on every load. Making **beA-Edit an installable, offline-capable PWA** reinforces the privacy story ("works without internet after first load"), enables home-screen/desktop installation, and is essentially free given there are no network calls to cache-around. --- ## Open decisions (defaults marked ✅) ### D1 — Implementation approach - ✅ **A. `@vite-pwa/sveltekit`** (new **dev** dependency). Mature integration: precaching of the built assets via Workbox, generated manifest, virtual `RegisterSW` module, works with `adapter-node` builds. The dependency is justified (agents.md allows deps when the task cannot reasonably be done without) — hand-rolling a correct service worker + update flow is error-prone. - B. Hand-written minimal `sw.js` in `static/` + manual manifest. Zero deps but custom update logic, manual asset hashing, more long-term maintenance. ### D2 — Update UX - ✅ **A. Auto-update with a toast.** New versions activate on next navigation; a small toast "Neue Version verfügbar – Seite neu laden" appears when an update is waiting (Workbox `onNeedRefresh`/`onOfflineReady` hooks). Never force-reload mid-work (users may have unsaved archive state). - B. Silent auto-reload. Rejected: could interrupt an in-progress merge. ### D3 — Legal pages - ✅ **Review required, not auto-edited.** A service worker + install prompt is client-side storage/behavior; `datenschutz/+page.svelte` should likely gain a sentence. Flag for the user; do not rewrite legal copy unilaterally (project constraint). --- ## Technical approach ### Vite config ```ts // vite.config.ts import { SvelteKitPWA } from '@vite-pwa/sveltekit'; export default defineConfig({ plugins: [ sveltekit(), tailwindcss(), SvelteKitPWA({ registerType: 'prompt', manifest: { name: 'beA-Edit', short_name: 'beA-Edit', description: 'PDF-Werkzeuge für die beA-Kanzlei – 100 % lokal im Browser.', lang: 'de', start_url: '/', display: 'standalone', background_color: '#f2f5f8', // align with theme background token theme_color: '#…', // align with primary token icons: [ /* 192 + 512 + maskable, derived from static logo */ ] }, workbox: { globPatterns: ['**/*.{js,css,html,svg,woff2}'], navigateFallback: '/' // SPA-style offline fallback for app routes }, devOptions: { enabled: false } }) ] }); ``` Notes: - pdfjs worker chunk (`pdf.worker.mjs?url`) is part of the built assets → precached by glob. - Legal routes (`/impressum`, `/datenschutz`) precache like any route; offline navigation works via the fallback. - No runtime caching strategies needed: the app makes **no** network requests by design. ### Registration UI - Root `+layout.svelte`: import the virtual `virtual:pwa-register` module (SSR-guarded), wire `onNeedRefresh` → toast state; render a small dismissible toast component (build with existing card/button styling; no new UI dep) with "Neu laden" / "Später". - `onOfflineReady` → optional one-time hint "beA-Edit funktioniert jetzt offline." (keep subtle). - Install prompt: rely on the browser's native install affordance (manifest makes it appear); optionally an "Installieren" hint on the start page later — out of scope for v1. ### Icons - Generate 192/512/maskable PNGs from the existing logo (`static/` logo asset) once, committed to `static/icons/`. Use an offline tool/imagemagick during implementation; no runtime dep. ### Docker / adapter-node - Service worker + manifest are static build output → served by `node build/index.js` without changes. Verify `Cache-Control` headers don't block SW updates (adapter-node defaults are fine; the SW itself must not be cached aggressively — Vite PWA emits the right filenames). ## Files changed - `vite.config.ts` — SvelteKitPWA plugin + manifest. - `package.json` / lockfile — `@vite-pwa/sveltekit` dev dependency (via pnpm only). - `src/routes/+layout.svelte` — registration + update toast. - `static/icons/*` — generated icons; `static/manifest.webmanifest` handled by plugin. - `src/app.html` — `theme-color` meta if plugin doesn't inject. - `agents.md` — document PWA setup + update behavior. - `datenschutz/+page.svelte` — only after explicit user approval (D3). ## Implementation phases 1. Add dependency, configure plugin + manifest + icons. 2. Registration + update toast in root layout. 3. Build + `pnpm preview`: verify `/` loads, then devtools → Application → service worker registered; toggle "Offline" → reload → app still works. 4. Production image smoke test (Docker build, offline reload). 5. Update `agents.md`; flag datenschutz review. ## Verification - Fresh load registers the SW (devtools), `onOfflineReady` path reachable. - Offline reload after precache: start page + all tool routes render; dropping a ZIP still works fully (entire processing chain offline). - New build deployed → toast offers reload; choosing it loads the new version; declining does not interrupt current state. - No console errors on iOS Safari (manifest parsing quirks) — best-effort check. - `pnpm check`, `pnpm lint`, `pnpm build` pass. ## Risks - **Stale cache confusion** during development: keep `devOptions.enabled: false`; document in agents.md that preview/production test the SW, not dev mode. - **Large precache manifest**: the pdfjs worker + fonts add a few MB to the first precache — acceptable for an installable tool; monitor build output stats. - **SW + SvelteKit hydration edge cases** on first offline visit are known upstream; pin a current stable plugin version and test the exact offline-reload sequence. ## Out of scope - Background sync (nothing to sync — no network by design). - App-store packaging. - Custom install button UI beyond the native affordance.