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
This commit is contained in:
Chengdong Zhang
2026-09-22 18:22:50 +08:00
parent 4d2ee3b0bf
commit 0d0e10b97b
101 changed files with 2833 additions and 7207 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-22
@@ -0,0 +1,56 @@
## Context
Three Nuxt 3 apps share `@vmall/shared` (API types, locales, `ui.css`) and otherwise style themselves with global CSS plus Vue `<style scoped>` (~4k lines, mostly mall). There is no Tailwind/PostCSS today. Mall is a classic B2B2C PC chrome (`#ca151e`, 1200px, 450px hero); admin consoles are a blue SaaS shell. Operators asked for a full rewrite onto Tailwind, one design system, a visual refresh that **keeps mall IA**, red as the default accent, and **preset accent switching in shop-admin and platform admin only**.
## Goals / Non-Goals
**Goals:**
- Replace `ui.css`, `mall.css`, and all scoped CSS with Tailwind v4 utilities plus a small Vue kit.
- One token set (red default) across mall, shop-admin, and platform admin.
- Console accent presets (`red | blue | teal | violet`) persisted locally; mall locked to red.
- Preserve mall shell behavior required by `frontend-mall`.
**Non-Goals:**
- Dark mode, free hex picker, per-shop customer-facing branding, theming the public mall.
- Element Plus or other component libraries.
- Backend/API work; catalog module split.
## Decisions
1. **Tailwind v4 via `@tailwindcss/vite`**, not `@nuxtjs/tailwindcss`. Apps already run Nuxt 3 + Vite 7; v4 is CSS-first (`@theme`) and avoids a PostCSS/v3-oriented module.
2. **Tokens in `@vmall/shared/theme.css`**, replacing `ui.css`. Shared stays the contract package (types, API, locales, theme). Vue is not added to shared.
3. **New `@vmall/ui`** for `VBtn`, `VBadge`, `VField`/`VInput`, `VCard`/`VPanel`, `VTable`, `VPage`, and `useAccent()`. Mall-only chrome (mega-menu, carousel, `w1200` shell, product-card lift) stays in `apps/mall`. Admin sidebar layouts stay in each admin app.
*Alternative considered:* put Vue primitives in shared — rejected so the API contract package stays Vue-free. *Alternative:* utilities only, no kit — rejected; repeating `.btn` / `.mbtn` as 10-class strings will rot.
4. **No `@apply` leftover stylesheets.** Layout = utilities; repeating chrome = kit. Spec-locked sizes become theme keys (`max-w-mall`, `h-hero`) rather than scattered `w-[1200px]`.
5. **Accent via `html[data-accent]`** mapping CSS variables that `@theme` aliases to `primary`. `useAccent()` writes `localStorage` (`vmall-accent` per app origin) and sets the dataset. Mall sets `data-accent="red"` at boot and does not render a switcher. Invalid stored values fall back to red.
*Alternative considered:* user-profile API — out of scope. *Alternative:* `class="dark"` — not in this change.
6. **Phased conversion, then delete.** Dual-load old CSS until each app is converted so screens are never unstyled; then remove globals and scoped blocks.
## Risks / Trade-offs
- [Mall visual regression] → Convert shell first; browser-pass spec flows (home hero, mega-menu, search grid, PDP); keep 1200px / 450px / `#ca151e` as tokens.
- [Tailwind v4 + Nuxt 3 plugin friction] → Spike on admin app first; fall back to documented Vite plugin wiring, not a new CSS engine.
- [Accent contrast on badges/tables] → Presets only; each preset defines primary + hover + soft background, not a raw hex.
- [Shared package build gate] → `theme.css` change still requires all three app builds.
- [Scope creep vs catalog split] → Separate OpenSpec change; do not mix PRs.
## Migration Plan
1. Scaffold `theme.css` + `@vmall/ui` + Vite plugin; keep loading `ui.css` / `mall.css`.
2. Convert platform admin, then shop-admin (introduce accent swatches).
3. Convert mall shell, then mall pages (largest last).
4. Delete `ui.css`, `mall.css`, remaining `<style scoped>`; switch shared export to `theme.css`; update `AGENTS.md`.
5. Rollback: git revert the change; no DB/API migration.
## Open Questions
None that block implementation. If `@tailwindcss/vite` cannot register cleanly in a given Nuxt 3 minor, document the working plugin hook in this change rather than switching to UnoCSS.
@@ -0,0 +1,33 @@
## Why
Styling is split across `ui.css`, `mall.css`, and ~4k lines of Vue scoped CSS, with two palettes (mall red vs admin blue) and no shared primitives. Adopting Tailwind v4 plus one token/kit package lets us rewrite every surface, refresh visuals, and give consoles accent presets without a second CSS stack.
## What Changes
- **BREAKING**: Remove `@vmall/shared/ui.css` and `apps/mall/assets/mall.css`. Shared contract export becomes `theme.css`.
- Add Tailwind v4 (`@tailwindcss/vite`) to mall, shop-admin, and platform admin.
- Add `@vmall/ui`: Vue primitives (`VBtn`, `VBadge`, `VField`/`VInput`, `VCard`/`VPanel`, `VTable`, `VPage`) plus `useAccent()`.
- One design system: commerce red (`#ca151e`) default; same radius/type/spacing on all three apps.
- Mall keeps classic PC chrome (1200px grid, dark nav, mega-menu, 450px home hero) with refreshed tokens.
- Shop-admin and platform admin: accent presets `red | blue | teal | violet`, persisted in `localStorage`. Mall stays red with no switcher.
- Delete all Vue `<style scoped>` in the three apps after conversion.
## Capabilities
### New Capabilities
- `frontend-ui`: Shared Tailwind theme, UI kit, and console accent presets.
### Modified Capabilities
- `frontend-mall`: Storefront MUST use the shared design system; visual IA (1200px / `#ca151e` / hero / mega-menu) stays; no public accent switcher.
- `frontend-admin`: Platform console MUST use the shared kit and expose accent presets.
- `frontend-shop-admin`: Merchant console MUST use the shared kit and expose accent presets.
## Impact
`packages/shared` (`ui.css``theme.css`), new `packages/ui`, three Nuxt apps (all SFCs), `AGENTS.md` / OpenSpec config (shared styles path). No API or money-handling changes. Three-app `pnpm` builds plus a mall visual pass.
## Non-goals
Dark mode, free hex picker, per-shop customer-facing branding, theming the public mall, Element Plus, mixing this with the catalog-module split.
@@ -0,0 +1,15 @@
## ADDED Requirements
### Requirement: Shared design system in the platform console
The platform admin console SHALL use `@vmall/ui` primitives and `@vmall/shared` theme tokens for chrome (buttons, fields, cards, tables, page headers, nav). It MUST NOT depend on Element Plus or the removed `ui.css` stylesheet.
#### Scenario: console chrome from the kit
- **WHEN** an authenticated platform admin opens the dashboard
- **THEN** primary actions and page chrome render through the shared kit with the current accent preset
### Requirement: Accent preset switcher
Platform admin SHALL expose the shared accent preset control (`red`, `blue`, `teal`, `violet`) in the authenticated shell header.
#### Scenario: switcher visible
- **WHEN** an authenticated platform admin opens the console
- **THEN** they can select an accent preset without leaving the current page
@@ -0,0 +1,29 @@
## MODIFIED Requirements
### 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`
## ADDED Requirements
### 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`
@@ -0,0 +1,15 @@
## ADDED Requirements
### Requirement: Shared design system in the merchant console
The shop-admin console SHALL use `@vmall/ui` primitives and `@vmall/shared` theme tokens for chrome (buttons, fields, cards, tables, page headers, nav). It MUST NOT depend on Element Plus or the removed `ui.css` stylesheet.
#### Scenario: console chrome from the kit
- **WHEN** an authenticated shop user opens shop-admin
- **THEN** primary actions and page chrome render through the shared kit with the current accent preset
### Requirement: Accent preset switcher
Shop-admin SHALL expose the shared accent preset control (`red`, `blue`, `teal`, `violet`) in the authenticated shell header.
#### Scenario: switcher visible
- **WHEN** an authenticated shop user opens shop-admin
- **THEN** they can select an accent preset without leaving the current page
@@ -0,0 +1,27 @@
## ADDED Requirements
### Requirement: Shared Tailwind design system
The three Nuxt apps SHALL load one token stylesheet from `@vmall/shared` (`theme.css`) and SHALL compose repeating chrome from `@vmall/ui` primitives (`VBtn`, `VBadge`, `VField`/`VInput`, `VCard`/`VPanel`, `VTable`, `VPage`). They MUST NOT depend on Element Plus, MUST NOT keep `packages/shared/src/ui.css` or `apps/mall/assets/mall.css`, and MUST NOT use Vue `<style scoped>` for app chrome after the migration. Default brand/primary color SHALL be `#ca151e`.
#### Scenario: apps share tokens
- **WHEN** a developer opens mall, shop-admin, and platform admin with the default accent
- **THEN** primary buttons, links, and active nav use `#ca151e` from the shared theme rather than per-app palettes
#### Scenario: no legacy stylesheets
- **WHEN** the migration is complete
- **THEN** `@vmall/shared` exports `theme.css` (not `ui.css`) and the three apps have no remaining `<style scoped>` blocks for layout or chrome
### Requirement: Console accent presets
Shop-admin and platform admin SHALL let the signed-in operator pick an accent preset of `red`, `blue`, `teal`, or `violet`. The choice SHALL apply immediately to primary actions, links, and active navigation, SHALL persist in `localStorage` for that app origin, and SHALL restore on reload. An invalid stored value MUST fall back to `red`. The public mall MUST NOT expose this control and MUST remain on the red accent.
#### Scenario: operator switches accent
- **WHEN** a platform admin or shop user selects the blue preset in the console header
- **THEN** primary buttons and active nav in that app render with the blue preset without a full page reload
#### Scenario: accent survives reload
- **WHEN** the operator reloads shop-admin or platform admin after choosing teal
- **THEN** the console still uses the teal preset
#### Scenario: mall stays red
- **WHEN** a shopper uses the mall after an operator has changed a console accent
- **THEN** the mall still uses `#ca151e` and shows no accent switcher
@@ -0,0 +1,38 @@
## 1. Tooling and tokens
- [x] 1.1 Add Tailwind v4 and `@tailwindcss/vite` to mall, shop-admin, and platform admin; wire the Vite plugin in each `nuxt.config.ts`
- [x] 1.2 Add `packages/shared/src/theme.css` with `@theme` keys (primary `#ca151e`, hover, soft, radius, type, `max-w-mall` 1200px, `h-hero` 450px) and `html[data-accent]` maps for `red|blue|teal|violet`
- [x] 1.3 Export `./theme.css` from `@vmall/shared`; load it in all three apps while still loading `ui.css` / `mall.css`
## 2. `@vmall/ui` kit
- [x] 2.1 Scaffold `packages/ui` (`@vmall/ui`) in the pnpm workspace with Vue peer deps and exports
- [x] 2.2 Implement `VBtn`, `VBadge`, `VField`/`VInput`, `VCard`/`VPanel`, `VTable`, `VPage` on shared tokens
- [x] 2.3 Implement `useAccent()` (read/write `localStorage`, set `document.documentElement.dataset.accent`, invalid → `red`) and an accent swatch control
- [x] 2.4 Add the kit as a dependency of the three Nuxt apps and transpile it if needed
## 3. Platform admin
- [x] 3.1 Convert `apps/admin` shell and pages to kit + Tailwind utilities; add the accent swatch to the authenticated header
- [x] 3.2 Remove admin `<style scoped>` blocks
- [x] 3.3 `pnpm --filter @vmall/admin build` green; browser-check accent switch + reload persistence
## 4. Shop-admin
- [x] 4.1 Convert `apps/shop-admin` shell and pages the same way; add the accent swatch
- [x] 4.2 Remove shop-admin `<style scoped>` blocks
- [x] 4.3 `pnpm --filter @vmall/shop-admin build` green; browser-check accent switch + reload persistence
## 5. Mall storefront
- [x] 5.1 Lock mall to `data-accent="red"` at boot; do not render the swatch
- [x] 5.2 Convert mall shell (utility bar, header, dark nav, mega-menu, 1200px grid, footer) to tokens/utilities/kit
- [x] 5.3 Convert mall pages (home hero/sidebar/carousel last-but-one; `goods/[id]`, integral, store last)
- [x] 5.4 Remove all mall `<style scoped>` and stop loading `mall.css`
## 6. Cut over and docs
- [x] 6.1 Delete `packages/shared/src/ui.css` and the `./ui.css` export; point `AGENTS.md` / OpenSpec config at `theme.css` and `@vmall/ui`
- [x] 6.2 Confirm no `<style scoped>` remains under `apps/mall`, `apps/shop-admin`, `apps/admin`
- [x] 6.3 `pnpm --filter @vmall/mall build` plus admin and shop-admin builds all green
- [x] 6.4 Browser pass: mall shell, home hero/sidebar, search grid, product detail, cart/checkout chrome; both consoles on default red and after a preset change