Files
vmall/openspec/changes/replace-mock-api-wave-3/tasks.md
T
james e1a0a5dbdb feat(mall): run the transaction chain against the live API
Wave 3 of replacing the fixed-data mock adapter: cart, orders, shipments and
invoices flip together, so one purchase runs end to end against the backend.

- cart: CartItemView carries the line's shop and the SKU's stock, so the cart
  keeps grouping per shop and the quantity stepper caps at real stock instead
  of a hard-coded 999
- contract: Shipment.items is optional and Invoice.invoice_no nullable, both
  matching what the API actually returns. Invoice was declared twice in
  types.ts and TypeScript merges duplicate interfaces, so the duplicate had to
  go for the change to take effect at all
- an anonymous add-to-cart redirects to /login?redirect=..., and sign-in
  honours only same-origin paths
- the fixed-data adapter learns the new cart fields, and its persisted state
  key moves to v2 because a cart saved by an older build is no longer valid
- order surfaces drop their storeById lookups and keep the generic store label
  until the public store read arrives

Verified end to end: two-shop cart grouping with live shop names, stock caps
read from the API, checkout, payment, shipment, delivery confirmation and an
issued invoice. Rollback re-verified with every domain on fixed data and the
backend stopped.

Also checks off Wave 3 in docs/TBD-migrate-wave.md and re-points that file at
the mock content that remains.

OpenSpec change: openspec/changes/replace-mock-api-wave-3
2026-09-17 16:33:22 +00:00

3.9 KiB

Tasks

1. Contract and backend cart view

  • 1.1 Add shop_id, shop_name and stock to CartItemView and to the cart_view SELECT in apps/api/src/cart.rs (the query already joins products, shops and skus); verified GET /api/cart returns a distinct shop and the SKU's stock per line for a two-shop cart
  • 1.2 Add shop_id, shop_name and stock to CartItem in packages/shared/src/types.ts. Also had to teach the fixed-data adapter to construct the new fields (apps/mall/mock/api.ts) and bump its persisted-state key to v2, because a cart saved by an older build is no longer a valid CartItem[]; verified the mall, shop-admin and admin builds all pass
  • 1.3 Make Shipment.items optional and Invoice.invoice_no nullable in packages/shared/src/types.ts. Invoice was declared twice in that file and TypeScript merges duplicate interfaces, so the duplicate was removed for the change to take effect at all; verified against a live shipment (no items key) and a requested invoice (invoice_no: null)
  • 1.4 Extend the two-shop cart test in apps/api/tests/orders.rs to assert each line reports its own shop, a bilingual shop name and the SKU stock; verified cargo test -p vmall-api --test orders is green

2. Anonymous add-to-cart gate

  • 2.1 Send a signed-out shopper to /login?redirect=<current path> when addCartItem answers 401, accepting only same-origin paths; verified the parameter is dropped for //host and absolute URLs
  • 2.2 Have pages/login.vue return the shopper to a valid redirect target after a successful sign-in; verified the round trip from a product page

3. Cart surface

  • 3.1 Group pages/cart.vue by the line's own shop_id and label each group with shop_name, dropping the productById/storeById lookups; verified a two-shop cart renders two groups labelled with the live names (Aurora Digital, Demo Store)
  • 3.2 Cap the quantity stepper with the line's stock instead of the 999 fallback, and use the line's image for the thumbnail; verified the caps read 12 and 25 from the live cart rather than 999

4. Transaction surfaces

  • 4.1 pages/checkout/index.vue: group by the cart line's shop and image rather than the mock catalog, keeping MOCK_ADDRESSES as the address source; verified the checkout preview groups correctly and the order submits
  • 4.2 pages/checkout/pay.vue and pages/user/orders/index.vue: drop the storeById lookups and keep the existing generic store label until Wave 4; verified order cards render with the placeholder and without errors
  • 4.3 pages/user/invoices.vue: render a placeholder when invoice_no is null, mirroring the existing order_no handling; verified the invoices table shows for a requested-but-unissued invoice alongside real INV… numbers

5. Flip the domains

  • 5.1 Add cart, orders, shipments and invoices to the default liveDomains in apps/mall/nuxt.config.ts in one commit with the page changes above; verified every one of those pages now reads from :8080

6. Verification

  • 6.1 Run pnpm --filter @vmall/mall build plus the shop-admin and admin builds, since the shared contract changed; all three pass
  • 6.2 With the backend seeded, ran one purchase in a browser: signed in, added from a product page, saw a two-shop cart group correctly, checked out, paid, then confirmed the order. The merchant half of the chain (ship → issue invoice) has no mall UI and was driven through the API, after which the order page showed the live shipment, confirming delivery moved the order to completed, and the invoices page listed the issued numbers. The only console entries were the header's signed-out GET /api/cart 401 (caught, count 0) and no hydration warnings
  • 6.3 Verified the rollback: with every domain set to fixed data and the backend stopped, sign-in, add-to-cart, cart grouping, the stock cap and checkout all still work from localStorage