What we learned building offline first
"Offline mode" is a promise most apps break in the same place: they let you type, and then they lose it. We started from the other end — the device is the source of truth and the server is a courier — and the hard part turned out not to be the syncing.
The hard part is what you show
Two people edit the same expense while both are out of signal. One says Rs 12,000, the other says Rs 12,400. Any automatic rule — last write wins, highest wins, server wins — silently destroys somebody's intent, and the person whose number vanished has no way to know it ever existed.
We keep both. You pick. Both versions stay in that expense's edit history, so the decision is visible afterwards.
Balances that already include what is queued
The second rule fell out of the first. If a change is saved on your device, it is real, so it counts. The amber banner tells you how many changes are still waiting to sync, but the arithmetic on screen is already the arithmetic you will settle by. Nothing is held back pending a round trip.
The unglamorous parts
Ordered mutation queues, idempotent writes, a schema mirrored name-for-name between the Flutter and web clients so the two can never disagree about a column. None of it is interesting until the moment it stops you losing an evening's expenses in a valley with no signal.