# ADR-020: Field PWAs Are Offline-First — IndexedDB Primary, Server Sync Secondary

## Status

Accepted, 2026-06-25.

## Status History

```yaml
status_history:
  - date: 2026-06-25
    status: Proposed
    changed_by: hkl
    reason: Formalising offline-first standard documented in pwa_dev_style.md Q0.3
    changed_via: adr-kit (360lm)
  - date: 2026-06-25
    status: Accepted
    changed_by: hkl
    reason: All field-capture PWAs follow this pattern; explicitly marked [standard]
    changed_via: adr-kit (360lm)
```

## Context

360lm field workers (installation crews, recce agents, activity reporters) operate in locations with poor or absent mobile data — warehouse loading docks, rural Tier 3 towns, basement retail spaces. A purely online PWA would be unusable when connectivity drops mid-form. The alternative is to capture all data locally first and sync when connectivity returns. This requires a client-side database. IndexedDB is the only browser-native persistent key-value store available in service worker context, making it the natural choice for offline-first PWAs.

This applies specifically to **field-capture PWAs** (recce, activity, installation, expense). Management and admin PWAs (hub, admin, finance) are online-required because they aggregate data from multiple sources and do not have an offline capture use case.

## Decision

All field-capture PWAs MUST be offline-first: data is written to IndexedDB immediately on user action, and synced to PostgREST in the background. The app must be fully usable (capture, save, view previous submissions) with no network connection. Server sync is a best-effort background operation — failures are queued and retried, never blocking the user.

PWAs that are management/admin dashboards (not field capture) may be online-required — they do not need IndexedDB.

**Decision Maker:** hkl

## Alternatives Considered

- **Online-required: write directly to PostgREST, show error on failure.** Rejected: unacceptable for field use — a failed network write loses user's data mid-form; field workers cannot reliably re-enter 10-photo recce submissions.
- **localStorage for offline storage.** Rejected: 5 MB limit is too small for photo blobs + submission queues across months of offline use; no structured query capability; no transaction support; synchronous API blocks the main thread.
- **Cache API (via Service Worker) for data storage.** Rejected: Cache API is designed for HTTP response caching, not structured application data; no query/filter capability; unsuitable for managing submission queues with status (pending/synced/failed).
- **No offline support — require connectivity.** Rejected: field reality makes this unworkable; connectivity tests during development showed frequent drops in target locations.

## Consequences

**Positive:**
- User can complete full form submission (including photos) with no connectivity.
- Submission queue drains automatically when connectivity returns — zero user action required.
- App is usable in any connectivity condition; network quality does not affect UX.

**Negative / Trade-offs:**
- Each field PWA needs its own IndexedDB schema design (stores, indexes, versioning).
- Sync conflict resolution must be handled — if the same record is modified offline on two devices, one will overwrite the other on sync.
- More complex than a simple PostgREST POST — requires queue management, sync status UI, and retry logic.

**Risks and mitigations:**
- IndexedDB version migration bugs corrupt local data: mitigated by incrementing `DB_VERSION` and writing explicit `onupgradeneeded` handlers; never delete stores, only add.
- Photo blobs fill device storage: mitigated by deleting local blobs after confirmed server upload; show storage warning if IDB size exceeds threshold.
- Sync silently fails for months: mitigated by sync status indicator in UI (pending count badge) and periodic retry on app open.

## Related Decisions

- ADR-005 (SW CACHE_VER) — service worker caches app shell; IndexedDB stores app data. Complementary.
- ADR-021 (SW cache-first strategy) — service worker serves offline app shell; IndexedDB provides offline data.
- ADR-014 (PostgREST API) — sync target for IndexedDB data.

## References

- `pwa_dev_style.md` — Q0.3 Storage priority: "Offline-first: IndexedDB primary, server sync secondary [standard]"
- `recce/index.html` — reference implementation of IDB + sync pattern
- `installation/releases/v1.0.0/service-worker.js` — CORE_ASSETS caching for offline shell
