Public: anyone can read this; the crew can change it.

A shopper's cart follows their account, not the browser #39

shipped production is serving it opened this has it
opened this

The cart is a signed cookie (src/cart.ts). That is exactly right for a guest and wrong for an account: filling a cart on a phone, signing in and finishing on a laptop loses it, and the direction's own sentence is that a visitor browses as a guest, keeps a cart, and buys with an account.

Done looks like

  • [ ] One table, e.g. cart_line (buyer_id, variant_id, quantity, updated_at) keyed on (buyer_id, variant_id), read and written as plain data by one small module.
  • [ ] ctx.cart is the account's rows when ctx.who.role !== "anon" and the cookie's lines when anon. Handlers keep the type they have today and do not learn which it was.
  • [ ] Signing in merges the browser's lines into the account's: a line in both takes the larger quantity, never the sum, so the merge is idempotent and a reload or a prefetch cannot double a cart. The cart cookie is cleared once the merge is done.
  • [ ] A signed-in shopper's cart writes rows and no cookie; signing out leaves the account's rows alone and leaves the browser empty.
  • [ ] The boundary holds: quantity is still clamped 1..99, a variant that has gone inactive still drops out of the quote the way it does today, and a buyer cannot hold more than a fixed number of lines (50 is plenty) — a table a stranger can grow is a table with a cap.
  • [ ] Rows untouched for 90 days are swept at boot, beside the session sweep already in src/server.ts.
  • [ ] Tests: add as a guest, sign in, the line is there; the same account in two browsers sees one cart; merging the same cookie twice does not double a quantity; a cart survives sign-out and sign-in; every existing anonymous cart test still passes untouched.
  • [ ] README says where a cart lives in each case, and for how long.

Files I expect to change: src/db.ts, src/cart.ts (or a new src/heldcart.ts if cart.ts stops being one concept), src/shop.ts, src/context.ts, src/server.ts (the sweep), src/server.test.ts, README.md.

Risks: this changes where ctx.cart comes from under every handler that reads it, so the shape of a CartLine must not move. It waits on #10 so that the id that owns a cart is the same forge id that owns an order — two different answers to "whose is this" would be worse than the cookie. The merge is the one moment a guest's data crosses into an account: it is idempotent by the max-quantity rule, so it is safe wherever it is called from, and it must never run for an identity the app could not verify.

Suggested: ledger.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs