Pulls events off the new order_events table and shows them as a timeline
block in the order detail view: Paid, Label created, In transit,
Delivered (etc) with timestamps. Filters out ops-only events like
payment_critical, label_orphaned, and tracking_marked_manually so the
customer never sees internal plumbing.
DisplayLabel on OrderEvent decides what's customer-visible and what's
redacted, so the same order_events rows can later power an admin view
with no filter.
the big colored status on the order detail page used to just say
"Shipped" the moment i bought the label, which didnt really feel right
since the carrier hadnt actually picked anything up yet. now it walks
through "Label created" -> "In transit" -> "Delivered" as real scans
come in, and keeps the "Delivery Issue" / "Returned to sender" labels
for unhappy paths
also wired up the existing tracking_status_details column to the tui. i
skipped it before but the response actually includes a description of
the delivery, that is now subtext under the colored statu.
and the shippo webhook now returns a small json body instead of an empty
200. this is just for the operator that runs the curls commands to
update the shipment, which makes debugging a lot less guesswork
Shippo's transaction validation rejects labels where the from-address
has no email — fails at the poll step with
"Attribute address_from.email must not be empty."
Add Email to Address, plumb it into addrPayload, and read
SHIP_FROM_EMAIL alongside the other SHIP_FROM_* env vars.
Shippo's /v1/orders endpoint rejects >2 decimal places on parcel
weight and >4 on line_items[].weight. Converting ounces to kg
(oz * 0.0283495) routinely produces 6-decimal values, so the first
real label purchase 400'd with "Ensure that there are no more than
N decimal places".
Round at the payload boundary in createShippoOrder rather than in
the caller — keeps the precision of the source (Coffee.Ounces) and
only loses it when the wire format demands it.
Adds POST /api/v1/orders/{id}/label (admin-only) that buys a shipping
label through Shippo and writes carrier + tracking + label fields back
onto the order, transitioning paid -> shipped.
realized per-shipment table is kinda bad, switched to single embedded tracking blob. since im only
shipping from my house no split is needed. adds tracking_status, tracking_status_details,
tracking_status_updated_at columns. reverse-backfills any historical shipments rows back onto orders
(idempotent, table-guarded). UpdateTracking rewritten to write to otder columns direcly