Shipment Packaging
One package table, one rate matrix, one packing grid — read by the listing builder’s margin maths and by the ShipStation rules, so a tare typed once cannot disagree with itself.
One package table, one rate matrix, one packing grid — read by the listing builder’s margin maths and by the ShipStation rules, so a tare typed once cannot disagree with itself.
MR-089. Spec: references/shipstation-package-engine.md §“Screen requirements”.
ShipStation derives a bundle’s weight as Σ(component package weight × qty), so the packaging tare is counted once per unit. Every Merch Riot SKU is a bundle, so that is the whole catalogue, and both UI weight fields are disabled. Fulfillment is fixed by configuration — components carry item weight only, and one automation rule per package adds the tare back. Merchandising is not. At listing-build time there is no order, so the listing builder has to predict package, weight and postage to compute margin (MR-064). That prediction is this screen.
Not the Catalog Product — a formula can supply several branded products and they pack identically. An earlier draft proposed a shared bottle profile; Paul rejected it (“I can never see us assuming that two different source formulas have the same weight or dimensions”) and the formula carries its own profile.
One thing the fixture says out loud, for Paul. In source-formulas-list.html, dimensions repeat across formulas even though weights do not: SF-0012 and SF-0031 are both 2.0×2.0×3.8; SF-0019, SF-0029 and SF-0044 are all 2.6×2.6×5.0. Since packing is driven by dimensions and weight is not, several rows will compute the same package — visibly. Per-formula still works and stays the decision; this is only why the grid looks repetitive, and it is the argument a profile would have made.
High — an explicit cell or override: package + dimensions + weight, so the order is batch-printable. Medium — computed fit only: weight is emitted and the package is withheld, because weight is the field that blocks a batch print and it is derivable from the formula alone. Low — nothing fits, or a combo never seen: nothing is emitted and the order stays UNRESOLVED.
Withholding is a feature. A wrong box produces a wrong label and a wrong postage charge; an absent box produces a warehouse associate doing exactly what they do today. State 2 is the state to look at.
The resolver never promotes a combo itself. Repeat combos surface as candidates with their shipment counts; a human names the package and that is what makes the row high-confidence. The approval step is the safeguard, and it is precisely what ShipStation’s own SmartFill lacks — SmartFill learns from the last two labels including ones it wrote itself.
USPS Ground Advantage still has four sub-1-lb tiers — up to 4, 8, 12, 15.999 oz, rounding up — and at Commercial they currently carry the same price. The screen renders that as “these cells happen to be equal”, never as “there is one price under a pound”: the structure is permanent, the equality is this year’s data. ShipStation gives Commercial Base plus additional discounts, and below-Commercial cards may still be ounce-priced — so the matrix must be able to show them differing tomorrow.
The only cliff that costs money at Commercial is 16.000 oz, drawn as a rule across the table.
A listing-time estimate has no destination, so it uses a zone blended from our own shipment history, named on the readout. The blend is a volume-weighted mean of the prices, not of the zone numbers — postage is not linear in zone. Until the history pull (S12 ①) exists it shows a fixed representative zone labelled provisional: the rejected option used honestly as a placeholder rather than silently as the design.
The grid is keyed on formulas, and formula names are manufacturing language while operators think in brands. The Show catalog product names toggle borrows the products built from each formula onto its row — a display join, not a data change. Watch two rows when you turn it on: SF-0007 has four products and no single name to borrow, and SF-0044 has three. That is the case that decides whether an overlay works at all or just moves the confusion.
One screen or three? Packages, rates and the grid have different edit rhythms (rarely / imported / occasionally) but must visibly agree. Does the resolver rail earn its width, or does this belong in the listing builder’s own margin readout? Does “we don’t know” read as a resting state rather than as breakage?
A shipment package and its tare are defined here and nowhere else. Everything downstream reads this table: the packing grid, the resolver, the listing builder’s Ship column, and the generated ShipStation rules. listing-builder.html still carries a four-row PKGS fixture of its own from before this screen existed — four packages against thirteen, an invented Box 6×6×6, and a tare wrong on every row (0.6 vs 0.21, 1.1 vs 0.42, 5.0 vs 6.52). MR-084 deleted its visible table; the data is still there and still wrong. It reads from here, or the margin quotes a shipping cost the warehouse’s postage does not support.
Paul, 2026-09-02: “we will never have shipment packaging without weight — if we’re gonna add it to the system we’re just gonna weigh it and it’s gonna be there.” So the empty weight is required on the add form: no draft state, no unknown option, nothing to save around. A package with no tare would silently short-pay every label that used it, and the cheapest place to prevent that is the one door packages come through.
This removes a whole category rather than handling it. The two unweighed boxes on the Packages tab are the last of the ones that predate the rule — a backfill queue that closes and cannot reopen, not a standing state the screen has to keep designing for. What survives as genuine uncertainty is entirely upstream: a formula with no unit weight, or a combo nothing fits.
Contents volume against package capacity, then the lightest package that fits — a mailer beats a box at equal fit, which is what the bench actually does. Two modelled constraints: a package with no weighed tare can never be auto-chosen (an override may still name one, and the missing tare then blocks the row), and a mailer holds two units, three for the large — past that you need a rigid box. That second one is a rule of thumb standing in for real packing, and it is exactly the sort of thing overrides exist to correct.
Material cost per package is empty on purpose — the source sheet’s Cost column was blank, and MR-064 needs it for margin. Editing a package, importing a rate card, and the history pull behind the blend are all stubs.