Running the Same Stack Three Different Ways
Running the Same Stack Three Different Ways
There’s one lighting rig, one controller, and one bus. On top of it I now run three separate front-ends, and the interesting work isn’t in any of them — it’s in keeping them honest about each other.
The three surfaces
The operator surface. Dense, technical, raw values, per-light drill-down. This is the one I use, and it should look like a tool rather than a product.
The demo surface. A salesperson, a customer, a laptop, no training. Big targets, obvious controls, numbers that look like numbers. It touches a deliberately tiny slice of the command surface.
The simulator. A fake controller that speaks the same protocol with the same timings — realistic sweep delays, faults, gear that drops off the bus. It’s how the demo gets rehearsed and how the tests run without touching live hardware.
The discipline
The first rule: the surfaces don’t share code paths with each other, only with the system beneath them. Each one is its own service with its own deployment. The demo can’t reach into the operator app’s state, and the operator app can’t reach into the demo’s.
The second rule: the demo is not allowed to publish to the raw command topic. That topic can reset a module and rewrite operating hours. There’s no code path to it in the demo’s bridge — not a permission check, not a flag, no path at all. “We don’t do that” is enforced by the absence of code, not by the presence of a check.
The third rule: write access requires an operator session. Read access doesn’t. Monitoring and controlling are different privileges, and the line between them should be a session, not a hidden flag.
Testing the real thing
The unit tests were never the hard part. The hard part was driving the actual page in an actual browser and asserting what a human would see:
- Green status dots only when the crane is genuinely online — including the case where the backend is stopped entirely and everything must go grey.
- The confirm dialog names the crane, the target and the level before anything is sent.
- The bulk action stays disabled until the exact phrase is typed, because “I meant to turn off one zone” is the most expensive kind of mistake.
- A drag of a light onto a group actually lands, and the membership becomes a verified chip.
- At phone width, nothing overflows sideways and every button is big enough to hit.
Most of the interesting failures were found by those assertions, not by reading the code.
Cleaning up after a test
Running the simulator leaves retained messages on the bus and stale state in the backend. The cleanup is deliberate: kill the simulator, publish a retained tombstone to each topic it touched, restart the backend. Never wipe the whole broker store — the live rig’s retained state has to survive testing, and finding that out the hard way is how you learn why.
The through-line
Three surfaces, one system, and every one of them has to be able to say “I don’t know” without crashing, without lying, and without taking the other two down with it.