chore(openspec): archive replace-mock-api-wave-4

The merge creates the storefront-content capability (two requirements, five
scenarios) and updates the mall's home-page requirement so its banner,
promotion, quick-link and floor advert content comes from the content API.

openspec validate --all --strict stays green at 12 passed / 0 failed.
This commit is contained in:
2026-09-17 16:49:35 +00:00
parent 104737e4e1
commit f6ddfd21cb
8 changed files with 38 additions and 2 deletions
+6 -2
View File
@@ -57,11 +57,11 @@ The mall SHALL select its API adapter per domain, so one domain can be served by
- **THEN** browsing and prices come from the backend while those other flows keep working against fixed data - **THEN** browsing and prices come from the backend while those other flows keep working against fixed data
### Requirement: Mock PC home page ### 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 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. 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 and the banners, promotions, quick links and floor advert art come from the content API.
#### Scenario: shopper lands on home #### Scenario: shopper lands on home
- **WHEN** `/` loads - **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 - **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 from the catalog API and the marketing content from the content API
#### Scenario: sidebar stays while scrolling #### Scenario: sidebar stays while scrolling
- **WHEN** a shopper scrolls the home page beyond 200px - **WHEN** a shopper scrolls the home page beyond 200px
@@ -71,6 +71,10 @@ The mall home page SHALL render a hero row composed of a pinned 240px category s
- **WHEN** a shopper hovers a top-level category in the pinned sidebar - **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 from the catalog API - **THEN** the mega-menu panel expands to the right, overlaying the carousel with that category's child and grandchild links from the catalog API
#### Scenario: home renders with no content published
- **WHEN** the content API returns empty lists
- **THEN** the page still renders its category sidebar and product floors instead of failing
### Requirement: Product discovery pages ### Requirement: Product discovery pages
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. 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.
+32
View File
@@ -0,0 +1,32 @@
# storefront-content Specification
## Purpose
The storefront's editable marketing content: the home page's banners, promotion tiles, quick links and floor advert art, written by platform admins and read publicly.
## Requirements
### Requirement: Public home content
`GET /api/content/home` SHALL be readable without authentication and SHALL return the active home content as four ordered lists: `banners`, `promos`, `quick_links` and `floor_adverts`. Each entry SHALL carry what the storefront renders: an image URL and a destination URL for banners and promos, a bilingual label and an inline SVG glyph for quick links, and an image URL for floor adverts, whose destination is the floor's own category. Entries flagged inactive SHALL never appear, and each list SHALL be ordered by its stored position.
#### Scenario: only active content is served
- **WHEN** a shopper loads the home content while one banner is marked inactive
- **THEN** that banner is absent from `banners`, and the remaining entries keep their stored order
#### Scenario: an empty kind still answers
- **WHEN** a kind has no active entries
- **THEN** the response returns an empty list for it rather than omitting the key or failing, so the page renders without that block
### Requirement: Content management
Platform admins SHALL read all home content with `GET /api/admin/content` and replace one kind at a time with `PUT /api/admin/content/{kind}`, where `kind` is `banners`, `promos`, `quick-links` or `floor-adverts`. A replacement SHALL validate every entry, apply as a single transaction, and reindex positions from the submitted order. A rejected entry SHALL leave the stored content exactly as it was. Writing SHALL require the `platform_admin` role.
#### Scenario: replace reorders and reindexes
- **WHEN** an admin submits the same three banners in a different order
- **THEN** a subsequent public read returns them in the new order
#### Scenario: an invalid entry is rejected atomically
- **WHEN** an admin submits a list whose second entry is missing its image URL
- **THEN** the request fails and the previously stored list is unchanged
#### Scenario: non-admins cannot write
- **WHEN** a signed-in customer or shop owner sends a replacement
- **THEN** the API refuses the write