Archive the three completed changes behind replace-mock-api-wave-1/2/3. Each
merge applied cleanly to the main specs:
- catalog gains the Public product browse requirement (subtree filtering and
price sort)
- frontend-mall picks up the per-domain adapter, the pinned home page, the
discovery-page changes, the live auth panels and the live transaction flows
- cart's Server-side cart requirement now documents the shop and stock carried
by every line
Also replace the TBD Purpose placeholder in all eleven specs with a one-line
description of what each capability covers. Those placeholders predate this
work and were the only reason `openspec validate --all --strict` reported
0 passed / 11 failed; it now reports 11 passed / 0 failed.
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
Wave 2 of replacing the fixed-data mock adapter: the auth domain joins the live
list, so credentials, roles and tokens belong to the real user.
- session: validate a restored token through /auth/me instead of trusting
localStorage, clearing it on 401/403 but keeping it when the API is merely
unreachable; the route guard now uses the validated session
- login: report a 401 as invalid credentials rather than a generic failure, and
drop the 6-character client rule so the API owns the password policy
- register: raise the rule to the API's 8 characters, remove the
verification-code field (its button only counted down and the value was never
sent), and report a duplicate email (409) distinctly
- TopBar and the user profile render their session-dependent branch client-only:
validating the session before hydration made those localStorage-backed
branches report hydration mismatches the previous code did not
Verified against the running backend: wrong password rejected, real JWT issued,
/user reachable, a short password refused with no network call, duplicate email
reported, a tampered token cleared and bounced to sign-in, a stale token kept
when the API is down, and the fixed-data rollback still signs in with the
backend stopped.
Also re-cuts docs/TBD-migrate-wave.md: auth is done, and cart, orders,
shipments and invoices must move together, because the mock adapter keeps their
state in one shared object and a partial flip fails at checkout.
OpenSpec change: openspec/changes/replace-mock-api-wave-2
Wave 1 of replacing the fixed-data mock adapter. The mall now selects its API
adapter per domain, with catalog and currency served live while auth, cart,
orders, shipments and invoices stay on fixed data.
Backend:
- seed the 6 x 2 x 2 category tree as reference data (migration 0005). The API
exposes no category write route, so this cannot come from the seed script
- filter public product listing by the category subtree with a recursive CTE,
matching the mock's existing behaviour instead of exact-match
- add sort=price with order=asc|desc, validated by hand so an unsupported value
returns the project's ApiError 400 shape rather than axum's own rejection
Mall:
- replace the all-or-nothing mockApi boolean with a liveDomains list composed
through a typed per-domain pick map
- source home floors, the category menu, search and product detail from the
catalog API; banners, promos, quick links, store card and comment/coupon
content stay local display-only content
- drop the brand facet and the sales/comments sorts: no backend model backs them
- fix salesOf/commentCountOf, which parsed digits out of the product id and so
rendered "NaN sold" for live UUID ids; they now hash the id
Seed: 24 products across 4 shops, idempotent on re-run.
Note: the mall defaults to a live catalog, so pnpm dev:mall now expects the API
to be running; set NUXT_PUBLIC_LIVE_DOMAINS to an empty array for all-mock work.
OpenSpec change: openspec/changes/replace-mock-api-wave-1
Move the completed change to
openspec/changes/archive/2026-09-17-pin-home-category-menu/ and merge its
delta into openspec/specs/frontend-mall/spec.md.
Modified requirements:
- B2B2C mall-style PC storefront shell: the header stays fully visible
while scrolling (no scroll-based auto-hide), and the category mega-menu
is a hover dropdown on every page except home, where it is pinned in
the hero row. Adds the "header persists while scrolling" and "hover
categories on a non-home page" scenarios.
- Mock PC home page: the hero row is a pinned 240px category sidebar plus
a carousel filling the remainder, both 450px tall, with the mega-menu
overlaying the carousel and banners center-cropped. Adds the "sidebar
stays while scrolling" and "expand a category" scenarios.
Home hero becomes a 450px flex row with a pinned 240x450 category rail
(ShellCategoryMenu `pinned` mode) left of the 960px center-cropped
carousel; hovering a top-level category expands the mega-menu over the
carousel. The header no longer collapses on scroll, and its category
dropdown is now hover-only on non-home pages.
OpenSpec change: openspec/changes/pin-home-category-menu
BREAKING CHANGE: the header logo/search/cart bar no longer auto-hides
past 200px of scroll, and the home page no longer shows the header
category dropdown (it is pinned in the hero row instead).