Wave 6, the last substantive piece of the mock-API migration. Wave 1 removed the
brand facet and the sales/comments sorts for want of a model; sales turn out to
be derivable from order_items and a brand model is a table plus a column.
- a `brands` table with a nullable `products.brand_id` and an ordered admin
replace, mirroring categories and storefront content; a public `GET /api/brands`
and a `brand_id` filter on the catalog, which the search page's facet uses
- `sold_count` per product, computed from `order_items` joined to orders that
reached payment, so an abandoned or cancelled checkout cannot count as a sale.
It is computed per read rather than stored, so it cannot drift from the orders
that produced it
- `sort=sales` alongside `sort=price`; anything else is still a 400
- merchants can set a product's brand through the existing product upsert
- the review UI is gone: the card's review figure and the product detail page's
reviews tab, summary and replies. There is no reviews model, and the mall
attributed invented comments to named shoppers and showed a "good rate". The
now-unreferenced fabrication helpers went with it (`salesOf`, `commentCountOf`,
`commentsFor`, `commentStats`, `salesRankFor`, `productDetail`, `storeDetail`)
Two bugs found by checking rather than trusting: the fixed-data `listProducts`
had silently ignored `brand_id`, `sort` and `order`, so the restored facet
rendered but filtered nothing until the rollback check caught it; and the seed's
brand lookup read back through the shared `r` variable the product loop
reassigns, working once and then throwing.
Verified: 29 backend tests green including a new brand-and-sales case; all three
frontends build; searching filters by brand (24 to 6) and sorts by sales with
counts matching the API; a product page offers detail and after-sale tabs only,
with a real sold count; the fixed-data rollback filters by brand too.
OpenSpec change: openspec/changes/replace-mock-api-wave-6
Planning only: proposal, delta specs, design and tasks. No code yet.
This is the last substantive in-scope box in docs/TBD-migrate-wave.md. Wave 1
removed the brand facet and the sales/comments sorts for want of a model; sales
are derivable from order_items and a brand model is a table plus a column, so
both come back for real.
Decisions recorded in the design:
- brands are reference data in their own table with a nullable products.brand_id
and an ordered replace endpoint, mirroring categories and storefront content
- a "sale" is a unit on an order that reached payment; pending and cancelled
orders do not count, so an abandoned checkout cannot inflate the figure
- sold_count is computed per read rather than stored, so it cannot drift from
the orders that produced it
- the review UI is removed rather than relabelled: the mall attributes invented
comments to named shoppers and shows a "good rate", which a migration that
makes everything else real has no business keeping on screen. Reviews are
recorded as a separate future capability
openspec validate --strict passes and the change is ready to apply.