chore(openspec): archive wave 6 and close the migration tracker
The merge creates the brand capability (two requirements) and extends the catalog browse requirement with the brand filter, the sales sort and a real sold count. openspec validate --all --strict stays green at 14 specs. With every box now checked, docs/TBD-migrate-wave.md is deleted, as its own instructions require. Its two durable notes move to the README instead of disappearing with it: - `nuxt build` does not typecheck, so a green build is not the type gate the verification section claimed. AGENTS.md told me to treat it as one, which is why waves 1-5 over-reported "builds pass" as evidence. - the list of what stays deliberately mock: addresses, coupons, favourites, account stats, the seckill/collective/integral pages, reviews, and the fixed-data adapter itself, which the adapter spec requires for rollback. The README's verification block also gains the real test count (29, not 16).
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
See `proposal.md` — Why. Wave 1 removed three things the catalog could not answer: the brand facet, and the sales and comments sorts (`search.vue` keeps only newest and price; `catalog.rs` accepts only `sort=price`).
|
||||
|
||||
Two of them now have real backing:
|
||||
|
||||
- `order_items` (`apps/api/migrations/0004_orders.sql`) records `sku_id` and `qty` per order line, and `skus.product_id` ties those to products — so units sold is derivable.
|
||||
- `products` has no brand column, and `ProductUpsertBody` (`packages/shared/src/api.ts`) carries `category_id`, `slug`, `name`, `description` and `images` only, so merchants cannot set a brand today either.
|
||||
|
||||
The mall's current figures are hashes of the product id: `salesOf` and `commentCountOf` in `apps/mall/mock/data.ts`. `ProductCard.vue` renders both as "N sold · N reviews", and `pages/goods/[id].vue` renders a reviews tab with invented reviewers, ratings and replies.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Both remaining facets answer from real data: the brand filter from a brand table, the sales sort from order lines.
|
||||
- Nothing in the product surfaces presents an invented figure as fact.
|
||||
|
||||
**Non-Goals:**
|
||||
- No reviews capability. Writing, moderating and displaying reviews is a feature with its own lifecycle.
|
||||
- No admin or shop-admin console UI for brands this wave — the API and the seed are enough to make them real.
|
||||
- No change to the seckill, collective or integral pages, nor to coupons, favourites, addresses or the session cookie.
|
||||
|
||||
## Decisions
|
||||
|
||||
**1. Brands are reference data in their own table, with a nullable `products.brand_id`.**
|
||||
Mirrors `categories`: a flat list with a position, administered centrally, referenced by products.
|
||||
*Alternative:* a free-text brand on each product — no filterable vocabulary, and every shop would spell the same manufacturer differently.
|
||||
|
||||
**2. `PUT /api/admin/brands` replaces the ordered list, as storefront content does.**
|
||||
Small ordered lists are edited whole; replacing reindexes positions and makes reordering, adding and deleting one operation.
|
||||
|
||||
**3. A "sale" is a unit on an order that reached payment.**
|
||||
`sold_count` counts `order_items.qty` joined to orders whose status is `paid`, `fulfilling`, `shipped` or `completed`. Orders still `pending_payment` and `cancelled` orders do not count: a cart that was abandoned is not a sale, and a cancelled one was refunded by stock restore.
|
||||
*Alternative:* counting every order line inflates the figure with abandoned checkouts.
|
||||
|
||||
**4. `sold_count` is computed per read, not stored.**
|
||||
A grouped aggregate over `order_items` joins the existing product queries, so the number cannot drift from the orders that produced it.
|
||||
*Alternative:* a `products.sold_count` column maintained when payment or cancellation happens — faster, but it makes correctness depend on every future order-path change, and this is cheap at MVP scale.
|
||||
|
||||
**5. The review UI goes, and reviews become declared future work.**
|
||||
The product cards drop their review figure and the product detail page drops its reviews tab, summary and reply blocks. The mall attributes comments to named shoppers and shows a "good rate" percentage; a migration that turns everything else real has no business keeping invented opinions on screen. The after-sale tab stays, because it shows the shop's own `after_sale` copy.
|
||||
*Alternative:* keep the reviews as sample content labelled as such — rejected: a demo label on a review list does not survive the page being shown to anyone else, and the data is attributed to customers.
|
||||
|
||||
**6. Merchants set a product's brand through the existing product upsert.**
|
||||
`ProductUpsertBody` gains an optional `brand_id`, so `seed-demo.mjs` can assign brands to the demo products and merchants can do the same from their console.
|
||||
*Alternative:* assign brands by direct SQL in the seed — bypasses the API the console uses, and leaves the field unsettable in the product form.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Removing the reviews tab is a visible feature loss] → deliberate, recorded in the proposal and the tracker, with a reviews capability named as the way back.
|
||||
- [The sales aggregate runs on every listing query] → acceptable at this scale; a stored counter is the escape hatch if it ever matters, and decision 4 says why it is not the default.
|
||||
- [A brand can be deleted while products reference it] → `products.brand_id` is `ON DELETE SET NULL`, so products survive with no brand rather than disappearing.
|
||||
- [The brand facet reappears with an empty list until brands are seeded] → the seed assigns the demo brands, and the facet renders only when brands exist.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Ship the migration (brands table plus the nullable column), the brand endpoints and the `sold_count` aggregate; nothing in the mall reads them yet.
|
||||
2. Seed brands and assign them to the demo products.
|
||||
3. Flip the search facet, the sorts, the card figure and the detail page in one commit.
|
||||
4. Rollback: revert those pages. The brand tables are additive, and the fixed-data adapter keeps serving `brandId` from `PRODUCT_BRAND`.
|
||||
Reference in New Issue
Block a user