Logistics dispatch board demo

A dispatch desk and a driver's screen over one browser database. Sixty orders across three days, eight drivers, and a rulebook that refuses an over-capacity route at the drop target and refuses it again inside the write.

Open the demo

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

What it does

Dispatch gets the board: unassigned orders on one side, the day's routes on the other, drag to assign, and a stop sequence that can be rearranged. A driver gets one route and the stops on it, with pickup, departure, arrival, delivery and failure as the only moves available. An administrator gets drivers, vehicles and the outbox, and a viewer gets all of it and can change none of it.

The rules live in one directory of pure TypeScript with no DOM, no database and no framework in it, and a test walks that directory to keep it that way. The order state machine, the capacity rulebook and the permission table are functions, so the check that disables a drop target is the same one that runs inside the write. A component that forgets to ask cannot introduce a capacity or a permission bug.

A refusal is a value rather than an exception. A guard returns the reason it refused and the reason carries a translation key, so a rejection renders as a sentence in the reader's language instead of a generic error. Statuses, priorities and failure codes are stored as machine codes and translated when they are drawn, which is why switching to Vietnamese also rewrites the historical audit entries.

Drag a bulky order onto a bike route. The drop target disables itself and names the limit it would break, and the attempt still reaches the audit timeline.

Features

60 orders across three days, 8 drivers, 8 vehicles and 18 routes, with one van off the road and one driver off duty, seeded on first open.

Board and drag to assign

Unassigned orders, the day's routes, and a drop target that disables itself carrying the exact limit it would break as its tooltip.

Capacity checked twice

Weight, volume and stop count per vehicle, gated in the interface and asserted again in the repository, so neither half is the only guard.

Driver run and proof of delivery

Pickup, departure, arrival, then a signature drawn on a canvas, a photo, or a failure code with a note. Each step is a legal transition or nothing.

Audit timeline and outbox

Every mutation appends its events and its outbox entries in the same transaction, so the log cannot drift from the data it describes.

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. Orders arrive

    Sixty across three days, each with a pickup and a drop window, a weight, a volume and a priority.

  2. A route is built

    Orders are dragged onto a driver's route, and the vehicle's weight, volume and stop limits decide what the drop target will take.

  3. The day is dispatched

    Dispatch hands the route over. From here the driver is the only role that can move a stop, and only on their own route.

  4. The driver runs it

    Pickup, depart, arrive, deliver. A jump the state machine does not allow is refused with a translated reason rather than quietly ignored.

  5. Proof of delivery, or a failure

    A signature on the canvas or a photo closes a stop. So does a failure, with a code and a note, because a day on which nothing failed is not a day.

  6. The log is the record

    Entities, audit events and outbox entries are written in one transaction that advances the Lamport counter inside itself, so two tabs cannot mint the same clock value.

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

  • Capacity refused at the drop target and refused again inside the write
  • Every row of the permission table, asserted by a test rather than by a review
  • A cold start with the network cut, checked in a real browser against the built bundle
  • Illegal transitions rejected with a reason that reads as a sentence in both languages

Deliberately not real

  • The role switcher is not authentication. There is no server to authenticate against, and the interface says so
  • The outbox is never drained by a real endpoint. It is built and inspectable, and a simulated reconnect flushes it
  • There is no map, no routing engine and no live vehicle tracking. Stops carry coordinates and nothing plots them
  • Nothing syncs between devices. The data is per browser profile, so a phone gets a fresh copy

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, TypeScript and Vite, with IndexedDB through idb and a service worker for the offline start. No application server behind any surface.
Boundary
The domain directory is pure TypeScript with no DOM, no IndexedDB and no framework inside it, and a test walks the directory to keep it that way.
Budget
60 KB gzip for the initial route and 110 KB for everything, computed from the build manifest rather than guessed from filenames. It currently sits at 37.5 KB and 90.3 KB.
Tests
206 tests over the state machine, capacity, every row of the permission table, audit completeness and offline durability, plus a smoke run that drives the built bundle in a real Chrome for the drag, the signature canvas and a start with no network.

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