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>
This commit is contained in:
co-authored by
Cursor
parent
94a64ec712
commit
6c1357ec4d
+33
-41
@@ -1,10 +1,9 @@
|
||||
# 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.
|
||||
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
|
||||
@@ -15,9 +14,9 @@ mock data and the page's `~/mock/data` import in the same change.
|
||||
|
||||
---
|
||||
|
||||
## Planning report — 2026-09-18
|
||||
## Implementation report — 2026-09-21
|
||||
|
||||
Five independent, implementation-ready OpenSpec changes were created and validated strictly:
|
||||
Five independent OpenSpec changes were implemented, verified, archived, and synced to main specs:
|
||||
|
||||
| Change | Business boundary | Prerequisite |
|
||||
| --- | --- | --- |
|
||||
@@ -27,10 +26,10 @@ Five independent, implementation-ready OpenSpec changes were created and validat
|
||||
| `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.
|
||||
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
|
||||
@@ -40,38 +39,32 @@ flash sales use activity-reserved stock and server-calculated prices; group seat
|
||||
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.
|
||||
**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** — covered by in-progress `add-shop-coupons` (not yet applied). Mall still
|
||||
reads `MOCK_COUPONS` until that change is implemented.
|
||||
- [ ] **Favorites** — `user/favorites.vue` lists `MOCK_FAVORITES` (products tab +
|
||||
stores tab); `user/index.vue` derives counts from it; `goods/[id].vue` heart is local
|
||||
`ref(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 reads `USER_STATS` until 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.
|
||||
- [x] **Coupons** — implemented and archived as `2026-09-18-add-shop-coupons`; mall product,
|
||||
customer coupon, and checkout surfaces use the live coupon API.
|
||||
- [x] **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.
|
||||
- [x] **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.
|
||||
|
||||
## Marketing pages that are display-only mock
|
||||
## Implemented marketing pages
|
||||
|
||||
These exist as full pages (`seckill.vue`, `collective.vue`, `integral.vue`) linked from
|
||||
the home navigation; all three read fixtures directly.
|
||||
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 (秒杀)** — covered by in-progress `add-flash-sales`. Page still reads
|
||||
`SECKILL_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-mall` after
|
||||
`add-customer-accounts` archives. Demo spendable points are seed-credited through the
|
||||
ledger; earning campaigns stay out of that change.
|
||||
- [x] **Seckill (秒杀)** — implemented and archived as `2026-09-18-add-flash-sales`.
|
||||
Catalog product detail intentionally does not show activity prices.
|
||||
- [x] **Collective / 拼团** — implemented and archived as `2026-09-18-add-group-buying`.
|
||||
Paid seats are claimed at payment; unpaid opener cancellation closes an empty group.
|
||||
- [x] **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)
|
||||
|
||||
@@ -102,10 +95,9 @@ transaction rules before implementation.
|
||||
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.
|
||||
- [x] **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.
|
||||
|
||||
Reference in New Issue
Block a user