About the order flow

The order handling flow chart from the UX team, redrawn as a live XState statechart, with two courses around it:
  • Order board (/features/order-flow/app): the original board as a statechart, in 8 lessons: welcome and legend, the happy path, "what else could happen?", guards, crossing the line of visibility, hierarchy and context (subscription and pack), time, and questions for the team.
  • Learn (/features/order-flow/app/learn): "State machines, from scratch". 16 lessons for non-technical readers, one concept each, every one with its own small live machine from the order domain. The course's own navigation runs on a state machine, drawn live in the last lesson.

Click an event chip on the board to send it. Undo (or Cmd+Z) steps back. Drag or two-finger scroll to pan, pinch to zoom.

Source

FigJam file V1PQF8WkLy2WMXUHhVjTHj, node 381:10052: "Order system – scoping", section "Giga flow chart order handling".

The model is built from that one board plus guesswork. Every rule in Assumed business rules was invented to make the machine runnable and must be checked.

The original board

Three swimlanes: Customer, Tenant sales and System (database), with a line of visibility between customer and admin. The board was drawn by hand and covers the happy path.

LaneWhat the board has
CustomerGallery → asset preview → "Finding the license I need?". Yes: add to cart or buy now → checkout (items, VAT) → accept terms and pay → payment gateway (card or invoice) → payment successful → download. No: contact customer support (phone, email, chat). Shortcut: a subscription or pack covering the image goes straight to download.
Tenant salesStarts at "Start ???" (undefined on the board). Custom license or negotiated price → notify the customer. Subscription or pack → create product or variant, name and description, select customer, product, licenses, duration or credit limit, price → email with pay link.
SystemCustomer profile, order registered in the order overview, download registered in the download log.

Also on the board: two unconnected decisions ("Downloaded and charged for before?", "Would a pack or subscription be cheaper?"), and sticky notes on visibility control, needing a profile to contact support, editorial gating, adding items to a customer's cart, showing deals as a delight ("15% off"), workspaces, and how Mediebank, Getty and Alamy do it.

Not modelled: the right-hand side of the board (order history, receipts, credit notes, refunds, supplier and royalty reports, VAT on commissions) and the Graveyard.

The additions

The Additions switch in the order board's toolbar fades everything that isn't on the original board and shows the original box names. The additions came from standing in each state and asking "what else could happen here?":

  • card declined, retry, cancel at the gateway
  • invoice overdue, order cancelled
  • offer declined, withdrawn or expired; inquiry declined ("can't help"); new inquiry
  • pack used up, subscription expired
  • back to cart

The UI refers to the original board neutrally ("the original board") and builds on it rather than correcting it.

The model

One parallel machine (-lib/machine/orderHandling.ts) with three regions. Each region is something both the customer side and admin touch:

RegionMeaningStates
orderWhat the customer is buyingasset preview → cart, checkout or waiting for support → payment (choosing method, card, declined, invoiced, overdue) → paid → done; or cancelled
offerWhat tenant sales prepares for one customerinquiry received (was "Start ???") → drafting (custom price, or product → licenses → duration or credit limit → price) → sent → accepted or closed
accessA paid subscription or packactive → used up or expired

Handoffs cross the line of visibility and are drawn dotted: support.contacted, offer.accepted, offer.closed, order.accessPaid. Each one is a notification, an email or a screen, so work on both sides.

Senders. Every arrow has a sender: customer (needs customer UI), sales (needs admin UI), system (needs backend work) or time (needs a scheduler or job). An arrow nobody sends is a gap.

Not modelled yet

  • Editorial gating: visible but not buyable until customer service approves the customer.
  • Workspaces: an enterprise customer picks which workspace an order is billed to.
  • Visibility control and products limiting what a customer sees.
  • Re-download without a new charge ("downloaded and charged before?").
  • Upsell to a pack or subscription at checkout.
  • Whether sales may add items to a customer's cart. The model sidesteps it: the customer accepts an offer.
  • Post-purchase: refunds, license changes, credit notes, order history.
  • Payment details: redirect vs webhook order, closing the tab on the gateway, timeouts.

Running it

It's part of this app: bun run dev, then open /features/order-flow/app or /features/order-flow/app/learn. Colour mode follows the sun/moon toggle in the header.

Tests: bun test src/routes/features/order-flow/app. They cover machine paths, every scene layout, every lesson played step by step against its real machine, and the course navigation machine.

Changing it

FileWhat it is
-lib/machine/orderHandling.tsThe order statechart (the thing to refine)
-lib/machine/basics.tsSmall teaching machines for Learn
-lib/scene.tsScene type: machine, layout, labels, toggles, context rows
-lib/scenes/order.ts, basics.tsBoard layouts: positions, lanes, arrow routes
-lib/courses/order.ts, basics.tsLesson text and steps; the Learn navigation machine
-lib/board/Machine → nodes and edges, orthogonal arrow routing, state → code
-lib/sim/simulator.tsWraps an actor: log, undo, fired arrows and actions, async events
-lib/components/Course view, board, lesson panel, now panel, extras (quiz, ladder, combinations)

Conventions the board depends on:

  • Every drawn transition has meta: { label, sender }. Transitions without meta aren't drawn but still work. added: true marks what isn't on the original board.
  • States use meta: { label, hint?, original?, added?, question?, hidden?, hideInitial? }. original keeps the board's wording; hidden is for idle states before an entity exists (hiddenOrigins in the layout says where their arrows start).
  • Event types with a dot (offer.accepted) are handoffs: dotted and not clickable.
  • Every visible state needs a position in the scene's nodeRects, keyed by path (order.payment.card). Arrow route overrides are keyed ${ownerPath}>${EVENT}#${index}; a test fails if one points at nothing.
  • Adding a state: add it to the machine, position it in -lib/scenes/order.ts, run the tests, look at it in the browser.