"The outbox is the whole trust model in one object store: things you've committed, waiting to be delivered."
Why IndexedDB, Not localStorage
For durable phone-side capture, the storage choice isn't cosmetic. localStorage is synchronous, string-only, and small — it blocks the main thread and can't hold image blobs. IndexedDB is the right tool: it's asynchronous (never freezes the UI), transactional (the atomic aggregate from the last lesson depends on it), and it stores structured objects and binary Blobs directly. For a client whose whole job is holding questions-with-images safely across app restarts, that combination isn't a nice-to-have — it's the foundation.
The Outbox Pattern
The core structure is an outbox: a durable queue of captured turns, each tagged with a sync status. It's the messaging-world outbox pattern applied on-device. The flow is a loop that's safe to interrupt at any point:
- Enqueue: a submitted turn lands in the outbox as
pending, atomically with its blobs. - Drain: a worker picks up
pendingturns and attempts delivery, moving them tosending, thenansweredorfailed. - Resume: on app launch, the worker re-scans the outbox for anything not yet
answeredand continues where it left off.
Because the outbox is durable, none of these steps assumes the previous one finished in the same session. Close the app between enqueue and drain, and the drain simply happens next launch. The queue is the memory.
The Outbox Is Ground Truth for the Phone
On the device, the outbox isn't a cache of what the server knows — it's the authoritative record of what the user has asked, independent of whether the server has heard yet. That's why it can survive offline for hours: it doesn't need the server to be the source of truth for local state. (The backend remains the source of truth for canonical conversations — the outbox is the phone's durable capture layer, a different job. Track 5 draws that line precisely.)