← Writing

From Desktop to the Browser

Moving the whole system into the browser — an owner CLI, a React web app, and an offline web POS — plus the bug my offline tests swore didn't exist.

4 min read Juan Montano


The desktop apps are great on the shop floor. They’re useless everywhere else. Admin staff working from home, the owner checking numbers from a phone, me wanting to change a price without walking to the office PC — none of that works when the software only runs as a Windows app installed on specific machines.

So the last leg of this project was dragging everything into the browser, in three steps of increasing nerve.

Step one: the owner lives in Excel

Before any web UI, I built a command-line tool for the owner-only chores: managing recipes and setting prices.

The key insight wasn’t technical. It was that the owner already does all their thinking in Excel. Fighting that would be arrogant. So the tool speaks Excel: it exports the live recipes and prices into a workbook, the owner edits it the way they always edit things, and the tool reads it back — diffing against what’s live and only pushing the rows that actually changed.

No new interface to learn. The spreadsheet is the interface. It’s the kind of feature that’s obvious once you stop designing for yourself and start designing for the one person who’ll use it.

Step two: the same screens, in a browser

Then the bigger piece — a proper web app mirroring the desktop screens, so the reports and admin tools work from any browser on our network, not just the installed apps.

The interesting problem here was auth. On desktop, “you’re on the right machine” was doing a lot of the work. On the web I needed real logins — but I didn’t want to replace the device allowlists from the last post, because those were hard-won.

So logins stack on top of the device gates. A valid session gets you a role, but you still have to be on an allowed device for the sensitive stuff. An owner signed in from a random machine still can’t do owner things. Both checks have to pass; neither alone is enough. It’s more locks than strictly necessary, and I’m fine with that — the whole system runs on the owner’s trust, and belt-and-suspenders is cheap.

I also seeded the very first owner password through a one-time setup step, so there’s never a real password sitting in the codebase — just a placeholder that can’t possibly work until it’s replaced. Small thing, but “no secrets in the repo, ever” is a line worth holding from day one.

Step three: the web POS, and the bug that humbled me

The final boss was putting the POS itself in the browser — as an installable, offline-capable web app. Same rules as the desktop POS: it has to ring sales with no connection and sync them later.

I rebuilt the whole offline machine for the web: a local in-browser database, a catalog cache, a queued sale that commits durably before anything touches the network, and the same idempotent sync from the desktop version. I tested it with an offline harness. I curl-tested the sync endpoint. Green across the board. Shipped.

Every single sale failed to sync.

The cause was almost insulting in its smallness: the server wanted the sale’s timestamp in a specific date format, and my web client was sending a slightly different one — a space where there should have been a T. My harness and my curl tests both happened to send the “right” shape, so they passed. Only a sale rung the way a real cashier would ring it, in a real browser, produced the broken shape.

The lesson I wrote down and won’t forget: an offline test harness will happily lie to you. It tests the code you wrote, not the thing the user does. For anything as critical as capturing a sale, there’s no substitute for doing the actual gesture — ring a real sale, in the real browser, against the real server, and watch it land. I found and fixed it (and a handful of follow-on issues) only by finally doing that.

Where it stands

In a little over two weeks it went from a pile of disconnected spreadsheets and offline tills to: one central database that owns the truth, a FIFO inventory model that tracks real cost, a delivery flow with a proper handshake, offline-first POS on both desktop and web, a recipe engine, an owner CLI, a full web app, and a security model I actually tested instead of assumed.

It’s not finished — software like this is never finished, and there’s a real go-live shakeout ahead where the numbers will drift before they settle. But it does the one thing the old setup never could: if you ask two parts of the business the same question, they give you the same answer.

That was the whole point. Everything else was just the work.