Files
vmall/openspec/changes/archive/2026-09-17-replace-mock-api-wave-5/tasks.md
T
james b44ba39e8c chore(openspec): archive replace-mock-api-wave-5
The merge creates the store-directory capability (two requirements, six
scenarios) and updates the mall's store-and-marketing requirement so the store
directory and store home read live shops and order surfaces name their shop.

openspec validate --all --strict stays green at 13 specs.
2026-09-17 17:13:49 +00:00

3.9 KiB

Tasks

1. Schema

  • 1.1 Add a migration creating shop_profiles keyed 1:1 to shops (shop_id primary key, logo, banner, company, region, address JSONB, notice JSONB, after_sale JSONB, four score columns, updated_at); verified the table exists after cargo run -p vmall-api

2. Shared contract

  • 2.1 Add ShopProfile and ShopProfileInput to packages/shared/src/types.ts, with every profile field nullable; verified all three frontends build
  • 2.2 Add listShops() / getShop(slug), admin.setShopProfile(id, body) and fixed-data implementations built from MOCK_STORES; also registered a shops domain in the per-domain switch. Unlike wave 4, where that registration was missed and only the browser caught it, here the domain was added alongside the methods

3. Public shop read

  • 3.1 Add unauthenticated GET /api/shops and GET /api/shops/{slug}, both tolerating a missing profile row; verified a suspended shop is absent from the list and that a suspended or unknown slug is a 404
  • 3.2 Add PUT /api/admin/shops/{id}/profile upserting the profile, refusing bilingual fields without non-empty en/zh and gated to platform_admin; verified a rejected payload leaves the stored profile untouched

4. Demo profiles

  • 4.1 Teach scripts/seed-demo.mjs to upsert a profile per demo shop; verified a re-run is idempotent and GET /api/shops returns all four with logos, companies and scores

5. Mall store surfaces

  • 5.1 pages/stores/index.vue: list from GET /api/shops, dropping the distance column, its sort and the whole two-option sort control that existed only to offer it; verified four shops render with their logos and no "km" text. A shop with no logo renders an initial-letter placeholder rather than an invented image
  • 5.2 pages/stores/[id].vue: load the shop by slug and its products from the catalog API by shop_id; verified the profile, its score rows and six products render. Two removals were needed beyond the plan, both following the wave-1 precedent that a UI must not claim what no model backs: the sort is now default + price only (sales and comments have no model), and the "best sellers" rail is gone because nothing ranks by sales
  • 5.3 pages/goods/[id].vue: read the store card from the shared shop read instead of storeById, guarding every field and dropping the card entirely when a shop has no profile
  • 5.4 pages/checkout/pay.vue and pages/user/orders/index.vue: resolve shop names from the same cached shop read; verified order cards name Terra Grocery, Aurora Digital and Demo Store instead of the placeholder
  • 5.5 Moved the pure lowestSku helper out of the fixed-data module into apps/mall/utils/product.ts, re-exported for the mock-era pages, so the live store and product pages no longer import ~/mock/data for it. A first attempt used export { lowestSku } from "~/utils/product", which creates no local binding and broke every internal caller at runtime

6. Verification

  • 6.1 cargo test -p vmall-api green at 28 tests (five new shop tests), and all three frontends build. Caveat worth recording: nuxt build does not typecheck — there is no typescript.typeCheck in nuxt.config.ts and no vue-tsc installed — so a build passing is not the type gate AGENTS.md describes. The lowestSku is not defined bug above passed the build and only failed in the browser, which is how it was found
  • 6.2 Verified in a browser: the directory lists the four demo shops with real logos and no distance text, a store home shows its profile and six products with default/price sorts, the product page's store card names Aurora Digital and links to its store, and order cards name their shop. Console showed only the header's signed-out cart 401, once per page load
  • 6.3 Verified the rollback: with every domain on fixed data and the backend stopped, the directory renders the four mock stores with their logos and a mock store home renders its profile and products