← Writing

Trying to Break the Till on Purpose

Stress-testing the offline POS in a real browser until it flinched — then filing down the rough edges: instant reconnect, an honest status badge, a batch one bad sale can't poison, and stale-price warnings.

4 min read Juan Montano


A while back I wrote that the point-of-sale is the one part of this system that isn’t allowed to go down. Offline-first, the sale is sacred, all of that. I believed the design. But “I believe the design” and “I watched it survive a bad afternoon” are not the same sentence, and only one of them is worth anything.

So today I stopped believing and started trying to break it.

Pulling the plug on purpose

I opened the web POS on a test till, started ringing sales, and then just… cut the network out from under it. Mid-sale. Then I reloaded the page while sales were still stuck in the queue. Then I brought the network back and watched what happened.

The good news first, because it’s the important news: nothing was ever lost. Every sale rung offline landed safely in the till’s local storage before the cashier even saw a confirmation, survived a full page reload, and synced up cleanly once the connection returned. The core promise held. That’s the part I most needed to see with my own eyes, and I did.

But three smaller things flinched, and a fourth I already knew was lurking.

The queue that waited too long to wake up

When the network came back, the till didn’t notice right away. It synced on a 60-second timer, so a backlog of sales could just sit there for up to a minute after the connection was clearly back — unless the cashier happened to ring another sale and nudge it awake.

Not lost, just late. But a minute is a long time to look “not synced” when you’re actually fine. The fix was almost embarrassingly small: listen for the browser’s own “you’re back online” event and sync immediately. Now the queue drains the instant the network returns — in my test, about a third of a second instead of up to sixty.

The status light that told a comfortable lie

Here’s the one that annoyed me most. If the till was offline and its login had also expired, the status badge cheerfully said “sign in to sync” — as if a password was all that stood between you and a clean sync. It wasn’t. The network was down. You could sign in all day and nothing would move.

A status indicator that hides the real problem is worse than no indicator at all. So I reordered it: if you’re offline, it says OFFLINE, full stop, no matter what else is going on. The sign-in nudge only shows up once the connection is actually healthy and that really is the thing to fix.

One bad sale used to poison the whole batch

This one was lurking under the surface. When the till pushes its queued sales, it sends them up as a single batch. I found that one malformed sale — a bad timestamp was enough — could make the entire batch bounce. Every queued sale stuck, not just the broken one. One rotten apple, whole barrel refused.

That’s exactly the kind of quiet failure that violates the one rule. So I changed it to quarantine: if a sale looks structurally broken, it gets set aside and flagged for the office to look at, and every other sale in the batch syncs normally. A bad one can’t take its siblings down with it anymore.

Warn, don’t reject — again

The last change is a variation on a theme I keep landing on. The till caches the price list so it can keep selling offline. But what if the owner changes a price after the till cached it, and then an offline sale rings up at the old one?

Old instinct: reject the sale, it’s got the wrong price. Wrong, for the same reason it’s always wrong — the customer already paid that amount, so the sale is real. Instead, the server now records what the price was at the moment it synced, notices it doesn’t match the current one, and flags it — without ever refusing the sale. Same philosophy as the stock shortfalls: never block a real sale, just leave an honest trail to reconcile later.

A device that changed jobs

One unrelated bit of housekeeping: a machine that had been a branch till got repurposed to manage the cake catalogue on the website, so I had to move it out of one access list and into another. It’s the second time this week I’ve hand-edited who’s-allowed-where across several scattered files, and it’s starting to feel like the kind of thing that wants a single map instead of five separate lists. A cleanup for another day — noted, not done.

Today was a quieter kind of progress: not new features, just going back to the most important thing I’d built and making sure it actually keeps its promise on a bad day. It does — a little more convincingly than it did this morning.