Build Log 01 · Offline-first sync

Building a ledger that reconciles mobile money sales even when the vendor never had signal.

A local-first Flutter app and Go sync service designed to let small vendors record sales offline all day, then reconcile automatically the moment connectivity returns.

Role
Solo build
Target
6 weeks
Status
● Building
Planned pilot
Kampala market vendors
Flutter Drift (SQLite) Go PostgreSQL MTN MoMo Sandbox
Follow progress on GitHub ↗
The problem

Vendors reconcile by memory, not by record.

Small vendors using MTN and Airtel Mobile Money have no easy way to check mobile money transactions against actual stock and sales. Most tools assume the vendor has constant data — most vendors don't. At the end of a market day, reconciliation happens by memory, or not at all.

The constraint
CONSTRAINT

The app has to be fully usable for an entire market day — recording sales, updating stock, calculating totals — with zero connectivity, then sync cleanly whenever a signal appears for even a few seconds. Kampala's markets have patchy indoor coverage; "offline-first" here isn't a resilience feature, it's the default operating condition the whole design has to start from.

Architecture

Local-first client, sync queue, source of truth.

Flutter Client Drift (SQLite) local writes Sync Queue on-device retry + backoff Go Sync API conflict check LWW resolution PostgreSQL source of truth always when online writes market vendor stateless, horizontally scalable

// Every write lands locally first. The sync queue drains opportunistically — no user-facing "connecting..." state ever blocks a sale.

The tradeoff

Last-write-wins over a full CRDT — for v1.

DECISION LOG

I'm planning last-write-wins conflict resolution over a full CRDT for v1. A CRDT is the theoretically correct answer, but it would roughly triple the timeline for a conflict pattern most vendors would trigger rarely — a single phone is normally the only writer for a given stock line. The scenario where this creates real data loss (two staff phones editing the same item within the sync window) is scoped as the first thing to fix in v2, likely with per-field version vectors rather than a general-purpose CRDT.

What I'm watching out for
Building the multi-device conflict test harness before the UI, not after — the plan is to surface the two-staff-phone edge case in week one rather than discover it late.
Links
Follow progress on GitHub ↗