Commit Graph
70 Commits
Author SHA1 Message Date
Chengdong Zhang 772aafa3fb docs(openspec): migration plan handoff notes for next agent 2026-09-24 17:37:30 +08:00
Chengdong Zhang 8181b3544b docs: visual design guidelines (tokens, components, interaction states) 2026-09-24 17:11:51 +08:00
Chengdong Zhang a968327b12 docs: architecture, guidelines, domain designs, progressive code index 2026-09-24 16:59:32 +08:00
Chengdong Zhang c532f87b03 feat(reviews): order-line reviews with merchant reply and platform moderation (add-product-reviews) 2026-09-24 16:38:05 +08:00
Chengdong Zhang 473d19d089 feat(freight): shop freight templates, server-side checkout fees, company dictionary (add-freight-templates) 2026-09-24 15:03:51 +08:00
Chengdong Zhang 5b426486ac wip(freight): migration 0017 + model/column-list groundwork for add-freight-templates 2026-09-23 17:39:45 +08:00
Chengdong Zhang a83e857bdf fix(mall): user-center content column full width + aftersales menu entry 2026-09-23 17:14:14 +08:00
Chengdong Zhang 2c2b54c21c feat(aftersale): per-line refund/return flow with ledger-backed completion (add-aftersale-refunds) 2026-09-23 16:56:30 +08:00
Chengdong Zhang 93e5a05d48 feat(admin,shop-admin): content/brand management + merchant shop profile (add-content-admin-ui) 2026-09-23 14:27:00 +08:00
Chengdong Zhang 9a749e2551 docs(openspec): propose 8 tigshop migration changes (P0-P7) + migration plan 2026-09-23 13:30:36 +08:00
Chengdong Zhang 0d0e10b97b feat(ui): adopt Tailwind v4 design system and archive change
- Add tailwindcss v4 + @tailwindcss/vite to mall, shop-admin, admin
- Add @vmall/shared/theme.css tokens with html[data-accent] presets
- Add @vmall/ui kit (VBtn/VBadge/VField/VInput/VCard/VPanel/VTable/VPage,
  VAccentSwatch, useAccent) as a Nuxt module
- Convert all three apps to kit + utilities; delete ui.css/mall.css and
  every <style scoped>; consoles get accent presets, mall locked to red
- Fix VCard boolean prop default (padding) and PDP/store stale
  useAsyncData keys on param navigation
- Archive adopt-tailwind-design-system; new frontend-ui capability spec
2026-09-22 18:22:50 +08:00
Chengdong Zhang 4d2ee3b0bf Archive add-favorites and split-catalog-into-product-category-brand
- Sync favorites capability spec and frontend-mall live-favorites
  requirement into openspec/specs/
- Split catalog spec into new category and product capability specs;
  remove now-empty catalog spec
- Move both completed changes to openspec/changes/archive/
- Ignore .superpowers/ scratch directory
2026-09-22 16:27:42 +08:00
Chengdong ZhangandClaude Sonnet 5 13cd598a45 fix(mall): prevent floor grid columns from overflowing container
`min-width: 0` on the flex item and `minmax(0, 1fr)` columns stop the
grid tracks from growing past their content and overflowing the
floor-grid's flex container.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:08:35 +08:00
Chengdong ZhangandClaude Sonnet 5 8446650baf refactor(api): split catalog module into product, category, brand
Rearranges the Rust backend by domain: `modules/catalog` bundled four
distinct concepts (Product, SKU, Category, Brand) behind one router
and one 392-line service file, unlike every other module in the
codebase which owns exactly one bounded aggregate.

Splits into `modules/product` (Product/SKU, publish lifecycle,
shop-scoped CRUD), `modules/category` (category tree, subtree query),
and `modules/brand` (brand list, admin replace-all). `Category`/`Brand`
move out of the shared `models.rs` into their owning modules;
`Product`/`Sku` stay since `favorite`/`flash_sale`/`group_buying`
reference them across modules. Purely internal restructuring — no
route, schema, or behavior changes.

Implements openspec change split-catalog-into-product-category-brand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:07:44 +08:00
Chengdong Zhang 0b594c0147 fix: harden favorite state consistency 2026-09-21 19:13:10 +08:00
Chengdong ZhangandCursor a4ef6ca49e chore(api): apply rustfmt across modules and tests
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 18:52:52 +08:00
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
Chengdong Zhang 94a64ec712 chore: add missing Nuxt tsconfig.json for editor path resolution 2026-09-21 13:08:20 +08:00
james 4d8062f176 fix(mall): normalise group quantity and drop abandoned intents
Opening or joining a group now sets the activity SKU to quantity 1 instead of
accumulating, so a retry after a failed checkout - or a second click - can no
longer push the line to quantity 2 and then fail the quantity-1 intent with an
opaque error.

