MOB-09 — Offline foundation for the delegate app (local DB + outbox sync)
hoco_offline: drift database for tours/visits/resellers/tasks, outbox of idempotent mutations with ordered replay through /sync/batch, delta pulls, conflict surfacing, sync status stream.
| Field | Details |
|---|---|
| Task code | MOB-09 |
| Phase | P0 — Foundations (cadrage, UX/UI, architecture & contract) |
| Product scope | V1 |
| Status | Planned |
| Start | Thu 29 Oct 2026 |
| Due | Wed 4 Nov 2026 |
| Responsible | Imad — Mobile developer (Flutter) |
| Co-responsible | — |
| Effort | 2.5 person-day(s) planned · 8 from scratch · size M |
| Area · app | Mobile foundation · mobile |
| Depends on | MOB-03 |
| Unblocks | MDA-01, MDA-11 |
| Milestones | — |
Summary
hoco_offline: drift database for tours/visits/resellers/tasks, outbox of idempotent mutations with ordered replay through /sync/batch, delta pulls, conflict surfacing, sync status stream.
Description
Delegates work in shops with weak or no network, so the delegate app must keep the tour, visits, notes, photos and new prospects locally and replay every action exactly once when the connection returns (SYN-03…SYN-06). Scope: hoco_offline — an encrypted drift database for the delegate's offline scopes, an outbox of idempotent mutations written in the same transaction as the local change, ordered replay through sync.batch (≤ 50 items, occurredAt, per-item Idempotency-Key), delta pulls through sync.delta, conflict and rejection surfacing, and a sync-status stream. Screens using it are MDA-*; the sync centre UI is MDA-11. It needs SRV-23 semantics from Anis (batch item shape, SyncItemResult values APPLIED/REPLAYED/CONFLICT/REJECTED/RETRY, delta cursors, 72 h occurredAt window) at least on Prism first. MDA-02…MDA-10 write through this outbox and MOB-11 hooks its upload queue into it.
Subtasks
| # | Subtask | Owner |
|---|---|---|
| 1 | Drift schema v1: tours, visits, visit_checklist_items, visit_photos, visit_reports, resellers, reseller_tasks, invitations, reference_items, outbox_mutations, sync_cursors; SQLCipher encryption with the key in secure storage | Imad |
| 2 | outbox_mutations table: id (UUIDv7), op (e.g. delegate.visits.arrive), method, path, body JSON, idempotencyKey, occurredAt, entityType, entityId, dependsOn, status (pending, sending, applied, replayed, conflict, rejected, retry), attempts, errorCode, errorMessage | Imad |
| 3 | OfflineRepository API: apply the optimistic local write and enqueue the mutation in one drift transaction; client-generated UUIDv7 ids for prospects, unplanned visits, photos, reports and notes | Imad |
| 4 | SyncEngine: triggers on enqueue when online (SYN-03), connectivity regained, app resume and a 15-min WorkManager job; FIFO per entity, batches of ≤ 50 sent to POST /sync/batch with a batch Idempotency-Key | Imad |
| 5 | Per-item result handling: APPLIED/REPLAYED → done and local record reconciled with the server copy; RETRY → backoff; CONFLICT/REJECTED (e.g. VISIT_LOCKED, PHONE_ALREADY_EXISTS, OCCURRED_AT_OUT_OF_RANGE) → item parked, dependants held, entity flagged for the sync centre | Imad |
| 6 | Delta pulls GET /sync/delta per scope (tours, visits, resellers, tasks, reference lists) with stored cursors; server copy wins for entities without pending mutations, pending mutations re-applied on top | Imad |
| 7 | Initial snapshot at sign-in and each morning from delegate.tours.today so the day works fully offline | Imad |
| 8 | SyncStatus stream (pendingCount, errorCount, oldestPendingAge, lastSyncedAt) for SyncStatusPill and the closing guard; warning when a pending item approaches the 72 h occurredAt limit | Imad |
| 9 | Schema migration strategy (drift schemaVersion, generated migration tests) and wipe-on-sign-out only when the outbox is empty | Imad |
| 10 | Review of the batch item shape, result codes and delta format with Anis before SRV-23 is frozen | Anis |
Deliverables
- packages/hoco_offline/lib/src/db/ (drift tables, DAOs, migrations)
- packages/hoco_offline/lib/src/outbox/outbox.dart and sync_engine.dart
- packages/hoco_offline/lib/src/delta/delta_puller.dart
- packages/hoco_offline/test/ (outbox, engine and migration tests)
Acceptance criteria
- In airplane mode a full visit (arrive, start, checklist, note, photo, report, end) is saved locally and survives an app kill (SYN-04)
- On reconnection the queued mutations are sent in their original order within 10 s, with their original occurredAt and Idempotency-Key (SYN-03, SYN-05)
- Replaying the same batch twice creates nothing twice: the second pass reports REPLAYED for every item
- A rejected item (e.g. VISIT_LOCKED) stops its dependants, keeps the local data and is listed with its error code; other entities keep syncing (SYN-05, SYN-06)
- pendingCount and errorCount on the status stream match the outbox at all times; sign-out is refused while pendingCount > 0
- A pending item older than 48 h raises a warning before the server's 72 h OCCURRED_AT_OUT_OF_RANGE limit
Requirements covered
| Requirement | Title | Release |
|---|---|---|
| SYN-03 | Critical data sent immediately when a connection is available | V1 |
| SYN-04 | Delegate app keeps the tour, visits, notes and photos locally under weak network | V1 |
| SYN-05 | Sync queue handling retries, duplicates (idempotency), errors and conflicts | V1 |
| SYN-06 | Visible sync state: synced, pending, error | V1 |
API surface
| Endpoint group | Operations |
|---|---|
| Offline sync & location | 3 |
Test measures
- Unit tests of the SyncEngine with a fake SyncApi: ordering, batching at 50, RETRY backoff, CONFLICT parking and dependency holds
- Drift migration tests generated with drift_dev schema dumps
- integration_test: offline visit → kill → relaunch → reconnect → server state verified against Prism or staging
Risks
- Ordering bugs between dependent mutations (photo before visit arrive, report before end) — explicit dependsOn links and tests per chain
- SQLCipher increases APK size and cold start — measure on a mid-range phone; fall back to an unencrypted DB plus device encryption if the cost is too high (decision with Anis)
KPI
- Delivered by Wed 4 Nov 2026 within 2.5 person-day(s).
- Every acceptance criterion above demonstrated before hand-off; linked requirements' acceptance criteria pass.
- Zero open critical defects, permission gaps or data-integrity regressions at sign-off.
Generated from content/plan/tasks.json by scripts/generate-team-tasks.mjs. Dates are computed by the scheduler (Sunday–Thursday weeks, holidays excluded) — change the plan, not this page.
MOB-08 — Push notifications foundation
Firebase projects per flavor, FCM token registration, foreground/background handling, notification → deep-link routing.
MOB-10 — Location, maps & consent foundation
Permission and consent flow, location service with accuracy handling, tolerance check helper, Google Maps setup per flavor, launch of external navigation, GPS-off and out-of-tolerance states.