LEDGERPAD
An app for running a small business from a phone. What you spent, what you sold, what stock is left, who ordered what, and whether the week actually made money.
- Role
- Product, mobile engineering, sync architecture
- Stack
- Expo and React Native, SQLite on device, Postgres behind an adapter
- Status
- Early. The core loop works

The books are a notebook, and the signal is gone.
A food vendor knows their business precisely and keeps almost none of it written down. Stock is in their head, expenses are in a notebook, orders are in WhatsApp, and the question of whether the week made money takes an evening with a calculator, if it gets asked at all.
The obvious fix is an app, and the obvious app fails immediately. Bookkeeping software assumes an accountant. Point-of-sale software assumes a counter, a card reader and a connection. A market stall has none of those and loses signal ten times a day.
So the constraint came first: it has to work with no network, at the moment of the sale, on a cheap Android phone. Everything else in the design follows from that.
What we built
FOUR PARTS.
Offline-first core
Not offline-tolerant. The phone is the source of truth, and the network is an optimisation.
- SQLite on the device backs every screen, with no loading state waiting on a server
- The app runs end to end with no backend, no keys and no account
- Every mutation writes locally and queues its push in one transaction
- A lost connection is a normal condition, not an error state
Sync engine
Small, boring and inspectable. Sync is where offline apps usually go wrong, so it got the least clever code in the project.
- Outbox for outbound writes, per-table watermarks for inbound
- Push and pull are separate, so a failing pull cannot block a sale
- Talks to interfaces only, never to a vendor SDK
- Tested without a device and without a network
The data model
Three decisions here carry the product. Each one came from watching how a vendor actually describes their own stock.
- One items table, where each item declares whether it is sold, bought, or both
- Money held as integer minor units, never as a float
- Stock derived from an append-only movement ledger, never stored as a number
- A generic small-business core, so a shop or a tradesperson needs no migration
Portability
The backend is currently Postgres on Supabase. That is a hosting choice, and it was built so it stays a choice.
- Only one folder is allowed to import a backend SDK
- CI fails the build if anything else does
- Server schema is plain SQL with no vendor-specific functions
- Moving host means writing one folder and changing one import
DECISIONS THAT CARRY IT.
Stock is derived, never stored
Two phones can sell the last three plates while both are offline. Because on-hand is a sum over an append-only ledger, the movements add up to minus three rather than one phone's idea of the truth overwriting the other. The vendor sees that they oversold, which is what actually happened. It is the one conflict that costs real money.
Money is integer minor units
Kobo and cents, never floats. A naira that rounds differently on two devices is the kind of bug a vendor finds before we do, and once they find it they stop trusting the totals.
Every item declares what it is
Sold, bought, or both. A caterer sells rice by the kilo and also cooks with it, so forcing that choice would mean entering rice twice. One answer decides which list an item appears in, whether it carries a selling price, and which picker offers it.
It has to work at the point of sale
A market stall loses signal ten times a day. An app that fails while a customer is standing there does not get opened again, so there is no path through the product that depends on a request succeeding.
Where it stands
Early, and honest about it. The core loop works: create a business, add items, record sales and expenses, watch stock move, take orders through to delivery, and read the numbers back. It is our own product rather than client work, which is also why it is the clearest look at how we build.
NEED SOMETHING THAT WORKS WITHOUT A NETWORK?
Offline-first is a whole architecture, not a cache. If your users are in the field, on a stall or down a mine, tell us what they are up against.
Start a project