The stashed group intent is cleared when the checkout page unmounts without
submitting, and when its SKU is no longer in the cart, so an abandoned intent
cannot silently reprice a later order or keep the coupon picker hidden.
2026-09-18 13:44:02 +00:00
james d7fddf53ab fix(api): record an elapsed group as expired when its opener cancels
Cancelling the opening unpaid order ran the expiry sweep only implicitly, so a
group whose lifetime had already ended was relabelled cancelled instead of
expired. Both conditions hold in that case; sweep first so the lifetime ending
wins. The behaviour was invisible to shoppers (neither state is joinable) but it
matters to a future refund flow reading expired paid groups.

Add an integration test that reproduces the precedence.
2026-09-18 13:44:02 +00:00
james cdfb1b06f2 chore(openspec): archive add-group-buying
Sync the group-buying capability plus the order, frontend-mall, and
frontend-shop-admin requirements into the main specs, and write the capability
Purpose the archive leaves as a placeholder. This closes the last of the five
planned marketing changes.
2026-09-18 13:22:47 +00:00
james 48ec5b4443 test(api): pass the group-buy intent in direct checkout callers
The checkout service gained an intent parameter; the order-service tests call it
directly and must pass none.
2026-09-18 13:22:47 +00:00
james 3fa104ca61 chore(seed): seed an active group-buying activity
Add one active activity for the demo shop on a SKU that is not already in the
seeded flash sale, idempotent by name, so the live mall can open and join a
group.
2026-09-18 13:22:47 +00:00
james fe354529d0 feat(shop-admin): manage group-buying activities
Add a group-buying page where a shop user lists, creates, edits, and deletes
its own activities, configuring the SKU, localized name and description, group
price, required paid members, active window, and group lifetime, and seeing each
activity's open groups. Expose it as a group-buying navigation entry.
2026-09-18 13:22:47 +00:00
james e540b91491 feat(mall): live group-buying page with open or join intent
The group-buying page lists active activities, the group price against the
catalog price, and each open group with its paid-member count and expiry. A
shopper opens a group or picks one to join; either way the activity SKU goes
into the cart and the intent is carried to checkout, which submits it and hides
the coupon picker for that order. Payment and order detail show the snapshotted
group price.

The group-buying fixtures leave the page; the fixed-data adapter still serves
the domain as the rollback path.
2026-09-18 13:22:47 +00:00
james a7bc476251 feat(api): group buying with payment-time seat claims
Shop-owned activities on one SKU, concrete groups with an
open/successful/expired/cancelled lifecycle, and one paid membership row per
paid order. Checkout accepts a single-SKU quantity-1 intent, snapshots the group
price and identity on the pending order, and opens or references a group; a paid
seat is claimed only at payment, which locks the group and fills it exactly at
capacity.

Pending-payment cancellation restores SKU stock only and never rolls back paid
seats; an unpaid opener cancelling closes a still-empty group. Coupons are
refused on a group shop order, and the flash-sale exclusion is now enforced in
both directions because the activity table exists, which activates the guard
add-flash-sales shipped dormant. The activity column names follow the contract
recorded in this change's design.

Surfaces (shop-admin, mall) and seeding follow.
2026-09-18 13:16:28 +00:00
james 705cbe249a docs: record the deferred promotion-window invariant and test-isolation rules
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.
2026-09-18 13:11:37 +00:00
james c93bff28b0 chore(openspec): archive add-flash-sales
Sync the flash-sales capability plus the order, frontend-mall, and
frontend-shop-admin requirements into the main specs, and write the capability
Purpose the archive leaves as a placeholder.

Restate the flash-price and coupon-exclusion scenarios in the group-buying
order delta, since its MODIFIED checkout block must carry the scenarios the main
order spec now has.
2026-09-18 12:53:26 +00:00
james b15a246c3f chore(seed): seed an active flash sale
Add one active session for the demo shop with a discounted activity item on one
of its SKUs, idempotent by English label, so the live mall has a discoverable
flash sale to check out.
2026-09-18 12:53:26 +00:00
james 5c30760351 fix(api): keep the flash-sale discovery test isolation-safe
The inactive-window test asserted that no active session exists at all, which
the shared test database breaks once another test has created one. Assert that
this session is absent from discovery instead.
2026-09-18 12:53:26 +00:00
james 26773f7890 feat(shop-admin): manage flash-sale sessions and activity items
Add a flash-sale page where a shop user lists, creates, edits, and deletes its
own sessions and their SKU activity items, setting the fixed sale price,
currency, reserved stock, and per-customer limit. Expose it as a flash-sale
navigation entry beside the existing shop operations.
2026-09-18 12:53:26 +00:00
james 2125ae437f feat(mall): live flash-sale page and activity price snapshots
The flash-sale page loads active sessions and their items through the shared
client with live sale price, remaining activity stock, sell-through, and a
countdown, and adds the SKU to the existing cart so checkout resolves the
authoritative price. Payment and order detail tag and render the snapshotted
activity unit price.

