feat(client): service-worker update toast (new version available — reload) #225

Closed
opened 2026-06-21 18:15:41 +00:00 by james · 0 comments
Owner

Context

#208 (PR #223) shipped a hand-rolled service worker at apps/client/public/sw.js that handles cache invalidation via a CACHE_VERSION constant — bumping it clears prior caches on the SW's activate event. That works for purging stale assets, but it doesn't tell the user a new version is available.

When Carol's deploy cadence picks up, a long-running installed PWA could keep serving a stale shell indefinitely until the user happens to refresh — and even then there's a brief race where the old SW serves cached content before the new one activates. A small in-app toast offering "Carol has a new version — reload?" closes both gaps.

Source

Follow-up flagged in #223.

Scope

  • In apps/client/app/+html.tsx (or a sibling registration helper), listen for the SW's updatefound event on the active registration.
  • When a new SW reaches installed state with a previous controller present (= new version waiting), expose the fact via a TanStack Query helper or a small useServiceWorkerUpdate hook.
  • Render a non-blocking toast in the universal client's layout — copy along the lines of "Carol has a new version. Reload to get it." with a primary "Reload" action and a dismiss affordance. Sentence case, no emoji, Carol's voice.
  • On reload, call registration.waiting?.postMessage({ type: "SKIP_WAITING" }) and window.location.reload() after the new SW takes control. Handle the SKIP_WAITING message in sw.js.
  • DS tokens via useTheme(); strings via react-i18next.

Acceptance criteria

  • After a deploy with a bumped CACHE_VERSION, the installed PWA shows the toast within a minute or so of the next route navigation (when the SW's update check fires).
  • Clicking the toast's "Reload" action loads the new shell on the next visit.
  • Dismissing the toast hides it for the session but it reappears on the next visit until the user reloads.
  • No layout shift or focus theft from the toast on small screens.

Out of scope

  • Auto-reloading without prompt. Some users hate that and Carol's data is per-user; an unannounced reload could lose a draft.
  • Background sync of in-progress writes before reload. Carol's writes are all individual API calls that already complete or fail; the toast just gates the next render.

Composes with

  • #208 — PWA install + offline shell.
  • #210 — nav shell, in case the toast lives in the same layout.

Part of

#176

## Context [#208](https://forge.wynning.tech/james/carol/issues/208) (PR [#223](https://forge.wynning.tech/james/carol/pulls/223)) shipped a hand-rolled service worker at `apps/client/public/sw.js` that handles cache invalidation via a `CACHE_VERSION` constant — bumping it clears prior caches on the SW's `activate` event. That works for purging stale assets, but it doesn't tell the user a new version is available. When Carol's deploy cadence picks up, a long-running installed PWA could keep serving a stale shell indefinitely until the user happens to refresh — and even then there's a brief race where the old SW serves cached content before the new one activates. A small in-app toast offering "Carol has a new version — reload?" closes both gaps. ## Source Follow-up flagged in [#223](https://forge.wynning.tech/james/carol/pulls/223). ## Scope - In `apps/client/app/+html.tsx` (or a sibling registration helper), listen for the SW's `updatefound` event on the active registration. - When a new SW reaches `installed` state with a previous controller present (= new version waiting), expose the fact via a TanStack Query helper or a small `useServiceWorkerUpdate` hook. - Render a non-blocking toast in the universal client's layout — copy along the lines of "Carol has a new version. Reload to get it." with a primary "Reload" action and a dismiss affordance. Sentence case, no emoji, Carol's voice. - On reload, call `registration.waiting?.postMessage({ type: "SKIP_WAITING" })` and `window.location.reload()` after the new SW takes control. Handle the `SKIP_WAITING` message in `sw.js`. - DS tokens via `useTheme()`; strings via `react-i18next`. ## Acceptance criteria - [ ] After a deploy with a bumped `CACHE_VERSION`, the installed PWA shows the toast within a minute or so of the next route navigation (when the SW's update check fires). - [ ] Clicking the toast's "Reload" action loads the new shell on the next visit. - [ ] Dismissing the toast hides it for the session but it reappears on the next visit until the user reloads. - [ ] No layout shift or focus theft from the toast on small screens. ## Out of scope - Auto-reloading without prompt. Some users hate that and Carol's data is per-user; an unannounced reload could lose a draft. - Background sync of in-progress writes before reload. Carol's writes are all individual API calls that already complete or fail; the toast just gates the next render. ## Composes with - [#208](https://forge.wynning.tech/james/carol/issues/208) — PWA install + offline shell. - [#210](https://forge.wynning.tech/james/carol/issues/210) — nav shell, in case the toast lives in the same layout. ## Part of [#176](https://forge.wynning.tech/james/carol/issues/176)
james closed this issue 2026-06-24 12:43:29 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#225
No description provided.