docs(openspec): propose 8 tigshop migration changes (P0-P7) + migration plan

This commit is contained in:
Chengdong Zhang
2026-09-23 13:30:36 +08:00
parent 0d0e10b97b
commit 9a749e2551
47 changed files with 1607 additions and 0 deletions
@@ -0,0 +1,85 @@
## ADDED Requirements
### Requirement: Mobile page inventory and navigation structure
The `apps/mobile` H5 app SHALL provide the migrated tigshop information architecture as Nuxt routes: home `/`, category browse `/category`, search `/search` with results `/search/result`, product detail `/product/:id`, cart `/cart`, checkout `/checkout`, order list `/orders`, order detail `/orders/:id`, coupon center `/coupon`, wallet `/wallet`, "My" center `/user`, and sign-in `/login` with register `/register`. Every listed route SHALL be reachable through navigation from the tab bar, the "My" center, or in-flow links, and no route SHALL depend on mini-program-only capabilities.
#### Scenario: walk the browse-to-buy path
- **WHEN** a mobile shopper opens `/`, drills into `/category`, opens `/search/result`, selects a product, adds it to the cart, checks out, and then opens `/orders/:id`
- **THEN** each route renders its documented surface from the shared API client and the created order is visible in the order list and detail
#### Scenario: user-center entries resolve
- **WHEN** a shopper opens `/user` and taps the coupon, wallet, order, or after-sale entry
- **THEN** each entry navigates to its documented route and no entry points at an unbuilt placeholder route
### Requirement: Custom tab bar and safe-area shell
The app SHALL render a custom fixed bottom tab bar with exactly four destinations — Home, Category, Cart, My (首页/分类/购物车/我的) mapped to `/`, `/category`, `/cart`, `/user` — with the active tab indicated by the shared brand token. Tab bar and page chrome SHALL pad with `env(safe-area-inset-bottom)` and `env(safe-area-inset-top)` so content is never hidden behind the home indicator or status bar. Routes outside the four tab routes SHALL replace the tab bar with a top back-bar instead.
#### Scenario: tab bar switches root sections
- **WHEN** a shopper taps a tab bar item
- **THEN** the matching root route renders with that tab shown active and the tab bar stays fixed above the safe area
#### Scenario: content clears the home indicator
- **WHEN** a shopper opens the cart on a device with a non-zero bottom safe-area inset
- **THEN** the last cart line and the tab bar remain fully visible above the inset
### Requirement: Shared-contract-only data access
The app SHALL obtain every piece of backend data through the `@vmall/shared` API client and its existing adapter selection (live backend or fixed-data fallback) and SHALL NOT define its own fetch wrapper, endpoint paths, or response types. Styling SHALL consume `@vmall/shared/theme.css` tokens rather than a private global stylesheet. Money SHALL be handled as integer minor units with shared formatting helpers and bilingual content SHALL render the active locale from `{en, zh}` localized text.
#### Scenario: no private API layer
- **WHEN** a developer inspects `apps/mobile`
- **THEN** all backend calls go through `@vmall/shared` client methods and no page defines its own HTTP request to an API path
#### Scenario: prices render from minor units
- **WHEN** any surface renders a price
- **THEN** the amount derives from integer minor units through shared formatting with no floating-point money arithmetic
### Requirement: Mobile interaction conventions
Home, search results, and order list SHALL support pull-to-refresh reloading through the shared client. Actions requiring authentication SHALL redirect a signed-out shopper to `/login` and return them to the interrupted route after sign-in. In-flight mutations (add to cart, place order, claim coupon) SHALL be guarded against duplicate submission.
#### Scenario: pull to refresh home
- **WHEN** a shopper pulls down on the home page
- **THEN** home content floors reload from the shared client and the refreshed state renders
#### Scenario: anonymous add returns to the product
- **WHEN** a signed-out shopper adds a product to the cart from `/product/:id` and then signs in
- **THEN** they land back on that product route
### Requirement: Mall-identical session and token contract
The app SHALL persist the session exactly as the mall does — `AuthTokens.token` in `localStorage["vmall.token"]` and the user in `localStorage["vmall.user"]` — and SHALL send it as a Bearer header via the shared client's `getToken`. A restored token SHALL be validated against the auth API on load and a rejected token SHALL clear the stored session and route the shopper to `/login`, matching mall semantics so one sign-in serves both apps on the same origin storage.
#### Scenario: token validated on load
- **WHEN** a shopper with a stored token reopens the app
- **THEN** the session is validated through the shared auth API and the "My" center renders the authenticated user
#### Scenario: rejected token clears storage
- **WHEN** a restored token is rejected by the auth API
- **THEN** `vmall.token` and `vmall.user` are cleared and the shopper is routed to `/login`
### Requirement: Transaction and account surfaces on existing APIs
Cart, checkout, order list and order detail SHALL read and write backend state exclusively through shared client cart, checkout, order and address methods, so carts and orders survive reloads and match the mall's data. Checkout SHALL offer the customer's saved addresses (defaulting to the default address) with an inline manual address form when none exist, and SHALL present eligible owned coupons per shop order. Order detail SHALL expose the appropriate status actions (pay, cancel, confirm delivery) through the shared client's guarded status transitions. Coupon center SHALL list claimable shop coupons, claim them, and show the customer's owned coupons through the shared client.
#### Scenario: cart survives reload
- **WHEN** a signed-in shopper adds an item and reloads `/cart`
- **THEN** the line is still present from backend state
#### Scenario: claim from the coupon center
- **WHEN** a signed-in shopper claims a coupon with remaining stock from `/coupon`
- **THEN** it appears in the owned-coupon list without any fixture import
### Requirement: Dependency-gated after-sale and wallet surfaces
The after-sale entry SHALL expose the customer's after-sale requests through the `aftersale` shared-client API from `add-aftersale-refunds`, and the wallet page SHALL render the customer's balances and ledger entries through the `wallet` shared-client API from `add-wallet-settlement`. While either change is not yet archived, the corresponding surface SHALL render a localized "Coming soon" placeholder instead of broken or fixture-backed behavior, and SHALL be enabled without further design change once its dependency lands.
#### Scenario: wallet before its dependency lands
- **WHEN** `add-wallet-settlement` is not yet archived and a shopper opens `/wallet`
- **THEN** a localized "Coming soon" placeholder renders instead of an error
#### Scenario: after-sale after its dependency lands
- **WHEN** `add-aftersale-refunds` is archived and a shopper opens the after-sale entry from `/orders/:id`
- **THEN** after-sale data renders from the `aftersale` shared-client API
### Requirement: Reachable mobile entry from the mall shell
The mall top bar's "Mobile" (手机端) entry SHALL resolve to the H5 app's home page instead of the current dead `/` link, so the desktop storefront is the discoverable entry point for the mobile client.
#### Scenario: top-bar mobile link opens the H5 app
- **WHEN** a desktop shopper clicks the mall top bar's Mobile entry
- **THEN** the `apps/mobile` home page opens instead of reloading the mall home