The flash-sale fixtures leave the page; the fixed-data adapter still serves the
domain as the rollback path.
2026-09-18 12:53:26 +00:00
james 393e7f7209 feat(api): flash sales resolved server-side at checkout
Shop-owned timed sessions carry SKU activity items with their own reserved
inventory and per-customer limit. Checkout resolves eligibility on the server,
locks candidate items by primary key after the SKU locks, and splits a cart line
into an activity-priced item plus a standard-priced remainder, so every unit
price snapshot is honest and a customer cannot exceed the limit.

Reserved activity stock and SKU stock decrement together under guards, and a
pending-payment cancellation restores both plus any redeemed coupon. Coupons are
rejected on a shop order that applied activity pricing, and a SKU cannot join
two overlapping enabled sessions.

The overlap check against group buying is present but dormant: that capability
lands later, so the check activates only once its table exists.

Surfaces (shop-admin, mall) and seeding follow.
2026-09-18 12:47:46 +00:00
james 82c1e8e4bb chore(openspec): archive add-points-mall
Sync the points-mall capability plus the frontend-mall and frontend-admin
requirements into the main specs, and write the capability Purpose the archive
leaves as a placeholder.
2026-09-18 12:43:18 +00:00
james f70d1e506a chore(seed): seed deterministic points products
Add five published points products, idempotent by English name, so the live
mall has a redeemable catalog. Spendable demo points are credited by the API
through the account ledger rather than here.
2026-09-18 12:43:00 +00:00
james 68317c2040 feat(admin): manage points products and redemptions
Platform operators get a points catalog screen (create, edit, publish and
unpublish products with localized names, points price, stock, and ordering) and
a redemption screen that lists orders and moves pending ones to fulfilled or
cancelled. Both are reachable from the console navigation.
2026-09-18 12:43:00 +00:00
james 059572f6f3 feat(mall): live points mall with address-aware redemption
The points page loads the published catalog and the customer's live points
through the shared client, redeems with a chosen saved address, refreshes the
balance, and lists the customer's redemption history with its fulfilment state.
The INTEGRAL_PRODUCTS and USER_STATS imports leave the page; the fixed-data
adapter still serves the points domain as the rollback path.
2026-09-18 12:43:00 +00:00
james 9c2b705880 feat(api): points mall with atomic redemption and admin fulfillment
Platform-owned points products and a redemption order lifecycle kept separate
from cash orders. Redeeming locks the published product, reserves stock, creates
the order, debits points through the archived customer-accounts ledger with the
order as reference, and snapshots the line in one transaction; a failure leaves
no order, no stock change, and no ledger entry. Fulfilment moves only from
pending_fulfillment, and customers see only their own redemptions.

Demo seeding credits the demo customer through the same guarded credit path
with reason seed, once, so no balance is ever written absolutely.

Surfaces (admin console, mall points page) and product seeding follow.
2026-09-18 12:24:46 +00:00
james cc07ac3230 chore(openspec): archive add-shop-coupons
Sync the shop-coupons capability plus the order, frontend-mall, and
frontend-shop-admin requirements into the main specs, and write the capability
Purpose the archive leaves as a placeholder.

Renaming the coupon scenario in the flash-sales and group-buying order deltas
to the exact name now in the main order spec keeps those changes archivable,
since a MODIFIED requirement replaces the whole block.
2026-09-18 12:13:49 +00:00
james c7fad42105 feat(shop-admin): manage shop coupon templates
Add a coupons page where a shop user lists, creates, edits, enables/disables,
and deletes only its own templates, with localized titles, minor-unit discount
and threshold, currency, claim stock, and an active window. Expose it as a
coupons navigation entry beside the existing shop operations.
2026-09-18 12:13:05 +00:00
james 0eb94f2ba1 feat(mall): claim and redeem shop coupons from the storefront
The product page loads a shop's claimable coupons through the shared client and
claims them in place; the buyer coupon list reads owned snapshots with their
claimed/redeemed/expired state. Checkout offers each shop order at most one
owned coupon and submits only the choice; the pay page and order detail render
the server-persisted discount and reduced total, never a client figure.

