chore(openspec): archive the mock-migration waves and green the spec set

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.
This commit is contained in:
2026-09-17 16:36:10 +00:00
parent e1a0a5dbdb
commit 2136a48fbe
28 changed files with 84 additions and 30 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# auth Specification
## Purpose
TBD - created by archiving change foundation-auth. Update Purpose after archive.
Buyer registration, sign-in and current-user lookup backed by the API's JWT auth.
## Requirements
### Requirement: Customer registration
The API SHALL provide `POST /api/auth/register` accepting email, password, display_name. New users are created with role `customer`. Duplicate emails MUST be rejected with 409.
+7 -2
View File
@@ -1,10 +1,12 @@
# cart Specification
## Purpose
TBD - created by archiving change cart-checkout-orders. Update Purpose after archive.
A per-user Redis cart holding SKU quantities, read back with product, price, stock and shop snapshots.
## Requirements
### Requirement: Server-side cart
Authenticated shoppers SHALL have a Redis-backed cart keyed by user id, containing sku_id + qty entries.
Authenticated shoppers SHALL have a Redis-backed cart keyed by user id, containing sku_id + qty entries. Reading the cart SHALL return, for every line, the SKU's current price, currency and stock together with the product's name, image and owning shop, so the storefront can group lines by shop and cap quantity without reading the fixed-data catalog. Lines whose SKU has become inactive or unpurchasable SHALL be omitted from the view.
#### Scenario: add and update
- **WHEN** a shopper POSTs sku + qty, then PUTs a new qty
@@ -14,3 +16,6 @@ Authenticated shoppers SHALL have a Redis-backed cart keyed by user id, containi
- **WHEN** adding a SKU that is inactive or whose product is not published
- **THEN** the API returns 400
#### Scenario: cart view carries shop and stock
- **WHEN** a shopper reads a cart holding SKUs from more than one shop
- **THEN** each line reports its shop and the SKU's current stock, so the storefront can group the lines per shop and cap quantity from the response alone
+21 -1
View File
@@ -1,8 +1,10 @@
# catalog Specification
## Purpose
TBD - created by archiving change catalog-i18n-currency. Update Purpose after archive.
The public product and category catalogue: localized content, publish lifecycle, shop isolation, SKU pricing and public browse.
## Requirements
### Requirement: Localized product content
Product and category names/descriptions SHALL be stored as JSONB maps keyed by locale (`en`, `zh`). The API MUST return the full map; clients pick the display locale.
@@ -37,3 +39,21 @@ Each SKU SHALL carry `price_minor` (integer minor units) and an ISO `currency` c
- **WHEN** upserting a SKU with stock < 0
- **THEN** the API returns 400
### Requirement: Public product browse
Public `GET /api/products` SHALL return only `published` products whose shop is active, and SHALL remain readable without authentication. When `category_id` is supplied, the filter SHALL match that category **and every category beneath it**, so requesting a parent category returns products assigned to its child and grandchild categories. The listing SHALL accept an optional `sort` of `price` together with an `order` of `asc` or `desc`, ordering by each product's lowest active SKU price; any other `sort` value SHALL be rejected with a 400 `ApiError` rather than silently ignored. An unsorted listing SHALL order newest first. Paging SHALL keep returning `page` and `per_page` alongside the filtered `total`.
#### Scenario: parent category includes descendant products
- **WHEN** a shopper requests products for a category that has child categories holding published products
- **THEN** the response contains the products assigned to those descendant categories, not only those assigned directly to the requested category
#### Scenario: sort by lowest active SKU price
- **WHEN** a shopper requests the product list with `sort=price` and `order=asc`
- **THEN** products come back ordered by their lowest active SKU price ascending
#### Scenario: unsupported sort is rejected
- **WHEN** a client requests a `sort` value that is not `price`
- **THEN** the API responds 400 with an `ApiError` body instead of ignoring the parameter
#### Scenario: unpublished products never appear
- **WHEN** any public listing or filter is applied
- **THEN** products that are not `published`, or whose shop is not active, are absent from both `items` and `total`
+1 -1
View File
@@ -1,7 +1,7 @@
# currency Specification
## Purpose
TBD - created by archiving change catalog-i18n-currency. Update Purpose after archive.
The currency registry and integer-minor-unit conversion between currencies.
## Requirements
### Requirement: Currency registry
The system SHALL maintain a currencies table: ISO code, localized name, symbol, exponent (minor units), enabled flag, and `rate_to_base` (NUMERIC). Exactly one currency is the base.
+1 -1
View File
@@ -1,7 +1,7 @@
# frontend-admin Specification
## Purpose
TBD - created by archiving change frontend-apps. Update Purpose after archive.
The platform console for managing users, shops and currencies.
## Requirements
### Requirement: Platform user and shop management
Platform admins SHALL assign user roles (with shop scope), create shops, and suspend/activate shops. Suspended shops' products MUST NOT be purchasable (enforced by API, reflected in UI).
+48 -19
View File
@@ -1,8 +1,10 @@
# frontend-mall Specification
## Purpose
TBD - created by archiving change frontend-apps. Update Purpose after archive.
The buyer-facing storefront: shell, home page, discovery, shopping and transaction flows, and the buyer center.
## Requirements
### Requirement: Localized storefront
The mall SHALL render every UI string and all catalog/store/marketing mock content in en or zh from one switcher, defaulting to en. Switching locale SHALL update the desktop shell and current page without a full reload.
@@ -18,12 +20,16 @@ The mall SHALL offer a currency switcher sourced from the selected API adapter a
- **THEN** the displayed price reflects the selected adapter's conversion rate with JPY exponent 0 and no floating-point money arithmetic
### Requirement: Shopping flow
A shopper SHALL be able to browse, view detail, add to cart, checkout with a shipping address, pay (mock), track orders/shipments, confirm delivery, and request an invoice through the selected API adapter. The MVP SHALL default to the fixed-data mock adapter and SHALL not require the live API.
A shopper SHALL be able to browse, view detail, add to cart, checkout with a shipping address, pay, track orders/shipments, confirm delivery, and request an invoice against the live API. Cart, order, shipment and invoice state SHALL be the backend's rather than the browser's, and the fixed-data adapter SHALL remain available as a configured fallback rather than the default. Adding to the cart SHALL require an authenticated shopper: an anonymous add SHALL send the shopper to sign in and return them to where they left off.
#### Scenario: end-to-end purchase
- **WHEN** a shopper completes checkout on a non-empty mock cart
- **WHEN** a shopper completes checkout on a non-empty cart
- **THEN** the resulting order appears in the buyer center and the cart is empty
#### Scenario: anonymous add prompts sign-in
- **WHEN** a signed-out shopper adds an in-stock SKU from a product page
- **THEN** they are sent to sign in and, once signed in, returned to that product page
### Requirement: B2B2C mall-style PC storefront shell
The mall SHALL render a buyer-facing desktop shell modeled on a classic B2B2C PC mall: a 30px utility bar, logo/search/cart header, dark primary navigation with a hover category mega-menu, a 1200px content grid, and a value-proposition footer. The header SHALL remain fully visible while scrolling; no part of the shell SHALL auto-hide based on scroll position. The category mega-menu SHALL appear as a hover dropdown under the navigation "All Categories" entry on every page except the home page, where it is instead pinned in the hero row. The visual language SHALL use #ca151e for brand/price/active states, #f5f5f5 section backgrounds, gray hairline borders, compact controls, and product-card hover lift/shadow. The implementation SHALL use Nuxt-native semantic components and SHALL NOT depend on Element Plus.
@@ -40,18 +46,22 @@ The mall SHALL render a buyer-facing desktop shell modeled on a classic B2B2C PC
- **THEN** the category mega-menu dropdown appears below the navigation and hides again on mouse leave
### Requirement: Mock API adapter
The mall SHALL default to a fixed-data mock adapter that implements the existing `@vmall/shared` API client surface. The adapter SHALL support auth, catalog, currency conversion, cart mutation, checkout, order/payment/shipment state, and invoices without a backend. A runtime configuration switch SHALL select the live API client later without changing page call sites.
The mall SHALL select its API adapter per domain, so one domain can be served by the live backend while the others stay on fixed data. The mall SHALL still ship a fixed-data adapter implementing the whole `@vmall/shared` API client surface, and the live/fixed choice SHALL be configurable per domain without changing page call sites. The fixed-data adapter SHALL remain able to serve every domain when the live backend is unavailable.
#### Scenario: mall runs without backend
- **WHEN** the mall starts with mock mode enabled and the API service is unavailable
- **THEN** browsing, cart, checkout, payment, orders and invoice pages return deterministic mock data and remain functional
- **WHEN** the mall starts with the API service unavailable and every domain configured to fixed data
- **THEN** browsing, cart, checkout, payment, orders and invoice pages return deterministic fixed data and remain functional
#### Scenario: domains migrate independently
- **WHEN** the live backend serves the catalog and currency domains while auth, cart, orders, shipments and invoices remain on fixed data
- **THEN** browsing and prices come from the backend while those other flows keep working against fixed data
### Requirement: Mock PC home page
The mall home page SHALL render a hero row composed of a pinned 240px category sidebar on the left and a hero carousel filling the remainder of the 1200px grid, both 450px tall and occupying layout space (not overlaid). The sidebar SHALL list all top-level categories with up to three child links each; hovering a top-level category SHALL expand the mega-menu panel to the right over the carousel. Banner images SHALL render at fixed 450px height, center-cropped horizontally to the narrower carousel width. Below the hero, the page SHALL render a six-item quick-link strip with promotion tiles and bilingual product floors with a left advert and product-card grid.
The mall home page SHALL render a hero row composed of a pinned 240px category sidebar on the left and a hero carousel filling the remainder of the 1200px grid, both 450px tall and occupying layout space (not overlaid). The sidebar SHALL list the catalog's top-level categories with up to three child links each; hovering a top-level category SHALL expand the mega-menu panel to the right over the carousel. Banner images SHALL render at fixed 450px height, center-cropped horizontally to the narrower carousel width. Below the hero, the page SHALL render a six-item quick-link strip with promotion tiles and bilingual product floors, where each floor's products come from the catalog API while the banner, promotion, quick-link and floor advert assets remain local content.
#### Scenario: shopper lands on home
- **WHEN** `/` loads in mock mode
- **THEN** the category sidebar is visible to the left of the carousel without any hover or click, the carousel renders center-cropped banners at 450px height, and the quick links, promotions and every non-empty product floor render fixed mock content
- **WHEN** `/` loads
- **THEN** the category sidebar is visible to the left of the carousel without any hover or click, the carousel renders center-cropped banners at 450px height, and the quick links, promotions and every non-empty product floor render, with floor products sourced from the catalog API
#### Scenario: sidebar stays while scrolling
- **WHEN** a shopper scrolls the home page beyond 200px
@@ -59,28 +69,48 @@ The mall home page SHALL render a hero row composed of a pinned 240px category s
#### Scenario: expand a category
- **WHEN** a shopper hovers a top-level category in the pinned sidebar
- **THEN** the mega-menu panel expands to the right, overlaying the carousel with that category's child and grandchild links
- **THEN** the mega-menu panel expands to the right, overlaying the carousel with that category's child and grandchild links from the catalog API
### Requirement: Product discovery pages
The mall SHALL provide `/search` with breadcrumb, category, brand and sort controls, a five-column desktop product grid, pagination and an empty state. It SHALL provide `/goods/[id]` with image gallery/zoom, bilingual name/subtitle, integer-minor-unit prices, attribute and SKU selection, stock-aware quantity, coupons/activity summary, store card, sales rail, and detail/comments/after-sale tabs.
The mall SHALL provide `/search` with breadcrumb, category and sort controls, a five-column desktop product grid, pagination and an empty state, listing products from the catalog API filtered by the selected category's subtree. The sort control SHALL offer newest-first and price ascending/descending only. It SHALL provide `/goods/[id]` rendering product and SKU data from the catalog API with image gallery/zoom, bilingual name/subtitle, integer-minor-unit prices, attribute and SKU selection, stock-aware quantity, store card, and detail/comments/after-sale tabs whose comment, coupon and sales content stays local display-only content.
#### Scenario: filter and inspect a product
- **WHEN** a shopper filters the search page and opens a product
- **THEN** matching mock cards are shown and selecting an in-stock SKU updates the displayed price, stock and cart target
- **WHEN** a shopper filters the search page by a parent category and opens a product
- **THEN** products from that category and its descendants are listed, and selecting an in-stock SKU updates the displayed price, stock and cart target from the catalog API
### Requirement: Mock transaction flow
The mall SHALL provide a store-grouped cart, address-selecting checkout preview, mock payment selection and payment-success result. Cart quantity/removal, selection totals, checkout and mock payment SHALL update the in-memory mock state for the current browser session.
The mall SHALL provide a store-grouped cart, address-selecting checkout preview, payment selection and payment-success result, all reading and writing the live cart and order APIs. Quantity changes, removals, selection totals, checkout and payment SHALL be persisted by the backend for the signed-in shopper, so they survive a page reload.
#### Scenario: complete mock purchase
- **WHEN** a shopper adds an in-stock SKU, checks out with a mock address and confirms a mock payment
- **WHEN** a shopper adds an in-stock SKU, checks out with a mock address and confirms a payment
- **THEN** the cart is cleared, the success page is shown and the new order appears in the user order list
#### Scenario: cart survives a reload
- **WHEN** a signed-in shopper adds an item and then reloads the page
- **THEN** the cart still holds that item, priced and stocked from the catalog
### Requirement: Auth and buyer center
The mall SHALL provide B2B2C mall-style login, register and forgot-password panels backed by deterministic mock auth. `/user` SHALL render a two-column buyer center with dashboard, order list/detail, addresses, favorites, coupons and invoices.
The mall SHALL provide B2B2C mall-style login, register and forgot-password panels backed by the live auth API, so credentials, roles and tokens belong to the real user rather than a fixed demo account. Registration SHALL require a password of at least 8 characters, matching the API's rule, and the register panel SHALL NOT ask for a verification code because no endpoint issues one. Failures SHALL be reported distinctly: invalid credentials on sign-in, and an already-registered email on registration. A token restored from storage SHALL be validated against the auth API on load, and a rejected token SHALL clear the session and return the shopper to sign-in. `/user` SHALL render a two-column buyer center with dashboard, order list/detail, addresses, favorites, coupons and invoices.
#### Scenario: sign in and inspect buyer data
- **WHEN** a shopper submits the mock login form and opens `/user`
- **THEN** the buyer-center shell and fixed account/order/address/favorite/coupon/invoice data render without backend access
- **WHEN** a shopper signs in with valid credentials and opens `/user`
- **THEN** the session carries the authenticated user, and the buyer-center shell and its fixed account/order/address/favorite/coupon/invoice data render
#### Scenario: wrong password rejected
- **WHEN** a shopper submits a password that does not match the account
- **THEN** sign-in fails with an invalid-credentials message and no session is established
#### Scenario: short password refused before the API call
- **WHEN** a shopper submits a registration password shorter than 8 characters
- **THEN** the panel asks for at least 8 characters without calling the API
#### Scenario: duplicate email reported
- **WHEN** a shopper registers an email that already has an account
- **THEN** the panel reports that the email is already registered rather than a generic failure
#### Scenario: rejected token clears the session
- **WHEN** a token restored from storage is rejected by the auth API
- **THEN** the stored session is cleared and the shopper is returned to sign-in
### Requirement: Store and marketing pages
The mall SHALL provide a store directory, store home, timed seckill page, collective-buy list and points-mall home using fixed bilingual mock content. Marketing pages MAY be display-only except navigation to product detail.
@@ -88,4 +118,3 @@ The mall SHALL provide a store directory, store home, timed seckill page, collec
#### Scenario: navigate storefront discovery channels
- **WHEN** a shopper opens stores, seckill, collective or integral routes
- **THEN** each page renders the appropriate B2B2C mall-style banner/filter/session/card layout and product links resolve to mock product details
+1 -1
View File
@@ -1,7 +1,7 @@
# frontend-shop-admin Specification
## Purpose
TBD - created by archiving change frontend-apps. Update Purpose after archive.
The merchant console for managing a shop's products and fulfilling its orders.
## Requirements
### Requirement: Merchant product management
Shop users SHALL manage only their own shop's products: create/edit bilingual content, manage SKUs, publish/unpublish with immediate effect on the storefront.
+1 -1
View File
@@ -1,7 +1,7 @@
# invoice Specification
## Purpose
TBD - created by archiving change fulfillment-invoices. Update Purpose after archive.
Invoice requests raised by buyers and their issuance by shops.
## Requirements
### Requirement: Invoice request
Customers SHALL request at most one open invoice (发票) per order, with title, kind (`personal | company`), and tax_no required for company invoices. Amount equals the order total in the order currency.
+1 -1
View File
@@ -1,7 +1,7 @@
# order Specification
## Purpose
TBD - created by archiving change cart-checkout-orders. Update Purpose after archive.
Checkout splitting a cart into per-shop orders, plus order lifecycle and ownership.
## Requirements
### Requirement: Checkout splits by shop
`POST /api/orders/checkout` SHALL create one order per distinct shop in the cart, in a single database transaction: stock decrement, order + item insert with price snapshots, cart clear. All amounts use the cart's SKU currencies converted into the buyer-chosen display currency at checkout time.
+1 -1
View File
@@ -1,7 +1,7 @@
# rbac Specification
## Purpose
TBD - created by archiving change foundation-auth. Update Purpose after archive.
The role model and the enforcement of role- and shop-scoped access.
## Requirements
### Requirement: Role model
The system SHALL support roles `platform_admin`, `shop_owner`, `shop_staff`, `customer`. Shop roles MUST carry a `shop_id` scope.
+1 -1
View File
@@ -1,7 +1,7 @@
# shipment Specification
## Purpose
TBD - created by archiving change fulfillment-invoices. Update Purpose after archive.
Shop-created shipments and buyer-confirmed delivery.
## Requirements
### Requirement: Shipment creation
Merchants SHALL create shipments (发货单) for their own orders in `paid` or `fulfilling` status, specifying carrier, tracking_no and per-item quantities.