# ADR-005: Service Worker Cache Busting via CACHE_VER String, Not skipWaiting()

## Status

Accepted, 2026-06-25.

## Status History

```yaml
status_history:
  - date: 2026-06-25
    status: Proposed
    changed_by: hkl
    reason: Formalising SW versioning pattern established after stale-asset incidents
    changed_via: adr-kit (360lm)
  - date: 2026-06-25
    status: Accepted
    changed_by: hkl
    reason: All 20 PWAs use CACHE_VER; skipWaiting() removed from all SWs
    changed_via: adr-kit (360lm)
```

## Context

Service Workers cache PWA assets for offline use. When a new version is deployed, the old SW continues serving stale assets to active tabs until the user closes all tabs and reopens the PWA. Early 360lm PWAs used `skipWaiting()` in the SW install handler to force immediate activation — this caused a race condition where the new SW activated mid-session, the old cache was deleted, and in-flight requests returned errors. Users reported "broken" PWAs after deployments. The fix was to use a versioned cache name (`CACHE_VER = 'pwa-name-vNN'`) — when the version string changes, the new SW creates a new cache, the old SW's cache is deleted on activation, and clients are served fresh assets on next load without mid-session disruption.

## Decision

Every PWA SW must define a `CACHE_VER` constant (e.g., `const CACHE_VER = 'tour-pg-v43'`). Increment the version suffix on every deployment that changes cached assets. Do NOT use `skipWaiting()` in any SW. The SW install event caches all assets under the new `CACHE_VER` key; the activate event deletes all caches not matching the current `CACHE_VER`.

**Decision Maker:** hkl

## Alternatives Considered

- **skipWaiting() for immediate activation.** Rejected: causes mid-session cache deletion → in-flight requests 404 → user sees broken PWA. Confirmed incident in production.
- **No versioning (always overwrite same cache key).** Rejected: browser may serve mix of old and new assets within the same cache entry during update; unpredictable behaviour.
- **Hash-based cache naming (auto-generated from file content).** Rejected: requires a build step; this project has no build pipeline — all PWAs are single HTML files edited directly. // ponytail: upgrade trigger=build pipeline introduced
- **Prompt user to refresh (postMessage from SW).** Rejected: adds JS complexity in every PWA; CACHE_VER bump achieves the same result with zero runtime code.

## Consequences

**Positive:**
- Zero mid-session disruption on deployment.
- Simple: one constant to increment, no build tooling required.
- Easy to audit: `grep CACHE_VER` shows current version of every PWA.

**Negative / Trade-offs:**
- Developer must remember to increment `CACHE_VER` on every deployment touching cached files.
- Users on old version continue using old cache until they close and reopen the PWA (acceptable — next open gets fresh assets).

**Risks and mitigations:**
- Forgetting to increment CACHE_VER: stale assets served until someone notices. Mitigated: Playwright test suite checks `CACHE_VER` is at or above a minimum version.

## Related Decisions

- None.

## References

- `memory/project_arch.md` — SW cache versions table across all PWAs
- All `*/sw.js` files — each defines `CACHE_VER`
