Offline Outbox & Optimistic Writes
src/lib/offline/db.ts defines the Dexie (IndexedDB) database — the sole client-side store (constitution §7.14.1). It holds a read cache of the user’s data plus the persistent mutation outbox. It never stores auth tokens, credentials, or secrets (§7.14.5).
The diagram below shows the write side: optimistic API → Dexie cache + outbox → replay to D1.
flowchart LR
UI["Islands / UI"] -->|"createList(), renameList(),<br/>deleteList(), moveList(), …"| API["sync.ts<br/>optimistic write API"]
API -->|"1. apply optimistically"| DX[("Dexie read cache")]
API -->|"2. enqueue OutboxEntry"| OB[("Dexie outbox table")]
OB -->|"buildRequest(entry)<br/>sync-core.ts"| REQ["HTTP request<br/>+ Idempotency-Key"]
REQ -->|"3. replay when online"| HONO{{"Hono API"}}
HONO --> D1[("D1 — source of truth")]
D1 -.->|"idempotencyKeys table<br/>dedupes replays"| HONO
Outbox
Section titled “Outbox”- The
outboxtable (OutboxEntry) queues mutations made locally, indexed bycreatedAtandentityId(for dependency lookups). The Dexie schema is versioned (v1…v8+) as read models were added per feature. - Each entry carries a stable Idempotency-Key so replays are deduplicated server-side (the
idempotencyKeysD1 table) — replaying the outbox is safe and idempotent (constitution §7.14.3). buildRequest(entry)(insync-core.ts) turns an outbox entry into the HTTP request the replay sends.
Optimistic write API
Section titled “Optimistic write API”src/lib/offline/sync.ts exposes the user-facing mutations (createList, renameList, deleteList, moveList, duplicateList, createGroup, deleteGroup, moveGroup, …). Each:
- applies the change to the Dexie cache and shows it immediately (optimistic);
- enqueues an outbox entry;
- replays to the server when online, where it is reconciled into D1 (the source of truth).
When offline, writes queue and replay on reconnect; there is no on-device primary database and no sync engine — the client is a cache plus outbox (Product Brief §1.1).
A single optimistic mutation flows from instant local apply through deferred replay:
sequenceDiagram participant U as "User" participant S as "sync.ts" participant DX as "Dexie (cache + outbox)" participant H as "Hono API" participant D1 as "D1" U->>S: "renameList(id, title)" S->>DX: "apply to cache (optimistic)" S->>DX: "enqueue OutboxEntry (Idempotency-Key)" Note over U,DX: "shown immediately — works offline" S->>S: "buildRequest(entry)" S->>H: "replay when online" H->>D1: "apply + record idempotencyKey" D1-->>H: "ok (replays deduped)" H-->>S: "confirmed → mark synced"
Related: Sync-Core Reconcilers (the read/fold side), DB Schema & Notify-Affected (the server tables + idempotency).