Files
vmall/docs/TBD-marketing.md
T
Chengdong ZhangandCursor 6c1357ec4d feat: persist customer product and shop favorites through the live API
Replace mall fixture favorites with customer-scoped endpoints, and send signed-out shoppers back to the page they left after sign-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 18:51:45 +08:00

8.4 KiB

TBD — marketing capabilities with no backend (mall mock holdouts)

The mock→live migration and the first marketing implementation wave are complete. Every domain with an API is live; only the unchecked items below still render from apps/mall/mock/data.ts because no backend capability exists for them. Each remaining entry is a new capability, not a domain flip, and requires its own OpenSpec change.

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.


Implementation report — 2026-09-21

Five independent OpenSpec changes were implemented, verified, archived, and synced to main specs:

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 is archived under openspec/changes/archive/2026-09-18-<change>/; its requirements are present in the main OpenSpec specs. Backend migrations 0010 through 0014, API modules, shared contracts, and live frontend domain picks are present. Source-project names are absent from the planning artifacts.

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.

Implemented order: add-customer-accounts, add-shop-coupons, add-points-mall, add-flash-sales, then add-group-buying. Favorites is implemented as add-favorites. The deferred designs below intentionally have no change yet.

Mock holdouts with a mall UI today

  • Coupons — implemented and archived as 2026-09-18-add-shop-coupons; mall product, customer coupon, and checkout surfaces use the live coupon API.
  • Favorites — implemented as add-favorites; customer product/shop favorites persist through the live API. Mall product/store detail, buyer-center list, and dashboard preview use the selected adapter instead of MOCK_FAVORITES or local heart state.
  • Account stats — implemented and archived as 2026-09-18-add-customer-accounts. The mall reads the live summary; append-only ledgers and guarded mutation back later flows. Frozen balance remains zero until a freeze flow exists.

Implemented marketing pages

The former display-only pages now use live backend domains; their fixtures remain only inside the fixed-data adapter as the required rollback implementation.

  • Seckill (秒杀) — implemented and archived as 2026-09-18-add-flash-sales. Catalog product detail intentionally does not show activity prices.
  • Collective / 拼团 — implemented and archived as 2026-09-18-add-group-buying. Paid seats are claimed at payment; unpaid opener cancellation closes an empty group.
  • Integral mall / 积分商城 — implemented and archived as 2026-09-18-add-points-mall; demo points are ledger credits and earning campaigns remain out.

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.help currently 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 — implemented as add-favorites with explicit nullable product/shop foreign keys, an exactly-one-target check, and one partial unique index per kind. Do not revisit a polymorphic target_id/type pair.
  • 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 | paid transitions. 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 with EXCLUDE 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 i64 minor 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/shared and 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.