Implements, verifies, and archives the three remaining Wave 2 changes from openspec/MIGRATION-PLAN.md. - add-wallet-settlement (P3): demo recharge, guarded withdrawal freeze and one-time admin review, paginated own fund entries, idempotent per-shop weekly/monthly settlement statements with commission rate and one-time payout confirmation. - add-merchant-onboarding (P5): personal/enterprise applications with one live application per user, guarded review with mandatory rejection reason, and transactional shop + owner provisioning returning one-time credentials; mall onboarding/status pages and an admin review console. - add-membership-messaging (P7): platform member levels, append-only growth accrual on order completion with guarded one-way leveling, order/shipment/ refund system messages with unread/read state and soft deletion, plus the mall header unread badge. Backend: migrations 0019-0023, new wallet, settlement, merchant_onboarding, membership and messaging modules, event hooks in order/fulfillment/aftersale, and integration suites for each. Shared contract extended and all three frontends updated; code indexes, domain docs, backend guidelines and the migration tracker synced. Verification: cargo test -p vmall-api green twice consecutively; mall, admin and shop-admin builds pass; browser smoke on every new surface; openspec validate --all --strict green (33 passed). The three changes share the @vmall/shared contract, the mall mock adapter and per-app locale/nav files, so they are committed together to keep every commit buildable.
31 KiB
frontend-mall Specification
Purpose
The buyer-facing storefront: shell, home page, discovery, shopping and transaction flows, and the buyer center.
Requirements
Requirement: Localized storefront
The mall SHALL render every UI string and all catalog/store/marketing mock content in en or zh from one switcher, defaulting to en. Switching locale SHALL update the desktop shell and current page without a full reload.
Scenario: switch to Chinese
- WHEN a shopper switches locale to zh
- THEN navigation, buttons, product/store names and mock marketing content render in Chinese without reload errors
Requirement: Multi-currency display
The mall SHALL offer a currency switcher sourced from the selected API adapter and SHALL convert product, cart and order prices through integer minor units and each currency's exponent. Mock mode SHALL use deterministic fixed conversion rates; live mode SHALL use the API.
Scenario: switch currency
- WHEN a shopper switches from USD to JPY on a product priced $10.00
- THEN the displayed price reflects the selected adapter's conversion rate with JPY exponent 0 and no floating-point money arithmetic
Requirement: Shopping flow
A shopper SHALL be able to browse, view detail, add to cart, checkout with a shipping address, pay, track orders/shipments, confirm delivery, and request an invoice against the live API. Cart, order, shipment and invoice state SHALL be the backend's rather than the browser's, and the fixed-data adapter SHALL remain available as a configured fallback rather than the default. Adding to the cart SHALL require an authenticated shopper: an anonymous add SHALL send the shopper to sign in and return them to where they left off.
Scenario: end-to-end purchase
- WHEN a shopper completes checkout on a non-empty cart
- THEN the resulting order appears in the buyer center and the cart is empty
Scenario: anonymous add prompts sign-in
- WHEN a signed-out shopper adds an in-stock SKU from a product page
- THEN they are sent to sign in and, once signed in, returned to that product page
Requirement: B2B2C mall-style PC storefront shell
The mall SHALL render a buyer-facing desktop shell modeled on a classic B2B2C PC mall: a 30px utility bar, logo/search/cart header, dark primary navigation with a hover category mega-menu, a 1200px content grid, and a value-proposition footer. The header SHALL remain fully visible while scrolling; no part of the shell SHALL auto-hide based on scroll position. The category mega-menu SHALL appear as a hover dropdown under the navigation "All Categories" entry on every page except the home page, where it is instead pinned in the hero row. The visual language SHALL use #ca151e for brand/price/active states, #f5f5f5 section backgrounds, gray hairline borders, compact controls, and product-card hover lift/shadow. The implementation SHALL use the shared Tailwind theme and @vmall/ui primitives plus Nuxt-native semantic components, SHALL NOT depend on Element Plus, and SHALL NOT expose a theme-accent switcher.
Scenario: shopper opens any mall page
- WHEN a shopper navigates to a buyer-facing route
- THEN the shared desktop shell wraps the route content and its nav/search/cart controls are usable
Scenario: header persists while scrolling
- WHEN a shopper scrolls any mall page
- THEN the logo/search/cart header bar remains visible and is never collapsed or hidden by scroll position
Scenario: hover categories on a non-home page
- WHEN a shopper hovers "All Categories" in the navigation on a page other than
/ - THEN the category mega-menu dropdown appears below the navigation and hides again on mouse leave
Scenario: mall has no accent switcher
- WHEN a shopper inspects the mall shell
- THEN there is no control to change brand accent and prices/active states remain
#ca151e
Requirement: Mock API adapter
The mall SHALL select its API adapter per domain, so one domain can be served by the live backend while the others stay on fixed data. The mall SHALL still ship a fixed-data adapter implementing the whole @vmall/shared API client surface, and the live/fixed choice SHALL be configurable per domain without changing page call sites. The fixed-data adapter SHALL remain able to serve every domain when the live backend is unavailable.
Scenario: mall runs without backend
- WHEN the mall starts with the API service unavailable and every domain configured to fixed data
- THEN browsing, cart, checkout, payment, orders and invoice pages return deterministic fixed data and remain functional
Scenario: domains migrate independently
- WHEN the live backend serves the catalog and currency domains while auth, cart, orders, shipments and invoices remain on fixed data
- THEN browsing and prices come from the backend while those other flows keep working against fixed data
Requirement: Mock PC home page
The mall home page SHALL render a hero row composed of a pinned 240px category sidebar on the left and a hero carousel filling the remainder of the 1200px grid, both 450px tall and occupying layout space (not overlaid). The sidebar SHALL list the catalog's top-level categories with up to three child links each; hovering a top-level category SHALL expand the mega-menu panel to the right over the carousel. Banner images SHALL render at fixed 450px height, center-cropped horizontally to the narrower carousel width. Below the hero, the page SHALL render a six-item quick-link strip with promotion tiles and bilingual product floors, where each floor's products come from the catalog API and the banners, promotions, quick links and floor advert art come from the content API.
Scenario: shopper lands on home
- WHEN
/loads - THEN the category sidebar is visible to the left of the carousel without any hover or click, the carousel renders center-cropped banners at 450px height, and the quick links, promotions and every non-empty product floor render, with floor products from the catalog API and the marketing content from the content API
Scenario: sidebar stays while scrolling
- WHEN a shopper scrolls the home page beyond 200px
- THEN the category sidebar remains rendered in the hero row and does not auto-hide
Scenario: expand a category
- WHEN a shopper hovers a top-level category in the pinned sidebar
- THEN the mega-menu panel expands to the right, overlaying the carousel with that category's child and grandchild links from the catalog API
Scenario: home renders with no content published
- WHEN the content API returns empty lists
- THEN the page still renders its category sidebar and product floors instead of failing
Requirement: Product discovery pages
The mall SHALL provide /search with breadcrumb, category, brand and sort controls, a five-column desktop product grid, pagination and an empty state, listing products from the catalog API filtered by the selected category's subtree and brand. The sort control SHALL offer newest-first, price and sales. It SHALL provide /goods/[id] rendering product and SKU data from the catalog API with image gallery/zoom, bilingual name/subtitle, integer-minor-unit prices, attribute and SKU selection, stock-aware quantity, store card, and detail/after-sale tabs. Product cards and the product detail page SHALL show the product's real sold count, and the mall SHALL NOT present reviews, ratings or reviewer comments while no reviews capability exists.
Scenario: filter and inspect a product
- WHEN a shopper filters the search page by a parent category and a brand, then opens a product
- THEN products from that category's subtree matching the brand are listed, and selecting an in-stock SKU updates the displayed price, stock and cart target from the catalog API
Scenario: sort by sales
- WHEN a shopper sorts the search results by sales
- THEN the order follows the products' reported sold counts
Scenario: no invented reviews
- WHEN a shopper opens a product
- THEN the page shows the shop's own after-sale copy and no rating, review count or reviewer comment
Requirement: Mock transaction flow
The mall SHALL provide a store-grouped cart, address-selecting checkout preview, payment selection and payment-success result, all reading and writing the live cart and order APIs. Quantity changes, removals, selection totals, checkout and payment SHALL be persisted by the backend for the signed-in shopper, so they survive a page reload.
Scenario: complete mock purchase
- WHEN a shopper adds an in-stock SKU, checks out with a mock address and confirms a payment
- THEN the cart is cleared, the success page is shown and the new order appears in the user order list
Scenario: cart survives a reload
- WHEN a signed-in shopper adds an item and then reloads the page
- THEN the cart still holds that item, priced and stocked from the catalog
Requirement: Auth and buyer center
The mall SHALL provide B2B2C mall-style login, register and forgot-password panels backed by the live auth API, so credentials, roles and tokens belong to the real user rather than a fixed demo account. Registration SHALL require a password of at least 8 characters, matching the API's rule, and the register panel SHALL NOT ask for a verification code because no endpoint issues one. Failures SHALL be reported distinctly: invalid credentials on sign-in, and an already-registered email on registration. A token restored from storage SHALL be validated against the auth API on load, and a rejected token SHALL clear the session and return the shopper to sign-in. /user SHALL render a two-column buyer center with dashboard, order list/detail, addresses, favorites, coupons and invoices.
Scenario: sign in and inspect buyer data
- WHEN a shopper signs in with valid credentials and opens
/user - THEN the session carries the authenticated user, and the buyer-center shell and its fixed account/order/address/favorite/coupon/invoice data render
Scenario: wrong password rejected
- WHEN a shopper submits a password that does not match the account
- THEN sign-in fails with an invalid-credentials message and no session is established
Scenario: short password refused before the API call
- WHEN a shopper submits a registration password shorter than 8 characters
- THEN the panel asks for at least 8 characters without calling the API
Scenario: duplicate email reported
- WHEN a shopper registers an email that already has an account
- THEN the panel reports that the email is already registered rather than a generic failure
Scenario: rejected token clears the session
- WHEN a token restored from storage is rejected by the auth API
- THEN the stored session is cleared and the shopper is returned to sign-in
Requirement: Store and marketing pages
The mall SHALL provide a store directory and store home reading live shops from the shop API, with each store's products coming from the catalog API, and SHALL keep the timed seckill page, collective-buy list and points-mall home on fixed bilingual content for now. The cart, payment and order surfaces SHALL show each order's shop by name from the shop API rather than a generic label. Marketing pages MAY be display-only except navigation to product detail.
Scenario: navigate storefront discovery channels
- WHEN a shopper opens stores, seckill, collective or integral routes
- THEN each page renders the appropriate B2B2C mall-style banner/filter/session/card layout, the store directory and store home list real shops, and product links resolve to product detail
Scenario: order surfaces name the shop
- WHEN a shopper views a cart line, a payment group or an order card
- THEN the shop is shown by its name, not a placeholder
Scenario: a shop without a profile still renders
- WHEN a shop has no profile row
- THEN the directory and store home render it without inventing logo, scores or copy
Requirement: Address book management
The mall buyer center SHALL list, add, edit, delete and set-default the signed-in customer's saved addresses through the selected API adapter, replacing the previous fixed mock list.
Scenario: manage addresses in the buyer center
- WHEN a signed-in shopper opens
/user/addressesand adds or edits an address - THEN the change persists through the adapter and survives a full page reload
Requirement: Checkout address selection
Checkout SHALL offer the customer's saved addresses for selection, defaulting to their default address, and SHALL submit the selected address with the order. When the customer has no saved address, checkout SHALL provide an inline manual address form instead.
Scenario: checkout with a saved address
- WHEN a signed-in shopper with saved addresses checks out
- THEN the default address is preselected and the created order carries the chosen address
Requirement: Live customer account statistics
The buyer-center dashboard and points surfaces SHALL load the signed-in customer's balance, frozen balance, currency, and points from the shared account API rather than USER_STATS.
Scenario: account summary reload
- WHEN a signed-in shopper reloads the buyer center
- THEN displayed account statistics come from the selected API adapter and survive browser state loss
Requirement: Live shop coupon surfaces
The mall SHALL load a product's currently claimable shop coupons and the signed-in customer's coupon list through the shared API client, replacing direct coupon fixtures. Checkout SHALL present eligible owned coupons separately for each shop order and show the server-returned discount after submission. Payment and order-detail views SHALL show the persisted discount_minor and post-discount total from the order payload.
Scenario: claim from a product page
- WHEN a signed-in shopper claims an active coupon with remaining stock from a product page
- THEN it appears in the shopper's coupon list without a fixture import
Scenario: select coupon by shop
- WHEN a cart spans two shops and the shopper selects a coupon for one shop
- THEN checkout submits the choice only for that shop and the other shop remains undiscounted
Scenario: order shows realized discount
- WHEN checkout succeeds with a coupon on one shop order
- THEN that order's payment and detail views show the server discount and reduced total without a client-calculated amount
Requirement: Live points mall
The mall SHALL display live published points products and the signed-in customer's live points balance, allow redemption with a shipping address, and show the customer's redemption history on the points route. It SHALL replace INTEGRAL_PRODUCTS and USER_STATS imports on the points route.
Scenario: redeem available product
- WHEN a signed-in shopper with enough points redeems a published in-stock product
- THEN the mall shows the created redemption order and refreshed points balance
Requirement: Live flash-sale page
The mall SHALL render active flash-sale sessions and activity products from the shared API client, including authoritative activity price, remaining stock, and sell-through. It SHALL replace SECKILL_SESSIONS and derived fixed product data. Shoppers SHALL purchase through the existing cart and checkout; payment and order-detail views SHALL show the snapshotted activity unit price from the order payload. This change SHALL NOT add flash-sale prices, badges, or claim CTAs to the catalog product-detail page (/goods/[id]); that page keeps catalog SKU pricing.
Scenario: choose live session
- WHEN a shopper selects an active flash-sale session
- THEN its live eligible products and countdown render without direct fixture imports
Scenario: order shows flash snapshot
- WHEN checkout applies a flash-sale price
- THEN payment and order-detail views show that unit price from the order payload
Scenario: product detail stays on catalog price
- WHEN a shopper opens a catalog product that is also in an active flash-sale session
- THEN the product-detail page still shows catalog SKU prices and does not require a flash-sale overlay in this change
Requirement: Live group-buying page
The mall SHALL render active group-buying activities and open groups from the shared API client, including group price, required member count, current paid-member count, and expiry. It SHALL let an authenticated shopper choose to open or join a group and replace derived fixture counts. Checkout SHALL submit the open-or-join intent for a single activity SKU at quantity 1. Payment and order-detail views SHALL show the snapshotted group price. This change SHALL NOT add group price or join CTAs to the catalog product-detail page (/goods/[id]); opening and joining happen from the group-buying page and checkout intent.
Scenario: open a group
- WHEN a shopper selects an active group-buying product and chooses to open a group
- THEN the checkout intent identifies the activity and requests a new group
Scenario: order shows group snapshot
- WHEN checkout succeeds with group-buy intent
- THEN payment and order-detail views show the server group unit price from the order payload
Scenario: product detail stays on catalog price
- WHEN a shopper opens a catalog product that also has an active group-buying activity
- THEN the product-detail page still shows catalog SKU prices and does not require a group-buy overlay in this change
Requirement: Live customer favorites
The mall SHALL use the shared selected API adapter for product and shop favorite state. Product and store detail controls SHALL load persisted state, require customer authentication, prevent duplicate in-flight mutations, and survive reloads. The buyer-center favorites page and dashboard preview/counts SHALL render paginated live favorite results and remove targets through the API instead of reading MOCK_FAVORITES or mutating local-only state.
Scenario: product favorite survives reload
- WHEN a signed-in shopper favorites a product and reloads its detail page
- THEN the favorite control remains selected from backend state
Scenario: anonymous favorite requires sign-in
- WHEN a signed-out shopper uses a product or store favorite control
- THEN the mall sends the shopper to sign in with the current detail URL as the return destination
Scenario: remove from buyer center
- WHEN a shopper removes a product or shop from the favorites page
- THEN the API state, visible list, dashboard preview, and visible count reflect the removal without fixture mutation
Scenario: fixed adapter remains functional
- WHEN the favorites domain is configured to fixed data
- THEN detail controls and buyer-center favorite flows behave deterministically through the same shared client methods
Requirement: Shared design system on the storefront
Mall pages SHALL style layout with Tailwind utilities and shared primitives. Mall-only chrome (mega-menu, carousel crop, 1200px shell, product-card hover lift) MAY live in apps/mall components but MUST consume shared tokens, not a private global stylesheet.
Scenario: storefront uses shared tokens
- WHEN a shopper views the home page
- THEN brand red, the 1200px grid, and the 450px hero height come from the shared theme keys rather than
mall.css
Requirement: Mall aftersale customer flow
The Mall SHALL use the shared selected API adapter to expose an after-sale action for eligible order items, an aftersale list and detail view, chronological buyer/merchant messages, cancellation and one-time rejected appeal, and return-refund shipping tracking. The form SHALL show the localized reason/type, integer minor-unit amount and remaining limit, evidence URL inputs, current status, and authoritative refund result. Anonymous actions SHALL redirect to sign-in with the current route as return destination.
Scenario: apply from an eligible order line
- WHEN a signed-in shopper opens a paid or shipped order inside its after-sale window and submits a valid item application
- THEN the Mall creates the aftersale through the shared client and shows its pending status without fixture-only state
Scenario: return shipping is recorded
- WHEN an approved return-refund shopper submits carrier and tracking data
- THEN the detail view shows the buyer-shipping status and the persisted tracking information after reload
Scenario: shopper cancels an application
- WHEN a shopper cancels a non-terminal pending aftersale
- THEN the API state and list/detail views show cancelled and merchant actions are no longer offered
Scenario: shopper messages the merchant
- WHEN a shopper sends a localized message with optional evidence URLs
- THEN it appears in chronological detail history and remains after a reload
Scenario: fixed adapter remains usable
- WHEN the aftersale domain is configured for fixed data
- THEN Mall list, detail, messages, cancellation, tracking, and refund-history flows behave deterministically through the same shared methods
Requirement: Checkout shipping fee display
Checkout SHALL show each shop group's delivery fee and the combined shipping total computed by the server, alongside the merchandise and order totals in the selected currency. Changing the selected shipping address SHALL refresh the fee breakdown before submission, and payment and order detail SHALL show the persisted shipping_fee from the order payload. Fees SHALL come only from the shared @vmall/shared contract; the fixed-data adapter SHALL implement the same quote surface as a fallback so checkout works without the backend.
Scenario: per-shop fees at checkout
- WHEN a shopper's checkout spans two shops with different templates
- THEN each shop group shows its own delivery fee and the summary shows their sum
Scenario: address change updates fees
- WHEN the shopper selects an address whose region rule raises shipping
- THEN the displayed per-shop fees and total update to the server's new calculation
Scenario: fixed adapter quotes fees
- WHEN the shipping surface is configured to fixed data
- THEN checkout renders deterministic per-shop delivery fees through the same shared client methods
Requirement: Live product review area
The mall SHALL render product reviews from the shared selected API adapter instead of display-only comment fixtures. Product detail SHALL show the rating summary (average, count, star distribution) and a paginated list of visible reviews with images and merchant replies, with totals from the API and no invented reviewers or ratings.
Scenario: detail page shows real reviews
- WHEN a shopper opens a product whose reviews were posted through completed orders
- THEN the review area shows the aggregated summary and those reviews with their merchant replies
Scenario: detail page without reviews
- WHEN a shopper opens a product with no visible reviews
- THEN the review area shows an empty state and a zeroed summary instead of fixture comments
Requirement: Buyer-center pending review and submission
The buyer center SHALL expose a "pending review" entry counting completed order lines awaiting review and a submission form posting rating, text, and optional image URLs through the shared API contract. Submission SHALL require the customer's own unreviewed completed order line, and the pending list SHALL refresh after a successful submission.
Scenario: submit a review from the buyer center
- WHEN a shopper submits a review for a pending order line and it succeeds
- THEN the pending list and count drop that line and the review appears on the product detail page
Scenario: anonymous submission requires sign-in
- WHEN a signed-out shopper opens the pending review entry or submission form
- THEN the mall sends the shopper to sign in with the current URL as the return destination
Scenario: fixed adapter remains functional
- WHEN the reviews domain is configured to fixed data
- THEN the review area, pending-review entry, and submission flows behave deterministically through the same shared client methods
Requirement: Buyer wallet surface
The mall SHALL render a buyer-center wallet page driven by the selected API adapter through @vmall/shared: available and frozen balance with currency, paginated fund entries, a clearly labeled demo recharge form, and a withdrawal request form. Balances and entries SHALL reflect backend state after each action rather than local-only state. The wallet SHALL be a mall API domain with fixed-adapter fallback methods and LIVE_PICKS wiring following the established per-domain adapter pattern.
Scenario: wallet page loads live state
- WHEN a signed-in buyer opens the wallet page
- THEN balances and the first page of fund entries render from the shared wallet contract
Scenario: demo recharge updates balance
- WHEN the buyer submits a demo recharge
- THEN the visible available balance reflects the credit without a reload and the form is visibly marked simulated
Scenario: withdrawal freezes visibly
- WHEN the buyer submits a withdrawal request
- THEN the visible summary shows available decreased and frozen increased by the requested amount, and the request appears in the withdrawal list as pending
Scenario: fixed adapter remains functional
- WHEN the wallet domain is configured to fixed data
- THEN the wallet page behaves deterministically through the same shared client methods
Requirement: Merchant onboarding multi-step form
The mall SHALL expose a merchant onboarding page ("商家入驻") reachable from the top-bar entry, with a multi-step form covering entity kind (personal 个人 / enterprise 企业), kind-specific entity information, operating categories from the published category tree, contact details, and qualification materials as URL input fields with no file-upload controls. The form SHALL be fillable while signed out, but submission SHALL require registration or sign-in and return the applicant to the completed form to submit through the shared selected API adapter. A duplicate-application conflict SHALL be surfaced inline. All copy SHALL come from the mall locale source in en and zh.
Scenario: anonymous fill then sign-in
- WHEN a signed-out visitor completes the form and submits
- THEN they are sent to register or sign in and, once signed in, returned to the completed form to submit
Scenario: enterprise kind shows company fields
- WHEN the applicant selects the enterprise entity kind
- THEN the company-specific entity and qualification fields replace the personal ones
Scenario: duplicate application surfaced
- WHEN a signed-in user holding a pending or approved application submits the form
- THEN the mall shows the conflict instead of silently creating a second application
Scenario: fixed adapter remains functional
- WHEN the merchant-onboarding domain is configured to fixed data
- THEN the form and status flows behave deterministically through the same shared client methods
Requirement: Application status page
The mall SHALL show a signed-in applicant their latest merchant application state — status, submitted entity kind, timestamps, and the rejection reason when rejected — linked from the onboarding page, with a re-apply action after rejection. Anonymous visitors SHALL be sent to sign in first.
Scenario: applicant tracks review
- WHEN a signed-in applicant opens the status page while the application is
pending - THEN the pending state and submission summary are shown
Scenario: rejection explains reason
- WHEN a rejected applicant opens the status page
- THEN the rejection reason is displayed with an action to apply again
Requirement: Member center level page
The buyer center SHALL render a membership level page from the selected API adapter: the current level's name, icon, and benefits, the total growth value, progress toward the next level's threshold, and the customer's growth history from the growth ledger. The page SHALL NOT derive level state from fixtures or local state, and it SHALL behave deterministically when the membership domain is configured to fixed data.
Scenario: level page reflects backend state
- WHEN a signed-in shopper whose growth qualifies for a level opens the member-center level page
- THEN the current level, benefits, growth total, and remaining growth to the next level render from the adapter without per-entry requests
Scenario: growth history lists ledger entries
- WHEN a shopper opens the level page
- THEN recent growth ledger entries with delta, reason, and time render paginated from the adapter
Scenario: fixed adapter remains functional
- WHEN the
membershipdomain is configured to fixed data - THEN the level page renders deterministic fixed level and growth data through the same shared client methods
Requirement: Message center
The mall SHALL render a user message center from the selected API adapter with a paginated message list and an unread-only filter. Opening or explicitly marking a message read, marking all read, and deleting a message SHALL persist through the API and update the visible list and counts without local-only mutation. Each message SHALL render its title and body in the active locale from the shared bilingual contract. Listing and refresh SHALL happen on entry and on demand; no push transport is required.
Scenario: unread filter and marking
- WHEN a shopper filters the message center to unread and marks one message read
- THEN the message persists as read and leaves the unread-only view with the unread count reduced
Scenario: mark all read
- WHEN a shopper uses mark-all-read in the message center
- THEN every unread message becomes read and the unread-only view empties
Scenario: delete a message
- WHEN a shopper deletes a message
- THEN it disappears from the list and the unread count through the API state
Scenario: fixed adapter remains functional
- WHEN the
messagingdomain is configured to fixed data - THEN message listing, read marking, mark-all-read, and deletion behave deterministically through the same shared client methods
Requirement: Top-bar unread badge
The mall shell SHALL show the authenticated customer's unread message count as a badge on its message entry, read from the shared unread-count contract. The badge SHALL refresh on page entry and after read, mark-all-read, and delete actions, and SHALL be absent for anonymous shoppers.
Scenario: badge reflects unread count
- WHEN a signed-in shopper with three unread messages loads any mall page
- THEN the message entry badge shows three
Scenario: badge clears after mark-all-read
- WHEN a shopper marks all messages read and returns to the shell
- THEN the badge shows no unread count