Public: anyone can read this; the crew can change it.
A paid order is dispatched: the buyer sees what happens next and the dispatch is a row #89
Check · Review · Land · Live done — Landed and live in production.
Pushed. `bun test` — the command `forge.json` names as the check — passes in order and randomized (139 tests, 1275 assertions).
shipped production is serving it deploy ab962da
main is merged in, the suite is green (163 tests, also in random order), and the branch is pushed.
A paid order is dispatched: order.fulfilled_at (a column, not a status — status goes on meaning where the money is), one order_event of kind dispatched naming the admin with the carrier and tracking in its detail, and one letter in the outbox distinguished from the receipt by the new email.kind. POST /admin/orders/<number>/fulfil is admin-only and same-origin, both fields capped at the boundary, and the rule lives in the UPDATE's WHERE, so two admins clicking together write one row, one letter and one date between them. The buyer's own order page gains a three-step "where this order is" — placed, paid, dispatched — and d at the terminal does the same job as the form.
This push is the merge of main, where three files meant different things in the same places and both meanings are kept: src/orders.ts takes main's imports for the stock race in place plus the import * as mail the second letter needs; src/desk.ts keeps main's giftCardSplit and my dispatch as two functions; src/pages/admin.ts is the interesting one — main made everything below the nav a single data-live region keyed to the order's number, so the dispatch pill, the dispatch form and what it says once the parcel has gone now live inside that region, with order.fulfilled added to the types it asks for. Worth looking at that seam in the preview: open an order in one tab, dispatch it, and the page says so without a reload, while another order's page stays put. One new test covers exactly that — the event names the order and carries nothing else, the shipped wants from static/live.js agrees the keyed region wants it, and the swapped-in region says when it went and with what; it fails if order.fulfilled is removed from the region's types.
Nothing ships, and the README says so plainly: whatever is typed in the tracking box is stored and shown back, escaped, and no carrier is contacted. No new dependencies.
🤖 Generated with Claude Code
Pushed.
bun test— the commandforge.jsonnames as the check — passes in order and randomized (139 tests, 1275 assertions).---
An order's life no longer ends at
paid. Dispatch is a column and a row rather than a status machine:order.fulfilled_atis added withaddColumn(null on every order placed before it, which is the truth about them), andstatusgoes on meaning only where the money stands —paid,partially_refundedandrefundedare untouched. Sending a parcel writes oneorder_eventof kinddispatchednaming the admin from the actor columns, with the carrier and tracking in its detail, and one letter in the outbox; a newemail.kindcolumn tells the receipt from the dispatch letter, so the receipt written at payment is never rewritten andforOrderstill means "the receipt".POST /admin/orders/:number/fulfiltakes a carrier and a tracking reference, both optional and both capped at the boundary, admin-only and same-origin like every other admin POST. TheUPDATEcarries the whole rule in itsWHERE(status = 'paid' AND fulfilled_at IS NULL), so two admins clicking at once write one row, one letter and one date between them; the second click is told it had already gone and writes nothing. An order that is not paid is refused in words, and that refusal is itself a row. Cancelling a dispatched order is refused in words — it can be refunded, not cancelled — and refunding one works as before, restocking still the admin's choice.order.fulfilledgoes on the bus after the transaction commits, like every other emit, so an open/admin/orderssees it.Worth looking at in the preview: the buyer's order page, which now opens with the order's life in three steps — placed, paid, dispatched — each with its date or a dash and what happens next in words, the tracking reference among them and escaped (a test dispatches with
1Z<999and asserts the page carries1Z<999and never the raw form). Then/admin/orders, where a second pill tells dispatched from paid at a glance, and the order page, which offers the dispatch form once and afterwards says when it went and with what. The terminal's orders tab says the same in its row and its open detail, andddispatches there withCARRIER / TRACKING, gated as an admin's key in the onesrc/desk.tstable the tab bar and the screen's foot are built from.What I chose not to do: no carrier or tracking columns — nothing computes with them, so they live in the event's detail and are read back through
orders.dispatchOf; nodispatchedorder status, which would have made every existingstatuscheck ambiguous; no delivery step, because nothing here could ever know about one. Dispatching apartially_refundedorder is refused along with everything else that is notpaid, which is what the issue asked for — say the word if a partly-refunded order should still be able to go out. No new dependency. README gains a section on the step and is blunt that nothing is handed to a carrier and no tracking reference is looked up anywhere.🤖 Generated with Claude Code
Closes #40