What postage costs, and what it takes to get it free, is the shop's own setting an admin can change — not two constants in src/cart.ts.
What the repository does today.export const SHIPPING_CENTS = 600 and export const FREE_SHIPPING_OVER_CENTS = 7500 sit in src/cart.ts, and shippingFor() reads them. Tax is a table an admin edits at /admin/tax and the rate is copied onto the order at the moment of sale; postage — the other line on every order — cannot be changed without a deploy. A shop that cannot change its postage is not a shop somebody runs.
Done looks like
[ ] A small src/shipping.ts holding the two numbers as settings, read from the database with the current constants as the seed, in the shape src/tax.ts already uses (a query, plain data out, one module for one concept).
[ ] /admin/shipping (or a block on /admin/tax, whichever keeps the admin nav honest) sets the flat rate and the free-over threshold: integer cents through the same validation the price fields use, no negative numbers, a threshold of zero meaning "never free", and refusing anything that is not a number rather than storing NaN.
[ ] The terminal can do it too — a key on the tax tab or a tab of its own in src/desk.ts, admin only, one table for both the way desk.ts already insists.
[ ] Changing it never rewrites what somebody was charged: shipping_cents is already copied onto the order at sale; a test places an order, changes the setting, and re-reads the order to prove the old figure stands.
[ ] The cart, the checkout estimate, the order and the receipt all read the one setting, so no two of them can drift; SHIPPING_CENTS no longer exists as an exported constant anyone can import.
[ ] The change is a row that names the admin who made it, through the admin_change ledger from #34 — postage is money, and money is a row.
[ ] Tests: the seed, a change, the free-over boundary exactly at the threshold and one cent below it, free shipping from a discount code still winning over the setting, and the order keeping its own figure.
[ ] The README's money paragraph says postage is a setting and where it is set.
Files I expect to change:src/shipping.ts (new), src/cart.ts, src/db.ts, src/admin.ts, src/pages/admin.ts, src/desk.ts, src/units.test.ts, src/server.test.ts, README.md.
Risks. Two sources of truth is the failure here: delete the constants rather than leaving them beside the setting. A setting read on every quote wants one cheap query, not one per line. The free-shipping discount and the free-over threshold must compose in one place, or a cart can end up with shipping charged twice or refunded once.
Waits for: #34 — so the row that names the admin is written once, for this and for the rest of the settings.
Whose brief fits:ledger — a price that moves, written down with who moved it.
What postage costs, and what it takes to get it free, is the shop's own setting an admin can change — not two constants in
src/cart.ts.What the repository does today.
export const SHIPPING_CENTS = 600andexport const FREE_SHIPPING_OVER_CENTS = 7500sit insrc/cart.ts, andshippingFor()reads them. Tax is a table an admin edits at/admin/taxand the rate is copied onto the order at the moment of sale; postage — the other line on every order — cannot be changed without a deploy. A shop that cannot change its postage is not a shop somebody runs.Done looks like
src/shipping.tsholding the two numbers as settings, read from the database with the current constants as the seed, in the shapesrc/tax.tsalready uses (a query, plain data out, one module for one concept)./admin/shipping(or a block on/admin/tax, whichever keeps the admin nav honest) sets the flat rate and the free-over threshold: integer cents through the same validation the price fields use, no negative numbers, a threshold of zero meaning "never free", and refusing anything that is not a number rather than storingNaN.src/desk.ts, admin only, one table for both the waydesk.tsalready insists.shipping_centsis already copied onto the order at sale; a test places an order, changes the setting, and re-reads the order to prove the old figure stands.SHIPPING_CENTSno longer exists as an exported constant anyone can import.admin_changeledger from #34 — postage is money, and money is a row.Files I expect to change:
src/shipping.ts(new),src/cart.ts,src/db.ts,src/admin.ts,src/pages/admin.ts,src/desk.ts,src/units.test.ts,src/server.test.ts,README.md.Risks. Two sources of truth is the failure here: delete the constants rather than leaving them beside the setting. A setting read on every quote wants one cheap query, not one per line. The free-shipping discount and the free-over threshold must compose in one place, or a cart can end up with shipping charged twice or refunded once.
Waits for: #34 — so the row that names the admin is written once, for this and for the rest of the settings.
Whose brief fits: ledger — a price that moves, written down with who moved it.