1. The real form
Ordinary DOM code with an ordinary async send. Use it as a client would. The panel plays the billing server and the clock, so you decide when each reply arrives, and in what order. After every step the page reads the form back and compares it with the function.
Design and network
- Green line
- The form on screen matches the function, including which taps are possible.
- Red line
- A mismatch, or a promise broken right now.
- Waiting send
- A send the server has not answered yet. Answer it with Worked or Failed, in any order.
- Step chip
- One thing you did, in order.
Each tap on Send now sends one request to the billing server. Here you play the server: answer any waiting send, oldest or newest first.
The form shows a message if no reply comes within 15 seconds. This button makes those 15 seconds pass now.
A blank form, an empty server and a fresh comparison, in the design chosen above.
The real form · Shipped
2. Eight promises, checked in every state
Starting from a blank form, the page tries all 21 events (8 taps and key presses, 6 typed values, 6 server replies, the 15 s clock) in every state it finds, until no new state appears. Any sequence of events, however long, ends in one of these states, so a promise that holds in all of them holds for every sequence.
What the three designs change
3. Replay a broken promise
The shortest way to break a promise, played step by step with real clicks, typing and key presses in a fresh copy of the real form. Play the same steps on another design to see the fix hold.
Replay
- Bright step
- The step playing now; its button glows in the form.
- Green step
- Done, and the form matched the function after it.
- Grey step
- Not possible in this design, so it was skipped.
- Red step
- The promise broke at this step.
Play runs every step about twice a second. One step runs the next step only, so you can read the form between steps.
The real form · Draft
4. Every state, drawn
One dot per state the design can reach. Left to right: the fewest events from a blank form. Tap a dot to read that state and to replay the shortest way there.
Map
- Grey dot
- Edit screen: email and amount.
- Blue dot
- Review screen.
- Purple dot
- Review screen with the Send now dialog open.
- Green dot
- Sent screen.
- Red dot
- A promise is broken in this state, or by the step into it.
- Ring
- The state you tapped.
Tap a dot.
5. Is the check fooling itself?
A promise list that always says "kept" proves nothing. These checks run the real form against the function, show that a wrong function gets caught, and change the Shipped design one line at a time to see which changes the promises catch.
Takes a few seconds on a laptop, longer on a phone. The model-only checks below already ran.
One-line changes to Shipped
Room for waiting sends
The search allows at most a set number of sends waiting at once. Here is what changes as that room grows.
6. What this covers, and what it does not
- Typed text comes in three kinds: blank, wrong and right, as the form's own validator sorts it. The check covers every order of those, not every possible string.
- The server is a stand-in inside this page. It answers only when told, in any order, and, like a billing service that honours an idempotency key (one key per invoice), makes the invoice once per key. No request leaves the page.
- At most 3 sends wait at once. Shipped can fill that room (Try again after each 15 s message), so its proof covers every sequence with up to 3 sends waiting. Rooms 1 to 6 change no verdict for Shipped (table above).
- The promises are what is checked. Six one-line changes to Shipped break no promise; they are listed above with what they do.
- The bench uses DOM events (click(), input events, keydown). They run the form's real handlers, disabled states and inert dialog backdrop, but not the operating system's touch input.
Where the ideas come from
- Visiting every reachable state and checking promises on each: explicit-state model checking, as in TLA+ and its TLC checker.
- Promises about what can always still happen ("no dead ends"): reachability in CTL (EF).
- Checking the real code against the model with random walks: model-based testing.
- One key per invoice so a retry never charges twice: Stripe idempotent requests.
- Changing one line to test the tests: mutation testing.
- Escape closes the dialog, and the page behind it cannot be tapped: WAI-ARIA modal dialog pattern and the inert attribute.
- Part 1 of this battery, the layout as a function: Proof-carrying layout.