A signed-in buyer types their address once: the next order offers it back, and they can keep more than one.
Why now. #10 makes an order belong to the account that bought it. The moment that lands, the checkout form is asking a person who is already known for eight fields they gave last time — and on a phone that is the slowest part of buying anything. The shop already stores the address on the order; what it does not have is the buyer's own list.
Done looks like
[ ] An address table keyed on the buyer's forge id, with the same columns the order already carries (name, address1, address2, city, region, postal, country), a label, and one flag for the default; nothing is written for an anonymous shopper, ever.
[ ] Placing an order while signed in offers to keep the address (a checkbox on the checkout, default off, so nothing is stored without being asked), and stores it once — the same address twice is one row, matched on its own fields, not two.
[ ] /checkout for a signed-in buyer with saved addresses shows them as a choice, the default one filled in, and "Use a different address" that gives back the empty form. With none saved, the page is exactly what it is today.
[ ] /account/addresses (or the tab that fits the header the app already has) lists them, sets the default, edits and deletes — every mutation a same-origin POST with the CSRF token, every field through the same validation placeOrder uses, and never another buyer's row: the query is keyed on ctx.who.id, checked in the handler, not in the template.
[ ] A cap — say ten addresses a buyer — refused in words, so the table cannot be filled by a script.
[ ] Choosing a saved address changes what tax is charged, because tax is read from the address: the estimate on the page and the rate on the order stay the one lookup they are today.
[ ] The terminal is unchanged: this is a shopper's own data and no admin screen needs it.
[ ] Tests: saving on checkout, the second order prefilled, a buyer never seeing another buyer's address (two identities, one 404), the cap, deleting the default promoting another, and an anonymous checkout writing no row.
[ ] The README's "Who can do what" gains the line: a signed-in buyer keeps addresses; an anonymous one keeps nothing.
Files I expect to change:src/db.ts, src/addresses.ts (new), src/shop.ts, src/routes.ts, src/pages/checkout.ts, src/pages/account.ts (new or the existing orders page), src/server.test.ts, src/units.test.ts, README.md.
Risks. This is the first table keyed on an identity: every read and every write is scoped by ctx.who.id and an id in a path is checked against it, or one buyer edits another's address. The address is customer data — it does not belong in a log line, a URL or a looked-up note. Do not let a saved address skip the validation the typed one gets: it is the same placeOrder path either way.
Waits for: #10 — there is no buyer to key a row on until it lands.
Whose brief fits:ledger, with sentinel reading the ownership checks.
A signed-in buyer types their address once: the next order offers it back, and they can keep more than one.
Why now. #10 makes an order belong to the account that bought it. The moment that lands, the checkout form is asking a person who is already known for eight fields they gave last time — and on a phone that is the slowest part of buying anything. The shop already stores the address on the order; what it does not have is the buyer's own list.
Done looks like
addresstable keyed on the buyer's forge id, with the same columns the order already carries (name,address1,address2,city,region,postal,country), a label, and one flag for the default; nothing is written for an anonymous shopper, ever./checkoutfor a signed-in buyer with saved addresses shows them as a choice, the default one filled in, and "Use a different address" that gives back the empty form. With none saved, the page is exactly what it is today./account/addresses(or the tab that fits the header the app already has) lists them, sets the default, edits and deletes — every mutation a same-origin POST with the CSRF token, every field through the same validationplaceOrderuses, and never another buyer's row: the query is keyed onctx.who.id, checked in the handler, not in the template.Files I expect to change:
src/db.ts,src/addresses.ts(new),src/shop.ts,src/routes.ts,src/pages/checkout.ts,src/pages/account.ts(new or the existing orders page),src/server.test.ts,src/units.test.ts,README.md.Risks. This is the first table keyed on an identity: every read and every write is scoped by
ctx.who.idand an id in a path is checked against it, or one buyer edits another's address. The address is customer data — it does not belong in a log line, a URL or alooked-upnote. Do not let a saved address skip the validation the typed one gets: it is the sameplaceOrderpath either way.Waits for: #10 — there is no buyer to key a row on until it lands.
Whose brief fits: ledger, with sentinel reading the ownership checks.