Numbers on this page are measured on this stand and refresh every 20 seconds. Demo home

How this QR menu is built

A guest scans the code on the table, orders from their own phone, and the ticket appears on the kitchen screen before the waiter would have finished walking to the till. The kitchen bumps it, and the guest's phone says the food is on its way. One system, two directions, no app to install.

The business version, in one paragraph

A waiter takes an order, walks to the terminal, types it in, and the kitchen starts cooking: two to four minutes of walking per order, plus the mistakes that come with retyping. Here the guest's phone is the terminal. The order is on the kitchen screen in a fifth of a second, already split between the hot line, the grill and the bar, with allergies and "no onion" attached to the right dish. The waiter's time goes back into the dining room, where it earns money.

What it actually does

Measured on this stand

—ms p50 order → screen0 samples
—ms p95server + network + paint
—ETA within 2 minhuman-run orders only
2orders today0 from real visitors
StepPaper / walk to the tillThis stand
Order reaches the kitchen2–4 min of walking and typingunder a second, measured above
Sold-out dishguests keep ordering it until every waiter is toldgreyed out on every phone at once
Allergy noterepeated verbally, sometimesattached to the dish on the ticket
Split billarithmetic at the tablecomputed from who ordered what
Weak wi-fiorder retyped, sometimes twicequeued locally, delivered once

Architecture

Guest phones menu · cart · orders Kitchen screen tickets by station Waiter · admin calls · floor · 86 list nginx TLS, gzip ws upgrade One Node process Fastify HTTP ws hub · topics order state machine simulator · outbox rate limits · idempotency ~60 MB RSS SQLite WAL event log every arrow is a websocket, not polling
One Node process holds the HTTP API, the websocket hub and the state machine; SQLite holds the data and the event log. No broker, no cluster, no external service.

Why an event log and not just pub/sub

Kitchen tablets sleep, screens get carried through the walk-in, phones lock. A plain broadcast loses whatever happened while a screen was away. Every state change here is appended to an events table first and broadcast second, so a reconnecting screen says since=1841 and receives the missed tickets in order before live traffic resumes.

Why the order carries an idempotency key

A phone on a bad connection cannot tell "the request never arrived" from "the answer never came back". So it retries — and a naive kitchen cooks the steak twice. Each order carries a key generated on the phone; the database has a unique index on it. Retry as often as you like: one steak.

Why the cart belongs to the table

People do not order individually; they order together. The cart is keyed to the table visit, every line remembers which phone added it, and that single decision gives you live "Basil added hummus" updates and a bill that can be split by person without anyone doing arithmetic.

Where the “ready in ~14 min” comes from

Not from a field in the menu. Each station's current queue is summed in cooking minutes, divided by three pairs of hands and capped at ten minutes, then added to the longest dish in your own order: eta = max(dish) + 0.5 × rest + min(10, backlog / 3). Press Rush hour on the home page and watch the promise move. The panel above compares promise to reality — and counts only orders a human actually ran on the kitchen screen, because the synthetic tables cook on a compressed clock.

Why the dish pictures are vectors

Stock food photography is somebody else's plates and roughly 200 KB each. All 48 dishes here are drawn by a small generator from a palette and a shape archetype — about 1 KB each, inline in the HTML, sharp on any screen, and legally ours. Zero image requests on the menu page.

What is honest about this demo

What it costs to run

Things that broke while building it

Demo home Kitchen screen Waiter screen Admin · demo/demo Live stats JSON