Add the shared promotion-window EXCLUDE-constraint idea to the deferred designs so the flash/group overlap ordering problem has a recorded structural fix. Note in AGENTS.md that suite assertions must be scoped to fixtures the test created, that a list endpoint warrants two consecutive green runs, and that a piped test command hides the real exit code.
9.0 KiB
TBD — marketing capabilities with no backend (mall mock holdouts)
The mock→live migration finished at replace-mock-api-wave-7 (address book, 2026-09-18).
Every domain that has an API is live; what follows is what still renders from
apps/mall/mock/data.ts because no backend capability exists for it. Each entry is a
new capability (schema + routes + contract + pages), not a domain flip — pick one up by
opening an OpenSpec change, the same way waves 1–7 did.
How to use: check a box only once the behaviour is implemented and verified against
the live backend (cargo run -p vmall-api, node scripts/seed-demo.mjs), then remove the
mock data and the page's ~/mock/data import in the same change.
Delete this file once every box is checked, or consciously dropped and recorded. The "deliberately out of scope" list at the bottom does not block deleting it.
Planning report — 2026-09-18
Five independent, implementation-ready OpenSpec changes were created and validated strictly:
| Change | Business boundary | Prerequisite |
|---|---|---|
add-shop-coupons |
Shop templates, customer claims, per-shop checkout redemption, and cancellation restoration | None |
add-customer-accounts |
Monetary/points accounts, append-only ledger, and live account stats | None |
add-points-mall |
Platform points catalog, atomic redemption, customer history, and admin fulfillment | add-customer-accounts must archive first |
add-flash-sales |
Timed sessions, SKU activity inventory, limits, and checkout pricing | None |
add-group-buying |
Activities, group lifecycle, paid membership, and payment-time capacity | None |
Each change has proposal.md, design.md, specs/, and tasks.md in
openspec/changes/<change>/; all five passed openspec change validate <change> --strict.
Their planning documents use generic B2B2C storefront terminology rather than a source-project
name.
Captured decisions: coupons are template plus customer-owned snapshot and one per shop order; activity-priced shop orders (flash or group) reject coupons; a SKU cannot be in overlapping flash and group windows; account debits use guarded atomic updates with immutable entries; points redemption uses a separate order lifecycle and demo points are seed-credited through the ledger; flash sales use activity-reserved stock and server-calculated prices; group seats are claimed at payment rather than checkout; an unpaid opener cancel closes an empty group; catalog product-detail does not show flash or group prices in these changes.
Recommended implementation order: start add-customer-accounts and/or
add-shop-coupons; archive accounts; then add-points-mall; implement add-flash-sales after
the coupon checkout shape is settled; implement add-group-buying last because it expands the
payment state machine. Favorites can be a separate OpenSpec change at any time (no order
coupling). The deferred designs below intentionally have no change yet.
Mock holdouts with a mall UI today
- Coupons — covered by in-progress
add-shop-coupons(not yet applied). Mall still readsMOCK_COUPONSuntil that change is implemented. - Favorites —
user/favorites.vuelistsMOCK_FAVORITES(products tab + stores tab);user/index.vuederives counts from it;goods/[id].vueheart is localref(false). Ready for its own OpenSpec change whenever convenient: no checkout coupling. Schema:favorites(user_id, product_id | shop_id)with a partial unique index per target kind (see deferred design). APIs:GET/POST/DELETE /api/favorites. - Account stats — covered by in-progress
add-customer-accounts(not yet applied). Mall still readsUSER_STATSuntil that change lands. The change already includes append-only ledgers and guarded mutation; public entry listing stays out of scope. Frozen balance is a reserved kind and stays zero until a freeze flow exists.
Marketing pages that are display-only mock
These exist as full pages (seckill.vue, collective.vue, integral.vue) linked from
the home navigation; all three read fixtures directly.
- Seckill (秒杀) — covered by in-progress
add-flash-sales. Page still readsSECKILL_SESSIONS. Catalog product-detail does not show flash prices in that change. - Collective / 拼团 — covered by in-progress
add-group-buying. Page still reads fixture counts. Paid seats at payment; unpaid opener cancel closes an empty group. - Integral mall / 积分商城 — covered by in-progress
add-points-mallafteradd-customer-accountsarchives. Demo spendable points are seed-credited through the ledger; earning campaigns stay out of that change.
Domains typical B2B2C storefronts have and this MVP does not (no UI here)
Recorded so the gap is explicit, not because all of them belong in scope:
- Reviews / 评价 — no model; the mall presents none (review counts, the detail
page's review tab/summary/replies were removed in wave 6 rather than kept invented).
Missing:
order_comments(order-item bound, rated, replyable), public read on product pages, shop reply, admin moderation. Writing/moderating/displaying reviews is a feature with its own lifecycle. - Distribution / 分销, cashes / 提现 — common B2B2C account modules; no mock,
no UI, no model here. Customer-account freeze/credit is the intended foundation.
Public money-log listing is not in
add-customer-accounts. Deferred design notes below. - Help center / articles — header nav
shell.nav.helpcurrently links to/user; the footer has no help links. No article model. Cheap version: static locale pages; full version: admin-managed articles. - OAuth login, SMS/captcha — common storefront plugins; here auth is email+password only.
- Freight templates / 运费模板 — shop-side shipping-fee rules; checkout currently charges no shipping at all.
Deferred designs from general B2B2C storefronts
These patterns are recorded for later product discovery only. They are not approved scope, have no OpenSpec change, and must be redesigned under VMall's minor-unit, JSONB, RBAC, and transaction rules before implementation.
- Threshold reductions / 满减 — merchant rules need a shop scope, localized name, threshold and reduction in one currency, active window, and an explicit best-eligible rule. Coupon vs flash/group is already exclusive (reject coupon on activity-priced shop orders). Keep 满减 independent from coupons until a stacking policy with those shop coupons is specified.
- Favorites — do not use a polymorphic
target_id/type pair. Keep the existing product-or-shop design with separate nullable foreign keys, a target-kind check, and one partial unique index per target type so duplicate claims are impossible. This can be a dedicated OpenSpec change; it is the smallest remaining mall mock with a UI. - Reviews / 评价 — a future review belongs to a fulfilled order item, not just a product. Preserve an immutable rating/content snapshot, allow one shop reply, and make public visibility and platform moderation explicit lifecycle states.
- Distribution / 分销 — a future merchant activity may configure per-product levels, but commissions must be created as order-item ledger entries and become payable only after the order reaches its chosen settlement condition. Do not use floating commission rates or mutate a balance without a ledger entry.
- Withdrawals / 提现 — model a payout request with account, amount minor, fee minor,
destination snapshot, and
requested → approved | rejected | paidtransitions. Funds must be frozen and released or debited atomically with the transition. - Promotion window exclusivity as a database invariant — flash sales and group buying
currently keep one SKU out of overlapping activity windows with cross-table queries, and the
flash-sales guard stays inert until the group-buying table exists. A shared
promotion_windows(sku_id, kind, starts_at, ends_at)table withEXCLUDE USING gist (sku_id WITH =, tstzrange(starts_at, ends_at) WITH &&)would make an overlap impossible to insert and remove the creation-order problem. Deferred because it retrofits the archived flash-sales schema; revisit if a third timed activity type appears.
Deliberately out of scope — does not block deleting this file
- The fixed-data adapter itself (
apps/mall/mock/api.ts,~/mock/data): the Mock API adapter spec requires it to keep serving every domain as the rollback path. Removing the fixtures above means pages stop reading them; the adapter stays.
Invariants to keep when implementing any box
- Money is
i64minor units + currency code; no float math anywhere. - New user-facing content fields are
{en, zh}JSONB; UI copy goes through$t(). - Contract changes land only in
packages/sharedand all three frontends must still build. - State transitions validate preconditions (
UPDATE ... WHERE status = ...pattern). - Each capability gets its own
openspec/changes/<name>/and archives green.