SERUYANGE / JOSEPH
OPEN TO REMOTE ROLES ONLINE
STATUS / AVAILABLEBASE / KAMPALA, UG
Build Log 01 / Offline-first sync

Building a ledger that reconciles 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 when connectivity returns.

FlutterDrift / SQLiteGoPostgreSQLMTN MoMo Sandbox
Role
Solo build
Status
Building
Planned pilot
Kampala market vendors
RoleSolo build
Target6 weeks
Status● Building
PilotKampala market vendors
01 / 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. At the end of a market day, reconciliation can happen by memory, or not at all.

02 / The constraint

Offline-first is the operating condition, not a fallback.

The core design starts from a harsher assumption: the connection may disappear for the entire period in which the user still needs to work.

The app has to be fully usable for an entire market day, recording sales, updating stock and calculating totals with zero connectivity, then sync cleanly whenever a signal appears for even a few seconds. Kampala's markets have patchy indoor coverage, so the design cannot make a sale wait for the network.

03 / Product model

The interface should never make the vendor think about synchronization.

The local database handles the immediate write. The sync state is visible as system status, but it does not block the workflow. The mock interface below visualises that model rather than presenting an implemented product screenshot.

LEDGER / LOCAL-FIRST CLIENTNETWORK / UNAVAILABLE
TODAY / LOCAL DEVICE

Sales ledger

3 WRITES QUEUED
RECORDED SALES18
LOCAL QUEUE03
LAST SYNC08:42
ENTRYAMOUNTSTATE
Sale / mobile moneyUGXQUEUED
Stock adjustmentlocalQUEUED
Sale / cashUGXQUEUED
04 / Architecture

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

Every write lands on the device first. A local queue retries opportunistically. When connectivity returns, the Go API receives queued operations, checks for conflicts and reconciles them against the server-side PostgreSQL store.

Flutter ClientDrift / SQLite
local writes
→
Sync Queueon-device
retry + backoff
⇢
Go Sync APIconflict check
LWW resolution
→
PostgreSQLserver source
of truth
// LOCAL WRITE IS ALWAYS AVAILABLE. NETWORK TRANSFER IS OPPORTUNISTIC.
05 / The tradeoff

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

The theoretically stronger conflict model is not automatically the correct product decision. For this first version, implementation cost and the expected writer pattern matter.

CHOSEN / V1

Last-write-wins

Fits the six-week target and the expected single-phone writer pattern for a stock line. It keeps synchronization logic small enough to test deeply before expanding the model.

NOT CHOSEN / V1

General-purpose CRDT

A more theoretically complete concurrent-editing model, but one that would roughly triple the timeline for a conflict pattern expected to be uncommon in the initial use case.

DECISION LOG / KNOWN FAILURE MODE

Two staff phones editing the same item inside the synchronization window can still create real data loss. That scenario is scoped as the first thing to fix in v2, likely with per-field version vectors rather than a general-purpose CRDT.

06 / What I am watching out for

Test the dangerous case before polishing the interface.

The multi-device conflict path is the risk that can invalidate the simplistic synchronization model, so the build plan deliberately brings that case forward.

BUILD NOTE / Create the multi-device conflict test harness before the UI. Surface the two-staff-phone edge case in week one rather than discovering it late.
Follow progress on GitHub ↗