Coupon fixtures leave the pages; the fixed-data adapter still serves the coupon
domain as the rollback path. The demo seed creates two deterministic templates
and claims them for the demo customer.
2026-09-18 12:12:51 +00:00
james 23955434c6 feat(api): shop coupons with per-shop checkout redemption
Templates belong to a shop; claiming copies their terms into a customer-owned
snapshot so a later edit or disable cannot rewrite a held coupon. Claim stock
is taken with a guarded decrement after locking the template, and a unique
(user, template) index makes a duplicate claim a 409. Deleting a template
leaves claimed snapshots standing via ON DELETE SET NULL.

Checkout accepts at most one owned coupon per generated shop order, locks the
selected coupons by primary key after the SKU locks, and resolves eligibility
and the discount server-side (ownership, shop, status, window, converted
threshold). The realized discount and coupon id land on the order, and a
pending-payment cancellation restores the coupon in the same transaction as
stock.

The shared contract gains the coupon types, claim/list/manage methods, and the
checkout coupon map; the fixed-data adapter implements the same surface.

Surfaces (shop-admin management, mall coupon pages, checkout selection) and
seeding still follow in tasks 3.1-4.2.
2026-09-18 12:03:00 +00:00
james 39d0158f29 chore(openspec): archive add-customer-accounts
Sync the customer-accounts capability and the frontend-mall stats requirement
into the main specs, and write the capability Purpose the archive leaves as a
placeholder so strict validation stays green.
2026-09-18 11:55:38 +00:00
james 7a2745fb16 feat(api): add customer accounts with live summary and append-only ledger
One balance row per (user, kind, currency): available and frozen carry the
platform base currency, points carries none. Monetary and points rows use
separate partial unique indexes because a plain UNIQUE lets NULL repeat.

Balance changes go through transactional primitives that debits guard with a
conditional update, credits add atomically, and freeze/release move both sides
in one transaction after locking rows by primary key. Every change appends an
immutable entry holding its resulting balance. Registration and a migration
backfill create the zero rows; GET /api/me/stats is the only public surface and
no endpoint mutates a balance.

The mall buyer center and points page drop the USER_STATS fixture for the
shared contract; the fixture stays exported so the fixed-data adapter can still
serve the account domain as a rollback path.

Implements openspec change add-customer-accounts.
2026-09-18 11:54:53 +00:00
james bcd97ab48f docs(openspec): fix group-buying archive scenario and address-book purpose
Rename the group-buying "Order lifecycle" cancel scenario to the exact name
the current order spec uses; a MODIFIED requirement replaces the whole block,
so the old name made archive refuse to drop the existing scenario.

Replace the placeholder Purpose in the archived address-book spec, which the
wave-7 archive left as TBD and strict validation rejected.

openspec validate --all --strict is green again.
2026-09-18 11:54:44 +00:00
Chengdong ZhangandCursor 835afbe5e0 fix(api): decrement SKU stock only when remaining quantity is sufficient
Prevent oversell by conditioning stock updates and locking SKUs in primary-key order, and record the same concurrent-counter rule in the API spec and agent guide.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 17:41:06 +08:00
Chengdong ZhangandCursor 53548196f8 docs(openspec): plan shop coupons, accounts, points, flash sales, and group buying
Capture exclusive promotion composition, empty-group cancel, and mall surface scope so the five marketing changes can be implemented without stacking or lifecycle ambiguity.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 17:32:08 +08:00
Chengdong Zhang e10cae5789 docs: API architecture ADRs, tech spec, and marketing capability tracker
Persist the modular-monolith decision (ADR 0001 handler/service/repo,
ADR 0002 keep REST) with the companion tech spec and the api-architecture
OpenSpec capability. Replace the finished mock-migration tracker with
docs/TBD-marketing.md listing the backend-less marketing domains still on
fixtures (coupons, favorites, account stats, seckill, collective, integral,
reviews). README and AGENTS.md point at the new docs.
2026-09-18 16:00:31 +08:00
Chengdong Zhang c51e96ae41 feat(mall): address book wave 7 frontend, contract, and archive
Live addresses domain for the mall: shared contract (AddressBookEntry +
five client methods), mock adapter state v3, live domain pick, addresses
page CRUD, checkout saved-address picker with manual fallback, demo seed,
and the archived change plus address-book capability spec.
2026-09-18 16:00:05 +08:00
Chengdong ZhangandCursor 5e866d08f4 refactor(api): split Axum handlers into handler/service/repo modules
Keep the REST contract; move domain logic out of route files so checkout, fulfillment, and identity can be reused across customer, shop, and admin surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 14:59:36 +08:00