feat: offline-first client
All reads now come directly from Dexie; all mutations write to Dexie + outbox immediately without waiting for the server. The background sync loop (every 30s) pushes the outbox and pulls server changes. Day/week close and reopen remain server-only (require server-side computation). triggerSync() is called after them to update Dexie promptly. The optimistic closedDaysMap update in the week page is kept separate from the Dexie reload to avoid a race that was causing the reopen button and day actions to disappear until a page reload. - client.ts: remove online-first fetch paths; all reads from Dexie - sync.ts: add triggerSync() and waitForSync() exports - DayDetail: pass ClosedDay | null to oninvalidate after close/reopen - week/+page.svelte: update closedDaysMap optimistically on close/reopen; only reload from Dexie on entry mutations - settings/+page.svelte: read history() directly (never throws 503); derive current locally - layout: remove offline banner and online.ts (behaviour is now identical online and offline)
This commit is contained in:
24
PLAN.md
24
PLAN.md
@@ -215,11 +215,13 @@ Three regions:
|
||||
|
||||
### Offline Strategy
|
||||
|
||||
Fully offline-first: the UI always reads from and writes to Dexie (IndexedDB) without touching the network. A background sync loop (every 30 s) reconciles with the server.
|
||||
|
||||
1. App shell precached by Workbox.
|
||||
2. All reads come from Dexie (IndexedDB); a sync worker reconciles with the server in the background.
|
||||
3. All mutations create a local Dexie record with a client-generated UUIDv7 and an entry in a local `outbox` table.
|
||||
4. When online, the outbox is flushed via `/api/sync/push`; responses update the local cache.
|
||||
5. `/api/sync/pull` fetches any server-side changes since `last_pulled_version`.
|
||||
2. **All reads** come directly from Dexie — no network latency in the hot path.
|
||||
3. **All mutations** write to Dexie + outbox immediately and return. The background sync loop pushes the outbox via `POST /api/sync/push` and pulls server changes via `GET /api/sync/pull`.
|
||||
4. **Exceptions**: day/week close and reopen require server-side computation and remain server-only. `triggerSync()` is called after them to update Dexie promptly without waiting 30 s.
|
||||
5. On `410 Gone` (server pruned data the client hasn't seen), `coldStart()` wipes Dexie and re-pulls everything from version 0.
|
||||
|
||||
## 4. Sync Protocol
|
||||
|
||||
@@ -285,7 +287,17 @@ Full offline support with online-first, offline-fallback mutation strategy.
|
||||
- `db.ts`: Dexie v3 — `settings_history` key path changed to `id` (string); upgrade handler clears the table for repopulation via pull.
|
||||
- Settings page: `editingId` and ID params updated from `number` to `string`.
|
||||
|
||||
### M10 — Future
|
||||
### M10 — Offline-first client ✅
|
||||
|
||||
Switched from online-first/offline-fallback to fully offline-first.
|
||||
|
||||
- `client.ts`: all reads now come directly from Dexie (no server fetch). All mutations write to Dexie + outbox immediately and return without waiting for the server.
|
||||
- `sync.ts`: added `triggerSync()` for imperative sync after server-only mutations. Updated comment header.
|
||||
- `DayDetail.svelte`, `week/+page.svelte`: call `triggerSync()` after day/week close and reopen so Dexie reflects server-computed state promptly.
|
||||
- `+layout.svelte`: removed offline banner (no longer meaningful; behaviour is identical online or offline).
|
||||
- `online.ts`: deleted (unused).
|
||||
|
||||
### M11 — Future
|
||||
CSV/JSON export, monthly summary view.
|
||||
|
||||
## 7. Decisions & Rationale
|
||||
@@ -308,4 +320,4 @@ CSV/JSON export, monthly summary view.
|
||||
| Settings history PK | TEXT UUID (migration 003) | Consistent with other entities; enables offline create; `updated_at` enables last-write-wins sync |
|
||||
| Sync prune strategy | Prune marker row at boundary version | No extra table; client detects stale state from the log itself; 410 triggers full re-sync |
|
||||
| Sync conflict resolution | Last `updated_at` wins | Server is authoritative; simple to implement and reason about for single-user |
|
||||
| Offline mutation flow | Online-first, offline-fallback | Server is primary; client writes to Dexie+outbox only on network failure; simpler than full local-first |
|
||||
| Offline mutation flow | Offline-first | All reads/writes go through Dexie; background sync loop reconciles with server. Day/week close remains server-only (requires server computation); `triggerSync()` called after. |
|
||||
|
||||
Reference in New Issue
Block a user