Retail — grocery store automation
A till that keeps selling when the line goes down
Point of sale, stock and a central panel for small and mid-sized grocery stores. The till sells with no network at all; when the connection returns, the queued sales reach the server in a single request and none are lost.
Stack: Go · TypeScript · Electron · React · Next.js · SQLite · PostgreSQL · Docker
Measured results
- 61 ms
- 50 sales with the network cable pulled
- M1 run report, 22 August 2026
- 792 ms
- Sync after reconnection — a single push request
- M1 run report, 22 August 2026
- 51/51
- Events accepted by the server — 0 rejected, 0 retries
- M1 run report, 22 August 2026
- 217 ms
- Catalogue pull, one call
- M1 run report, 22 August 2026
- 126
- Contract tests — server output read against the client schemas
- Market handover note, 27 August 2026
The problem
When the till stops, the shop stops. There is a customer at the counter, and the queue does not wait.
The desktop systems that small and mid-sized grocery stores run on tend to repeat the same three things.
- Stock and pricing live in one program, selling in another. A transfer service bridges the gap once a minute. When that service stops, the till happily keeps selling a repriced product at yesterday’s price.
- The main menu carries around twenty-five modules. A small store might use eight of them. The rest is paid for in training time and in surface area for mistakes.
- The database sits on the store’s own computer. So installation means a technician visit and support means a remote desktop session.
All three rest on the same assumption: the till works as long as the system behind it is reachable. The moment the shop loses its connection, that assumption collapses.
The approach
We built the till so that it does not depend on the network. A sale is written to the local database, and the till never waits on the connection. That is not a performance optimisation but the rule the architecture is built around: closing a sale cannot depend on the state of a network.
When the connection comes back, the sync engine sends the queued records as one batch. Records leave the queue only when the server has accepted them — lost sales come from records assumed to have landed.
Ordering matters too: the event that opens the till session is queued before any sale, otherwise the server receives sales referring to a session it has never heard of and rejects the whole batch.
We treated installation as part of the problem as well. The installer is downloaded from the web and the application updates itself, which takes the technician visit out of the setup path.
What it does
The system today:
- Sells by barcode, prints a receipt, decrements stock. Payment is taken, a receipt number is issued, the sale closes.
- Works with no internet. Catalogue search, barcode lookup and closing a sale all run offline; the till does not treat a missing connection as an error state.
- Syncs itself when the line returns. Queued sales go up in a single request and the queue empties. Nobody presses a button.
- Is managed from a central panel. Catalogue, pricing and sales reports live on the server; tills pull the catalogue from it.
- Installs and updates itself. The installer comes off the web and later versions arrive on their own.
- Separates tenants at the database level. Each store's data is isolated with PostgreSQL row-level security, rather than leaving separation to the care of application code.
How it was verified
The milestone was written as a single sentence before any code existed: fifty sales are made on a machine with the network cable pulled, and when the cable goes back in, every one of them reaches the server.
The run was executed on 22 August 2026 against the till's production code — the very functions the on-screen buttons call. Nothing was mocked.
- The catalogue came down from the live server in 217 ms, in one call.
- With the cable out, 50 sales took 61 ms, made against the real products and prices just downloaded, barcode lookup included.
- 51 records accumulated in the outbound queue: 50 sales and 1 till-session opening.
- On reconnection the sync finished in 792 ms, in a single push request.
- The server accepted 51 of 51: no rejections, no transport errors, no retries. The queue ended the run at zero — no sale was lost.
Four days later the human end of the chain was walked as well: a sale rung up by barcode closed as receipt A0001, ₺217.30, cash, and the same record was read back from the server to confirm it.
The distribution path was tested separately: the installer was published to object storage and downloaded with no authentication — HTTP 200, 77.2 MB, with the release manifest verified by sha512.
Because the server and the till are written in different languages, the data contract is generated from a single source, and 126 contract tests read the server's real output against the client's schemas — so the claim that both ends speak the same wire is tested rather than hoped for.
Stack
Go · TypeScript · Electron · React · Next.js · SQLite · PostgreSQL · Docker