# TBD — migrate the mall off the mock API (waves 3+) Waves 1 and 2 are captured in `openspec/changes/replace-mock-api-wave-1/` (catalog + currency) and `replace-mock-api-wave-2/` (auth). This file tracks what remains. **How to use:** check a box only once the behaviour is implemented *and* verified against the live backend (`cargo run -p vmall-api`, `node scripts/seed-demo.mjs`). The per-domain switch in `apps/mall/plugins/api.ts` means most of these are adding a domain name to the live list — each still needs its own verification below. **Delete this file** once every box in Wave 3 is checked. Wave 4 is optional: if you decide against it, delete this file anyway and record that decision wherever you like. --- ## Wave 2 — auth (done) Captured in `openspec/changes/replace-mock-api-wave-2/`; auth is live, with the session validated through `/auth/me` rather than trusted from `localStorage`. - [x] Flip `auth` to live and verify `login` / `register` / `me` against `:8080` using the seeded `customer@vmall.local` / `customer123`. - [x] Confirm bad credentials now produce a real 401 — the mock accepted any input (`apps/mall/mock/api.ts:125`), so this is a deliberate UX change. - [x] Confirm the JWT round-trips through the `vmall.token` localStorage key, shared with shop-admin and admin, and that logout clears it. ## Wave 3 — the transaction chain: cart + orders + shipments + invoices These move **together**, not one at a time. The mock adapter keeps `state.cart -> state.orders -> state.shipments` in a single shared state, so a partial flip leaves the mock half reading state the live half never populates: - live cart + mock `checkout()` fails with `EMPTY_CART` (`apps/mall/mock/api.ts:194`), - mock `requestInvoice()` 404s on any live order id (`mock/api.ts:285`), - mock `listMyShipments()` returns shipments whose `order_id` matches no live order, so the shipment block is silently empty (`mock/api.ts:282`, `pages/user/orders/[id].vue:25`). - [ ] Flip `cart`, `orders`, `shipments` and `invoices` in the same change, then verify one purchase end to end: add to cart, check out into per-shop orders, pay, ship, confirm delivery, request an invoice. - [ ] Verify cancel restores stock and `payOrder` only accepts `pending_payment` (both enforced live; the mock only mimicked them). - [ ] Verify a company invoice requires a tax number, and that one order can hold only one active invoice. - [ ] Remove cart's mock display coupling: `apps/mall/pages/cart.vue:4,34,41,56,60` uses `productById` / `storeById` for shop name, image and stock. The live `CartView` already returns `product_name`, `image` and `unit_price_minor` (`apps/api/src/cart.rs:44-53`) — but **not** the shop, so decide whether to add a shop field to `CartItem` in the shared contract or accept ungrouped lines until the public store read exists. - [ ] Decide what caps cart quantity: `CartItem` carries no `stock`, so `maxFor` falls back to 999 (`pages/cart.vue:60`). The live cart does not enforce stock either — only checkout does (409). Either add `stock` to `CartItem` or keep the cap at checkout and say so. - [ ] Gate add-to-cart for anonymous shoppers: a live `addCartItem` on the public product page returns 401, and `pages/goods/[id].vue` is not behind the auth middleware. - [ ] Checkout keeps sourcing `shipping_address` from `MOCK_ADDRESSES` (`pages/checkout/index.vue:4,23,37,127`) — intentional; see the out-of-scope note below. - [ ] Fix contract debt so the TS types stop lying: `Shipment.items` is required in `packages/shared/src/types.ts` but the live struct has no `items` field (`apps/api/src/models.rs:172-182`), and `Invoice.invoice_no` is nullable live (`models.rs:194`) but non-null `string` in TS. - [ ] Remove the remaining `storeById` mock usage on the order pages (`apps/mall/pages/user/orders/index.vue:4`). - [ ] Confirm the mall still renders when the live API is down, with every domain configured to fixed data. ## Wave 4 — optional, mostly new backend capabilities Only if you want more of the storefront backed by real data. Each is a new capability, not a flip. - [ ] **Storefront content** — banners, promos, quick links and floor advert art. Needs real tables, admin CRUD and i18n JSONB. Do this first if you want the home page fully live; it is the most visible remaining mock surface. - [ ] **Public store read** — a buyer-facing shop endpoint so `stores/index`, `stores/[id]` and the cart's shop grouping leave mock. Small: products already carry `shop_id`, and the public catalog already joins shops for the active check. - [ ] **Brand model + sales/comments sorts** — restores the brand facet and the sorts removed in Wave 1. Needs a `brands` table (`products.brand_id` + i18n) plus sales and comments data, neither of which exists today. - [ ] Extend the `ORDER BY` whitelist if more sorts are wanted beyond the `sort=price` added in Wave 1. - [ ] *(adjacent, not part of the migration)* Move the session token to a cookie so SSR knows whether anyone is signed in. Today a full page load of a guarded route renders the page and then redirects on the client, which logs a hydration mismatch; it is pre-existing (verified identical before Wave 2) and harmless, but it is the real fix for the `ClientOnly` workarounds in `components/shell/TopBar.vue` and `pages/user.vue`. --- ## Deliberately out of scope — not tracked here, and they do not block deleting this file These have no API contract and no backend model. Leaving them on `~/mock/data` is a decision, not a backlog item. - **Addresses** — never a blocker: `Address` is embedded in the order and live checkout takes it in the request body, so no addresses table is needed. `MOCK_ADDRESSES` can stay behind checkout indefinitely. - **Favorites, coupons, account stats** — pure presentation, no transactional impact. - **seckill / collective / integral marketing pages** — display-only mock content. ## Decisions already made (do not relitigate) - Category filtering is by **subtree**; the backend exact-match filter was the bug (fixed in Wave 1). - A migration wave must not change what the UI claims: facets without a backing model are removed rather than left matching nothing. - The mall is the last mock holdout; `shop-admin` and `admin` already run live against the same backend. - Auth flips independently, but the transaction domains do not: the mock's shared cart/order/shipment state makes any partial flip fail loudly.