feat(fase-14): PWA auto-update toast (14.1)

Migrate from `registerSW({ immediate: true })` to `useRegisterSW` from
`virtual:pwa-register/svelte`, mirror its `needRefresh` / `offlineReady`
stores into stable layout-local writables, and render a new
`UpdateToast` pill (bottom-center) that surfaces:

  - "New version available" + a "Reload" button that calls
    `updateServiceWorker(true)`
  - "Ready to use offline" (auto-dismiss after 4s) on first install

Adds `[pwa] …` console.info telemetry on register / need-refresh /
apply. Stand-in for the unavailable `updateViaCache: 'none'` runtime
option (RegisterSWOptions doesn't expose it in vite-plugin-pwa 0.21.2):
poll `reg.update()` every 15min, which keeps a too-aggressively-cached
SW file from pinning users on the previous build for up to 24h.

`apps/web/src/global.d.ts` declares the virtual module's surface inline
so svelte-check picks it up without a relative path into node_modules;
the same file pre-declares the Fase-14.3 `__APP_VERSION__` triplet.

PU-01 stubs the registrar via `window.__pwaRegisterStub` (the layout
checks for it before calling the real `useRegisterSW`) so the toast
flow can be driven from a Playwright test without a second build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 06:59:29 +02:00
parent d34c578ad8
commit a6e6915b0b
6 changed files with 342 additions and 5 deletions

View File

@@ -164,6 +164,22 @@
"edit": "Editar",
"add": "Añadir",
"done": "Listo",
"pwa_update_available": "Nueva versión disponible",
"pwa_update_reload": "Recargar",
"pwa_offline_ready": "Lista para usar offline",
"pwa_offline_chip": "Sin conexión",
"pwa_offline_tooltip": "Sin conexión — los cambios se sincronizarán al volver",
"pwa_pending_ops": "{count} cambio pendiente",
"pwa_pending_ops_plural": "{count} cambios pendientes",
"pwa_sync_conflicts_banner": "Hay conflictos de sincronización",
"pwa_sync_conflicts_review": "Revisar",
"sync_conflicts_title": "Conflictos de sincronización",
"sync_conflicts_empty": "Nada pendiente — local y servidor coinciden.",
"sync_conflicts_discard_local": "Descartar local",
"sync_conflicts_discard_remote": "Descartar remoto",
"sync_conflicts_back": "Volver a ajustes",
"settings_about": "Acerca de",
"settings_about_version": "v{version} · {commit} · {date}",
"sync_offline": "Sin conexión — los cambios se sincronizarán al reconectar",
"sync_syncing": "Sincronizando…",
"undo": "Deshacer",