# ADR-021: Service Worker Uses Cache-First Strategy with Network Fallback

## Status

Accepted, 2026-06-25.

## Status History

```yaml
status_history:
  - date: 2026-06-25
    status: Proposed
    changed_by: hkl
    reason: Formalising SW cache strategy documented in pwa_dev_style.md Q0.10
    changed_via: adr-kit (360lm)
  - date: 2026-06-25
    status: Accepted
    changed_by: hkl
    reason: All PWA service workers use cache-first; explicitly marked [standard]
    changed_via: adr-kit (360lm)
```

## Context

Service workers intercept network requests and can serve responses from cache or the network. The choice of strategy determines offline behaviour and update latency. 360lm PWAs must work fully offline (ADR-020) and must serve fast on poor connections — both requirements favour cache-first. The risk of stale assets is managed by CACHE_VER bumps (ADR-005), not by network-first fallback.

The strategy documented in `pwa_dev_style.md §Q0.10`:
> "Cache-first, network fallback, fallback to cached `/` on failure"

## Decision

All 360lm PWA service workers MUST use cache-first strategy for static assets:

1. **Cache-first for CORE_ASSETS** (HTML, CSS, JS, images, self-hosted libs): serve from cache immediately; do not hit the network unless the asset is missing from cache.
2. **Network-fallback**: if asset not in cache, fetch from network and cache the response.
3. **Root fallback**: if network also fails, return cached `/index.html` (offline shell).
4. **API calls (`/db/`, `/tour-pg-proxy/`, etc.) are NOT cached** — they always go to the network; offline data comes from IndexedDB (ADR-020), not from cached API responses.

**Decision Maker:** hkl

## Alternatives Considered

- **Network-first for all requests.** Rejected: defeats offline support — every request requires network; slow on poor connections; first load on bad connectivity shows spinner, not cached UI.
- **Stale-while-revalidate.** Rejected: serves stale cache immediately but fires a background network request to update it; adds complexity; CACHE_VER already handles updates cleanly without background revalidation traffic.
- **Network-first for HTML, cache-first for assets.** Rejected: HTML is the entry point; if the network is down and HTML is network-first, the app shows an offline error screen before any JS runs — IndexedDB data becomes inaccessible. Cache-first for HTML ensures the app shell always loads.
- **No service worker (no offline).** Rejected: field use case requires offline support (see ADR-020).

## Consequences

**Positive:**
- App loads instantly from cache on repeat visits — zero network round-trip for the shell.
- Fully offline after first load: JS, CSS, HTML, and icons all served from cache.
- Consistent performance on slow connections — user sees the UI immediately.

**Negative / Trade-offs:**
- Stale assets served until CACHE_VER is bumped — developer MUST increment CACHE_VER on every deployment (see ADR-005).
- First load requires network to populate cache — no offline on first ever visit.
- Cache size grows with each PWA; total cached assets across 20 PWAs can be significant on the device.

**Risks and mitigations:**
- API responses accidentally cached by a catch-all fetch handler: mitigated by explicitly excluding `/db/` and proxy paths from the cache handler — only CORE_ASSETS go through cache-first.
- Cached root fallback serves wrong PWA shell (e.g., hub shell from a recce SW): mitigated by each PWA having its own SW with its own CACHE_VER key and its own CORE_ASSETS list.

## Related Decisions

- ADR-005 (SW CACHE_VER) — cache-first only works correctly for updates because CACHE_VER invalidates the old cache.
- ADR-020 (offline-first IndexedDB) — SW cache-first serves the app shell offline; IndexedDB serves the data offline. Together they enable full offline use.
- ADR-013 (single HTML file) — one file = one CORE_ASSETS entry = simple cache manifest.

## References

- `pwa_dev_style.md` — Q0.10: "Cache-first, network fallback, fallback to cached / on failure"
- `installation/releases/v1.0.0/service-worker.js` — CORE_ASSETS and fetch handler reference
- All `*/sw.js` files — follow this strategy
