An offline-first till that cannot be stopped by the internet
The shop has to keep selling when the line dies, and nothing may be lost when the power goes with it.
| Qty | Item | Amount |
|---|---|---|
| 1 | Arabic coffee 250g | 3.750 |
| 2 | Basmati rice 5kg | 12.400 |
| 1 | Dates, medjool 1kg | 2.250 |
| 4 | Bottled water 1.5L | 8.900 |
| Net · VAT 5% | 27.300 · 1.365 | |
| Total | 28.665 | |
Interface rendered in code from the platform’s own structure — illustrative, not a screenshot. Basket contents are invented.
The problem
A desktop point-of-sale client for the same platform. Retail in the Gulf does not stop because a connection does, and a till that refuses to sell is worse than no till at all. The hard part is not working offline — it is coming back online without losing or duplicating a single sale.
In active development and pre-pilot — not in production, and not part of the twenty-plus live businesses above. I am saying so here rather than letting the two blur together.
What I built
I architected and own it end to end: the failure model, the sync protocol, the security design, the technology choices, the written standards and the review gate. The code itself is AI-assisted, written under those standards and reviewed by me. I say that plainly because it changes what you should ask me about, and I would rather you asked. Every review decision is recorded. Two are worth reading: the offline sign-in design I killed after reading how the server hashes passwords, and a dependency race I caught that would have shipped an unencrypted database with nothing visibly wrong.
Data flows two ways, and the two never meet. Products and prices come down from the server. Sales go up from the till. The till is never the authority on anything it receives, so two versions of the same record can never disagree. There is no conflict-resolution code in the system because the design makes conflicts impossible, which is cheaper than handling them. The till gives every document its own unique id before sending it. That makes the retry rule simply "send it again" — the server recognises a repeat and ignores it, so there is no did that work? logic to get wrong. The local database is encrypted, and its key is held by Windows rather than stored inside the database it protects. A copy is escrowed so a dead machine does not mean lost sales.
An outbox that never deletes
Every sale is written in one order: a line in a journal file, flushed to disk; then the sale and its outbox row in one transaction; only then the receipt; and the upload in the background. A crash after the journal leaves a sale the journal can replay, and the journal can rebuild the whole local database on its own. The outbox rows move from pending to sending to acknowledged to archived, and stop. They are never deleted on success, because "delete after success" means a bug in the success path silently destroys the only copy of a sale. A sale the office refuses is held for review with its complete original payload, forever.
A cursor that moves with the data
Coming down, the obvious design is "send me everything changed since this time". It fails three ways: a shop PC's clock can be wrong, a row committed by a transaction that started before the last pull is skipped forever, and the wire format gets tied to a clock. So the server hands the till an opaque cursor that the till stores and echoes back but never reads. Each page is applied in one transaction with the cursor saved inside it, so the cursor moves with the data or not at all and a power cut leaves a matching pair. A first download is resumable: a till holding 8,000 of 12,000 products when the line dies can still sell those 8,000.
Never silent, never a pop-up
A system that trades through failure can also hide it. A stale till says so in words, keyed to the stalest of prices, stock and customer balances, never the newest download of anything, and it is never red, because a stale till still sells. In early runs, eight sales waited silently behind a register left open on the web, and the only way to find out was to read the server's database. Since then a held upload shows in the status bar with a colour and an icon, and one click gives what is held, the office's own message word for word, and the steps to fix it.
The ordering that decides everything
The receipt prints last — after the journal write and after the transaction commits. It would be friendlier to print first and commit after. But a customer walking out holding a receipt for a sale that is not durably on disk is an unrecoverable failure, and a customer waiting an extra 80 milliseconds is not. Every ordering decision in the write path falls the same way: always fail in the recoverable direction.
What it produced
- A written failure model: for every step in a sale, what happens if the power goes at exactly that moment, and which way the system should fail.
- Architecture rules the build enforces rather than a reviewer — a layer that reaches for something it should not is a failed build, not a comment.
- Four architecture decision records, a threat model, an incident-response procedure and ten numbered security rules, each carrying its why.
What I would do next
A pilot. Then TypeScript on the web platform: 1,026 JavaScript files with no types means a renamed field is only found when a user hits it. And a pipeline that gates releases, rather than the scripts I run against my own servers today.
Ten minutes on a screen-share tells you more than this page can. Get in touch.