HOCO B2B
Team tasksImad

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.

FieldDetails
Task codeMOB-09
PhaseP0 — Foundations (cadrage, UX/UI, architecture & contract)
Product scopeV1
StatusPlanned
StartThu 29 Oct 2026
DueWed 4 Nov 2026
ResponsibleImad — Mobile developer (Flutter)
Co-responsible—
Effort2.5 person-day(s) planned · 8 from scratch · size M
Area · appMobile foundation · mobile
Depends onMOB-03
UnblocksMDA-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

#SubtaskOwner
1Drift 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 storageImad
2outbox_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, errorMessageImad
3OfflineRepository 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 notesImad
4SyncEngine: 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-KeyImad
5Per-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 centreImad
6Delta 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 topImad
7Initial snapshot at sign-in and each morning from delegate.tours.today so the day works fully offlineImad
8SyncStatus stream (pendingCount, errorCount, oldestPendingAge, lastSyncedAt) for SyncStatusPill and the closing guard; warning when a pending item approaches the 72 h occurredAt limitImad
9Schema migration strategy (drift schemaVersion, generated migration tests) and wipe-on-sign-out only when the outbox is emptyImad
10Review of the batch item shape, result codes and delta format with Anis before SRV-23 is frozenAnis

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

RequirementTitleRelease
SYN-03Critical data sent immediately when a connection is availableV1
SYN-04Delegate app keeps the tour, visits, notes and photos locally under weak networkV1
SYN-05Sync queue handling retries, duplicates (idempotency), errors and conflictsV1
SYN-06Visible sync state: synced, pending, errorV1

API surface

Endpoint groupOperations
Offline sync & location3

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.

On this page