Files
vmall/openspec/changes/archive/2026-09-17-frontend-apps/agent-conventions.md
T

28 lines
3.2 KiB
Markdown

# VMall frontend conventions (READ FIRST — applies to every app agent)
Repo: /Users/chengdzhang/github/jamyun/vmall (pnpm workspace). The Rust API is COMPLETE and tested; do not touch apps/api, packages/shared, openspec, or any app other than yours.
## Your app
- Nuxt 3 + pinia + @nuxtjs/i18n. Deps installed. Verify with `pnpm --filter @vmall/<your-app> build` at the end (MUST pass). Do not run dev servers, do not run other apps' builds, do not run cargo.
- The API may not be running while you work — code against the contract, prove with `nuxt build` (type safety + compile). NEVER mock the API.
## Contract (packages/shared/src)
- `useNuxtApp().$api` is a typed `ApiClient` (see packages/shared/src/api.ts for every method + request body types). Provided by plugins/api.ts (already wired, reads baseUrl from runtimeConfig public.apiBase, token from localStorage `vmall.token`).
- Types in packages/shared/src/types.ts (User, Product, Sku, Cart, Order, Shipment, Invoice, Currency, Paged, etc.). Money = integer minor units + ISO currency code.
- `t(localizedText, locale)` picks the display string from a JSONB {"en","zh"} map. `formatMoney(amountMinor, currency, exponent, locale)` formats minor units.
- All UI strings via $t with keys from @vmall/shared/locales (en + zh exist). If you need a key that doesn't exist, add it to YOUR app's locales-extra.ts (enExtra/zhExtra, same nested shape) — NEVER edit packages/shared.
- Shared stylesheet `@vmall/shared/ui.css` is loaded: use its classes (.card, .btn .primary .sm .danger, .table, .badge .green/.blue/.orange/.red, .field, .grid.products, .product-card, .page-title, .page-head, .form-narrow, .muted, .row, .between, .mt, .mb, .error-text). Add app-scoped CSS only in a <style> block when needed.
## Session & guards
- stores/session.ts: `useSessionStore()` — hydrate() on mounted (app.vue already does), setAuth({token,user}), logout(), getters.isLoggedIn, state.user (role, display_name).
- Route guard convention: create `middleware/auth.ts` with defineNuxtRouteMiddleware that (client-side) reads localStorage `vmall.token` + `vmall.user` (JSON) and redirects to /login when absent or when user.role is not in the app's allowed roles. Apply with `definePageMeta({ middleware: "auth" })` on every protected page. The /login page itself is public.
- Login page pattern: form → `$api.login(email, password)` → on success check `user.role` against the app's allowed roles (wrong role → show error, do not setAuth) → `session.setAuth(...)` → navigateTo("/"). Show API error message on failure.
## Behavior rules
- All pages render in BOTH en and zh — switcher already in app.vue; never hardcode user-facing strings.
- All list pages handle empty state ($t('common.empty')) and API errors (show err.message in .error-text).
- After every mutation, re-fetch the affected list/detail from the API (no local-only state faking).
- Dates: `new Date(x).toLocaleString(locale === 'zh' ? 'zh-CN' : 'en-US')`.
- Money inputs: enter major units (e.g. 12.99), convert to minor via exponent (SKU currencies are exp-2 except JPY exp-0; SKU editor may restrict currency to USD/CNY/EUR which are all exp-2).
- Status badges: map statuses to .badge colors (green=positive terminal, blue=active/in-progress, orange=pending, red=cancelled/rejected).