Runbook — PWA outbox won't drain after reconnect
Symptoms
- Pending indicator on transactions / banks / categories stays lit even though the device is back online.
- New writes work; older queued writes never reach the server.
- DevTools shows entries in IndexedDB under the app's database name.
Diagnose
- Network: is the app actually online? Service worker may be serving cached responses. DevTools → Application → Service Workers → check status.
- IndexedDB: DevTools → Application → IndexedDB → open the app DB → inspect the outbox store. Look for entries with old timestamps and an error/retry field.
- Sync hook:
useOutboxSync should kick on online event. Check console for sync errors.
- Server response: a 4xx (e.g. validation failure on a stale schema) will keep retrying forever. Check
src/lib/offline/sync.ts retry policy and watch network tab for the actual failing request.
Fix
Bad payload (most common)
A queued entity references something deleted server-side (e.g. category that no longer exists). Two options:
- Edit the IndexedDB entry directly in DevTools to point at a valid category, then trigger sync.
- Delete the stuck entry from the outbox store and re-enter the transaction manually.
Service worker stale
DevTools → Application → Service Workers → Update or Unregister, then hard reload (Cmd+Shift+R). On next load the new SW takes over.
Server-side schema drift
If a model field was renamed/removed since the device queued the write, the server merge will reject it. Either roll the server back, ship a tolerant merge handler in src/lib/offline/merge*.ts, or drop the entry.
Prevention
- New entity types must register in
entity-dispatch.ts and have merge logic in src/lib/offline/merge*Outbox.ts.
- Server-side merges should be idempotent — outbox can replay the same write.
Related
- [[Projects/personal-finance-notion/changelog/2026-04-19-offline-first-writes|Offline-first writes changelog]]