Restaurant floor and kitchen demo

Open it twice, take an order as a waiter in one window, and watch the ticket land on the kitchen rail in the other. Bump it and the table turns green back on the floor. Most people assume that needs a server. It needs one browser.

Open the demo

Opens kitchen.taiphamsolutions.com in a new tab. No account, no signup, and nothing you do in it leaves your browser.

What it does

The waiter window is the floor plan: tables by zone, open an order, add dishes, send to the kitchen. The kitchen window is the rail: tickets coloured by station with a running age timer, start and bump. The pass sits between the two, and a manager gets service statistics, the audit log, and an auto cook simulator that advances tickets on its own so one person can demonstrate the whole loop.

Every mutation goes through one function that writes the changed records to IndexedDB, applies them to local state, and broadcasts the records themselves. A receiving window applies the payload straight to memory and paints rather than reading the database again, because the database is per origin and the sending window has already persisted it. That is where the immediacy comes from, and the header prints the measured latency so the claim is checkable rather than asserted.

Concurrent edits resolve by last writer wins on a Lamport counter, with the writing window's id as a deterministic tiebreak, so every window lands on the same result rather than on its own. Mutations are serialised through a small queue, so a fast double tap raises a quantity instead of leaving two identical rows. An order's status is derived from its items and a table's from its order, so bumping the last item promotes the ticket and repaints the floor with no second message crossing the channel.

Open a second window from the lobby, pick Waiter in one and Kitchen in the other, and send an order. The peer list in the header prints the measured latency between them.

Features

14 tables across three zones, 26 dishes in five categories routed to four stations, seven staff, and one dish already off the menu.

Floor and rail

Tables by zone with guests, order state and a ready count, and a kitchen rail grouped by station with an age timer on every ticket.

Live between windows

A broadcast channel with a storage event as its fallback, and a peer list naming every open window, its role, and the latency measured to it.

A role per window

Waiter, kitchen, expo and manager, held per window rather than per browser, so two windows open side by side do not fight over one role.

Manager controls

Service statistics, the audit log, a reset, and an auto cook simulator that advances tickets so the loop can be shown without a second person.

The flow it closes, end to end

Every step below happens inside the demo. There is no point in it where the work leaves the browser and comes back.

  1. Seat a table

    A table on the floor plan takes a guest count and opens an order against it.

  2. Send to the kitchen

    Dishes are added and sent. The records are written, applied locally and broadcast in the same step.

  3. The rail receives it

    The other window paints the ticket without reading the database again, coloured by station, with its age timer already running.

  4. Cook and bump

    Items are started and bumped. Bumping the last one promotes the ticket, because a ticket's status is derived from its items rather than stored beside them.

  5. The floor finds out

    The table turns green with a ready count and a toast, from the same broadcast rather than from a message sent for the purpose.

  6. Serve, and start again

    Serving the ready items closes the loop and returns the table to free, which is where the next order begins.

What is real, and what is not

There is no server behind any of this, which decides both columns. The left one is what that constraint made worth building properly. The right one is what it rules out.

Actually handled

  • Two windows in step, with the latency between them measured and printed in the header
  • A fast double tap raising a quantity rather than writing two identical rows
  • Concurrent edits landing on the same result in every window, by Lamport order with a deterministic tiebreak
  • Vietnamese across the floor, the rail and the seeded menu, with prices reformatted rather than retyped

Deliberately not real

  • Both windows have to be the same browser on the same machine. That is the real boundary of a demo with no server, and the lobby says so before you find it
  • A role is a choice per window, not authentication
  • Nothing is printed, no payment is taken, and no ticket reaches a real kitchen
  • Ticket codes are numbered per window, so two windows opening an order in the same instant can show the same code. Orders are keyed by id and the code is cosmetic

How it is built

The write-up sells harder than the application does, so it is on the page rather than in a repository nobody can open.

Stack
Svelte 5 with runes, TypeScript and Vite, IndexedDB through idb, a hash router of about forty lines, and a translation layer written by hand. No CSS framework, no state library, no icon package, and about 38 KB gzip of JavaScript on two runtime dependencies.
Transport
A broadcast channel between windows, with the storage event as the fallback. Records are broadcast rather than invalidations, so a receiving window paints from the payload instead of going back to the database.
Conflicts
Last writer wins on a Lamport counter with the writing window's id as the tiebreak, and a serialised mutation queue so two taps in quick succession cannot interleave.
Checks
Typechecking across the components and the TypeScript is the only automated gate here. There is no unit suite, because the thing worth checking is two windows staying in step, and that is checked by opening two windows.

Want the version of this with your rules in it?

This one was built to a constraint nobody would choose. Yours would have a server, real accounts, and the parts of the domain that are actually hard. Tell us what the software has to do and we will tell you what it takes.

Bring the messy version of the problem. That is the conversation we enjoy most.

Email us