feat(client): service-worker update toast (#225) #270
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!270
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "225-sw-update-toast"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a PWA update toast (#225) that surfaces when the service worker detects a newer deployment. The user picks "Reload" or dismisses the toast for the session.
What ships
apps/client/public/sw.js—messagelistener that callsself.skipWaiting()on{ type: "SKIP_WAITING" }.CACHE_VERSIONbumped tov2per the file's own contract (any sw.js change bumps the version).apps/client/lib/pwa/useServiceWorkerUpdate.ts— hook that observes the SW lifecycle. FlipsupdateAvailabletotruewhen a new worker reachesinstalledAND a controller already exists (i.e. this isn't the first-ever install).reload()postsSKIP_WAITINGand waits forcontrollerchangebefore callinglocation.reload()— reloading earlier produces a stale-shell flash.dismiss()persistspwa.update.dismissed = trueinsessionStorageso the toast stays gone for the session but reappears on next visit. No-op on native and inside the Tauri shell.apps/client/lib/pwa/serviceWorkerUpdateObserver.ts— passive observation lifted out of the hook as a plain function so vitest's node environment can drive it without a React renderer.apps/client/lib/pwa/UpdateToast.tsx— absolutely-positioned card at the bottom of the viewport. Carol's voice: "Carol has a new version." / "Reload to get it." Split into a wiredUpdateToastand a pureUpdateToastViewso the DOM-shape test can exercise the presentation without a renderer.apps/client/app/_layout.tsx— mount the toast insideThemeProvider+I18nProviderbut outsideServerUrlGate, so it appears on every route including/loginand/server-setup.packages/i18n/messages/en.json— newpwaUpdate.*namespace. Spanish stays partial per ADR-0025.Decisions worth flagging
sessionStorage), not persistent. The toast reappears next visit per the acceptance criterion.navigator.serviceWorkeris absent in the Tauri webview, soisSupported()returnsfalseand the hook hands back a stub object with no listeners ever attached. The toast component then returnsnull.startSwUpdateObserver) and the hook is a thin wrapper. This keeps the lifecycle logic testable in vitest's node environment.Test plan
pnpm -F @carol/client typecheck/lint/test/export:webpnpm -F @carol/api-client typecheck/lint/test/checkpnpm -F @carol/api typecheck/lint/testupdatefound→statechange: installedwith a controller present.registration.waitingis already populated at mount time.nullwhen no update is pending or the user dismissed.reload()anddismiss().CACHE_VERSION, open the PWA in a tab that's already controlled by the previous SW, confirm the toast appears, confirm Reload + Dismiss behave per spec.Related: #208 (where the hand-rolled SW came from).
Closes #225.
So the page can ask a waiting SW to activate immediately. The update-toast flow posts { type: "SKIP_WAITING" }; the SW calls skipWaiting() which fires activate, the new SW claims existing clients, and the browser fires controllerchange in the page. CACHE_VERSION bumped to v2 per the file's own contract — any change to sw.js bumps the version so the activate handler clears prior caches.📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.