Files
2026-05-27 23:29:53 +01:00

293 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks: enterprise-react-ui-v2
**Change ID**: enterprise-react-ui-v2
**Package**: `@suaveplan/react-ui`
**Target path**: `packages/react/react-ui`
**Prerequisite changes (must be archived before this one ships)**: `enterprise-react-i18n` (amended with the `LocaleDetector` chain + `ContributedTranslationKeys` augmentation pattern)
**Prerequisite packages on Verdaccio**: `@suaveplan/react-i18n`, `@suaveplan/i18n` (with `i18n-storage-backends` and `i18n-completion` folded in), `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/locale-data`, `@suaveplan/types`
**Sibling changes (parallel work; coordinated archive order)**: `enterprise-react-forms-v2`, `enterprise-react-ui-theme-dracula`, `enterprise-react-ui-pack-auth`, `react-ui-registry-deployment`
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 0. Supersession
- [ ] 0.1 Mark `openspec/changes/enterprise-react-ui/` as superseded with a one-line note in its `proposal.md` header pointing here. Move it to `openspec/archive/<yyyy-mm-dd>-enterprise-react-ui-superseded/` only after this change archives (per AGENTS.md §13 archival is post-ship; pre-ship the legacy folder stays where it is so nobody picks it up by accident — the superseded note is enough).
- [ ] 0.2 Update `STATUS.md` and `SUMMARY.md` to reflect `@suaveplan/react-ui` v2 in flight; explicitly note the legacy proposal is superseded so the parity-checklist tooling picks the right source.
## 1. Wave 0 — Spec authoring + spike
### 1.1 Spec authoring
- [ ] 1.1.1 Read `proposal.md` and `specs/react-ui/spec.md` end-to-end
- [ ] 1.1.2 Read `openspec/cross-cutting-designs/2026-05-05-react-ui-react-i18n-react-forms.md` (full document; this change owns §3, §4, §5, §6, §8, §10, §11, §12)
- [ ] 1.1.3 Read `openspec/AGENTS.md` §0a (parity), §13 (pre-ship verification), §13a (behavior-change propagation), §14a (Affected dependents)
- [ ] 1.1.4 Read CLAUDE.md §10 (no `export *` in non-root barrels) and §11 (test-runner imports via `@suaveplan/testing/runner`) — both are non-negotiable for every snippet in this change
- [ ] 1.1.5 `openspec change validate enterprise-react-ui-v2` exits with no errors
### 1.2 Wave 0 — Component-list reconciliation
- [ ] 1.2.1 Run `npx shadcn@latest search @shadcn` from a clean checkout; capture the raw output
- [ ] 1.2.2 Read upstream `/mnt/s/web/suaveplan/packages/react/react-ui/src/components/` (57 entries) end-to-end; note every component the legacy proposal listed
- [ ] 1.2.3 Diff the two lists; classify every entry as kept / renamed / split / dropped / added; record rationales for every drop
- [ ] 1.2.4 Update the §"Component count — resolved here" table in `proposal.md` with the final list and lock the count before any Wave 3 code lands
- [ ] 1.2.5 Reconcile the `## Parity Checklist` table to reflect the final set; every row resolves to ✅ / 🚫 (with one-line justification) / ⬆️
### 1.3 Wave 0 — Theme typing spike
- [ ] 1.3.1 Spike PR (`openspec/changes/enterprise-react-ui-v2/spike-theme-typing/`) implementing two real cva configs (Button + Card) under the §"Type-safety trade-off" pattern: `useThemedVariants('Button')` returns base variants, `useThemedVariants<'dracula', 'Button'>('Button')` allows brand-new variants
- [ ] 1.3.2 Validate the typing ergonomics — write `expectTypeOf` assertions covering the four cells of `(default-typed × narrowed) × (base-variant × theme-variant)`
- [ ] 1.3.3 Land the spike (or escalate a different mechanism) before Wave 3 starts — the cross-cutting design §16 row 2 makes this a Wave 0 deliverable, not a Wave 3 risk
## 2. Wave 2 — Scaffold + chassis
### 2.1 Package scaffold
- [ ] 2.1.1 Create `packages/react/react-ui/` with `package.json` at `0.1.0`, `tsconfig.lib.json` extending `@suaveplan/typescript-config/react-library.json`, `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`, `vite.config.ts`, `tailwind.config.ts`, `playwright.config.ts`, scripts (`lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `e2e`, `registry:build`, `locales:check`, `locales:types`, `lint:no-hardcoded-strings`, `lint:translation-keys`)
- [ ] 2.1.2 Dependencies (`workspace:^` for internal, `catalog:<key>` for external — never bare versions per AGENTS.md §1):
- Internal: `@suaveplan/react-i18n`, `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/locale-data`, `@suaveplan/types`, `@suaveplan/i18n` (transitive but listed explicitly so the `MessageParser` import path is reviewed)
- External: every Radix primitive listed in proposal.md §"Available workspace deps"; `class-variance-authority`, `clsx`, `cmdk`, `embla-carousel-react`, `input-otp`, `lucide-react`, `react-day-picker`, `react-hook-form`, `@hookform/resolvers`, `react-resizable-panels`, `recharts`, `sonner`, `tailwind-merge`, `tailwindcss`, `vaul`, `tslib`, `zod`, `@opentelemetry/api`
- [ ] 2.1.3 Peer dependencies: `react ^18.0.0 || ^19.0.0`, `react-dom ^18.0.0 || ^19.0.0`
- [ ] 2.1.4 Dev dependencies: `@suaveplan/testing workspace:^`, `@suaveplan/testing-dom workspace:^`, `@suaveplan/testing-e2e workspace:^`, `@testing-library/react`, `@types/react`, `@types/react-dom`, `react`, `react-dom`, `vitest`, `vite`, `vite-plugin-dts`, `fast-check`, `@vitest/coverage-istanbul`, `@axe-core/playwright`, `@storybook/react`, `@storybook/addon-a11y`
- [ ] 2.1.5 NO `i18next`, NO `react-i18next`, NO `@suaveplan/react-translate` — verified by grep before commit
- [ ] 2.1.6 `"sideEffects": ["**/*.css"]` (CSS imports DO have side effects — Tailwind layers register globally)
- [ ] 2.1.7 Subpath exports map: one `exports` entry per feature module (`./provider/react-ui-provider/react-ui-provider`, `./theme/theme-provider/theme-provider`, `./theme/use-themed-variants/use-themed-variants`, `./pack/pack-registry-provider/pack-registry-provider`, `./pack/use-pack-component/use-pack-component`, one per component, one per locale JSON, etc.) plus `./styles/global.css`. AGENTS.md §13 step 8 verifies subpath count matches feature module count.
### 2.2 Errors and types
- [ ] 2.2.1 Define `ReactUiError` and codes via `defineCodes("REACT_UI", [...])`: `NO_PROVIDER`, `THEME_NOT_FOUND`, `THEME_TOKEN_MISSING`, `PACK_ALREADY_REGISTERED`, `PACK_NOT_FOUND`, `PACK_COMPONENT_NOT_FOUND`, `PACK_VERSION_MISMATCH`, `LOCALE_BUNDLE_LOAD_FAILED`, `NO_HARDCODED_STRINGS_VIOLATION`
- [ ] 2.2.2 Define types: `ThemePlugin`, `ThemeTokenSet`, `MotionTokenSet`, `TailwindPreset`, `ThemableVariants` (empty interface — augmented per-component), `ComponentPack`, `PackComponents` (empty interface — augmented per-pack), `PackContext`, `ReactUiProviderProps`, `ThemeProviderProps`, `PackRegistryProviderProps`, `SupportedLocale`, `LocaleBundle`, every hook return type
- [ ] 2.2.3 `react-ui-error.test.ts` — code registry, error chain, JSON wire format
- [ ] 2.2.4 Sibling `.md` for the error class
### 2.3 Locale bundles
- [ ] 2.3.1 Author `src/locales/en-GB.json` as the source-of-truth bundle. Single namespace `react-ui`. Flat keys dot-namespaced by component. Cover every default user-perceivable string for every component planned in §"Component count". Use ICU plural syntax (`{count, plural, =0 {No rows} one {# row} other {# rows}}`) where component output is plural-sensitive.
- [ ] 2.3.2 Translate to `zh-CN.json` (CJK), `ar-AE.json` (RTL — values only; `dir="rtl"` is set by `<ThemeProvider>` not the JSON), `cy-GB.json` (six plural categories — every plural key MUST exercise `zero / one / two / few / many / other`), `ru-RU.json` (Slavic plurals)
- [ ] 2.3.3 `src/locales/schema.ts` — Zod `LocaleBundleSchema` derived from `en-GB.json` keys; every other locale validated against this schema at module load
- [ ] 2.3.4 `src/locales/index.ts` — explicit named export of `reactUiBundles: Record<SupportedLocale, () => Promise<LocaleBundle>>` and `SUPPORTED_LOCALES = ['en-GB', 'zh-CN', 'ar-AE', 'cy-GB', 'ru-RU'] as const`. NO `export *`.
- [ ] 2.3.5 `scripts/check-locale-completeness.ts` — Bun script asserting every key in `en-GB.json` exists in every other locale file; structured diff output on failure
- [ ] 2.3.6 `scripts/generate-translation-types.ts` — Bun script generating `src/locales/augmentation.d.ts` from `en-GB.json` keys; output module-augments `ContributedTranslationKeys` per cross-cutting design §11.2
- [ ] 2.3.7 `src/locales/locales.spec-conformance.test.ts` — runs the completeness check as a unit test; asserts every key exists in every locale and every locale parses against `LocaleBundleSchema`
- [ ] 2.3.8 `src/locales/cy-plural.spec-conformance.test.ts` — asserts `Intl.PluralRules('cy', { type: 'cardinal' }).select(n)` returns the expected category for canonical reference values (`0 → zero`, `1 → one`, `2 → two`, `3 → few`, `6 → many`, `4 → other`) under both `bun test` and `vitest run`
- [ ] 2.3.9 `src/locales/locales.md` — covers add-a-locale workflow, key naming convention, ICU plural usage, RTL note, Welsh six-plural note
### 2.4 ThemeProvider + theme primitives
- [ ] 2.4.1 `src/theme/theme.types.ts` — `ThemePlugin`, `ThemeTokenSet`, `MotionTokenSet`, `TailwindPreset`, `ThemableVariants` (empty interface — augmented per-component); Zod `ThemePluginSchema` / `ThemeTokenSetSchema` / `MotionTokenSetSchema`
- [ ] 2.4.2 `src/theme/theme-provider/theme-provider.tsx` — accepts `themes`, `defaultTheme`, `storageKey`, `enableSystem`, `attribute = 'data-theme'`, `scriptNonce`. Injects an inline FOUC-prevention script into `<head>` before hydration (CSP-nonce-aware). Subscribes to `prefers-reduced-motion` and updates `--motion-duration-*` / `--motion-easing-*` CSS variables. Emits `theme.changed` on the bus and increments `react_ui.theme.changed_total` + `react_ui.theme.switch_duration_ms`.
- [ ] 2.4.3 `src/theme/use-theme/use-theme.ts` — `useTheme()` returns `{ theme, themes, setTheme, isPending }`; throws `REACT_UI.NO_PROVIDER` outside a `<ThemeProvider>` in dev, soft-fails to identity in prod
- [ ] 2.4.4 `src/theme/use-theme-tokens/use-theme-tokens.ts` — returns the live `ThemeTokenSet` for components needing direct token access (recharts palette, etc.)
- [ ] 2.4.5 `src/theme/use-themed-variants/use-themed-variants.ts` — implements the §"Type-safety trade-off" pattern. Resolves the active theme at render and merges `variantOverrides[componentName]` with the base cva config. Memoised per `(themeName, componentName)` tuple. Histogram `react_ui.themed_variants.resolve_duration_ms` on cache miss only.
- [ ] 2.4.6 `src/theme/themes/light.ts`, `src/theme/themes/dark.ts`, `src/theme/themes/high-contrast.ts` — three built-in themes, each implementing `ThemePlugin`; each with sibling `.md` documenting tokens
- [ ] 2.4.7 `src/theme/theme-switcher/theme-switcher.tsx` — headless render-prop mirror of `<LanguageSwitcher>`; reads `theme.labelKey` (TranslationKey, never literal) and renders via the active i18n instance
- [ ] 2.4.8 Tests: `theme-provider.test.tsx` (FOUC script injection, system listener, CSP nonce, `setTheme` happy path + `THEME_NOT_FOUND`), `use-theme.test.tsx`, `use-themed-variants.test.tsx` (base typing, narrowed typing, memo invalidation on theme change, brand-new variant fallback when active theme lacks it), `theme-switcher.test.tsx`
- [ ] 2.4.9 Sibling `.md` per implementation file
### 2.5 PackRegistryProvider + pack primitives
- [ ] 2.5.1 `src/pack/pack.types.ts` — `ComponentPack`, `PackComponents` (empty interface — augmented per-pack), `PackContext`; Zod `ComponentPackSchema`
- [ ] 2.5.2 `src/pack/pack-registry-provider/pack-registry-provider.tsx` — accepts `packs: ComponentPack[]`, optional `components` for runtime override per pack; throws `REACT_UI.PACK_ALREADY_REGISTERED` when two packs share `name`. Calls each pack's `onRegister(ctx)` in registration order, `onUnregister(ctx)` in reverse order. Emits `pack.registered` / `pack.unregistered`. Per-pack scoped React context. Forwards each pack's `translations` to the active i18n instance via `addResourceBundle(pack.name, pack.translations[locale])`.
- [ ] 2.5.3 `src/pack/use-pack-component/use-pack-component.ts` — generic-typed `usePackComponent<TName, TComponent>(packName, componentName)`; throws `PACK_NOT_FOUND` / `PACK_COMPONENT_NOT_FOUND`
- [ ] 2.5.4 `src/pack/use-ui-component/use-ui-component.ts` — for built-in components, allows pack override via the runtime `components` prop on `<ReactUiProvider>`
- [ ] 2.5.5 `src/pack/version-check/version-check.ts` — semver `peerRange` against the running `react-ui` version; mismatch logs `pack.version_mismatch` warning + increments `react_ui.pack.version_mismatch_total`; never throws
- [ ] 2.5.6 Tests: `pack-registry-provider.test.tsx` (register order, unregister reverse-order, name-collision throw, runtime override, lifecycle hooks fire), `use-pack-component.test.tsx`, `version-check.test.ts`
- [ ] 2.5.7 Sibling `.md` per implementation file
### 2.6 ReactUiProvider (opinionated wrapper)
- [ ] 2.6.1 `src/provider/react-ui-provider/react-ui-provider.tsx` — composes `<TranslationProvider> → <ThemeProvider> → <PackRegistryProvider>`. ~15 lines of composition. Awaits the lazy `reactUiBundles[locale]()` import on mount and passes the result to `i18n.addResourceBundle('react-ui', bundle)`. Honours the `reactUiOverrides` prop documented in cross-cutting design §8.4.
- [ ] 2.6.2 OTel span `react_ui.provider.mount` with attributes `{ theme, pack.count, locale }`
- [ ] 2.6.3 Tests: `react-ui-provider.test.tsx` (composition order, bundled-translation auto-registration, override merge order, mount span attributes)
- [ ] 2.6.4 Sibling `.md`
### 2.7 Utilities + hooks
- [ ] 2.7.1 `src/utils/cn/cn.ts` — `clsx + twMerge`; `cn.test.ts` (merging conflicts, no-arg, array input); sibling `.md`
- [ ] 2.7.2 `src/utils/motion/motion.ts` — `motionToken(reducedMotion: string, fullMotion: string): string` reading `window.matchMedia('(prefers-reduced-motion: reduce)')`; SSR guard; sibling `.md`; tests
- [ ] 2.7.3 `src/hooks/use-mobile/use-mobile.ts` — `useMediaQuery('(max-width: 768px)')` via `useState + useEffect`; sibling `.md`; tests
- [ ] 2.7.4 `src/hooks/use-toast/use-toast.ts` — toast queue backed by `sonner`; emits `toast.show` span event + `react_ui.toast.shown_total`; sibling `.md`; tests
### 2.8 Wave 2 gate
- [ ] 2.8.1 `bunx turbo lint typecheck test --filter=@suaveplan/react-ui` — green chassis with zero components yet
- [ ] 2.8.2 `bunx turbo test:coverage --filter=@suaveplan/react-ui` — 100/100/100/100 on the chassis surface
- [ ] 2.8.3 `bun scripts/check-locale-completeness.ts` — green
- [ ] 2.8.4 `bun scripts/generate-translation-types.ts` — produces `src/locales/augmentation.d.ts` matching `en-GB.json` exactly; idempotent
- [ ] 2.8.5 Wave 2 PR opens; reviewers verify the §"Type-safety trade-off" spike landed and the §"Component count" table is finalised before Wave 3 starts
## 3. Wave 3 — Components in 8 batches
For **every** component within a batch:
- [ ] 3.X.Y.1 Implement `<component-name>.tsx` — Radix wrapper (where applicable), `forwardRef`, `asChild` support, `useThemedVariants(componentName)` for variant resolution, `useTranslation('react-ui')` for any default user-visible copy, every prop carrying user text typed as `TranslationKey` (NEVER `string`)
- [ ] 3.X.Y.2 `<component-name>.types.ts` — every public prop interface, no inline shapes (AGENTS.md §11)
- [ ] 3.X.Y.3 `<component-name>.md` — ≥200 words; Purpose + Features + Basic + Advanced + API + Implementation Notes
- [ ] 3.X.Y.4 `<component-name>.test.tsx` — public-API contract, RHF integration where applicable, callback firing, a11y attributes (`role`, `aria-*`), variant classes, theme-override flow under `<ThemeProvider themes={[lightTheme, draculaTheme]}>` ; `{ timeout: 5000 }` on every `it()` per CLAUDE.md
- [ ] 3.X.Y.5 `<component-name>.stories.tsx` — Default + Playground + Variants + States + WithI18n (5 locales incl. RTL `ar-AE`) + WithTheme (4 themes incl. `draculaTheme`) + A11y + Composition (where composable)
- [ ] 3.X.Y.6 `index.ts` barrel using **explicit named re-exports** (CLAUDE.md §10; no `export *`)
- [ ] 3.X.Y.7 Add the component's translation keys to `src/locales/en-GB.json` first, then port to the other four locales
- [ ] 3.X.Y.8 Re-run `scripts/generate-translation-types.ts`; commit the regenerated `augmentation.d.ts`
- [ ] 3.X.Y.9 Re-run `scripts/lint/check-no-hardcoded-strings.ts` and `scripts/lint/check-translation-keys.ts`; both green
### 3.1 Batch 3a — Foundation (5)
- [ ] 3.1.1 Button (exercises core theme + variant pipeline)
- [ ] 3.1.2 Input
- [ ] 3.1.3 Label
- [ ] 3.1.4 Separator
- [ ] 3.1.5 Skeleton
- [ ] 3.1.6 Batch 3a gate: `bunx turbo lint typecheck test test:coverage --filter=@suaveplan/react-ui` green
### 3.2 Batch 3b — Form primitives (10)
- [ ] 3.2.1 FieldGroup, Field, FieldSet, InputGroup, Textarea, Checkbox, Switch, RadioGroup, Toggle, ToggleGroup
- [ ] 3.2.2 Each primitive's API surface MUST be re-exportable from `@suaveplan/react-forms` without modification (cross-cutting design §9.2)
- [ ] 3.2.3 Batch 3b gate
### 3.3 Batch 3c — Overlays (8)
- [ ] 3.3.1 Dialog, Sheet, Drawer, AlertDialog, Popover, HoverCard, Tooltip, ContextMenu
- [ ] 3.3.2 Every overlay component's `<component-name>.test.tsx` MUST cover focus-trap + restore + Escape-closes (cross-cutting design §10.4)
- [ ] 3.3.3 Batch 3c gate
### 3.4 Batch 3d — Data display (10)
- [ ] 3.4.1 Table, DataTable, Card, Badge, StatusBadge, Avatar, Alert, Empty, Progress, Spinner
- [ ] 3.4.2 `DataTable.emptyMessage` prop accepts `TranslationKey | ReactNode` per cross-cutting design §11.7 pattern C
- [ ] 3.4.3 Batch 3d gate
### 3.5 Batch 3e — Navigation (8)
- [ ] 3.5.1 Sidebar, NavigationMenu, Breadcrumb, Tabs, Pagination, Menubar, DropdownMenu, Command
- [ ] 3.5.2 `Pagination` swaps chevron icons under `useDirection() === 'rtl'`
- [ ] 3.5.3 `Command` emits `command.select` span event + `react_ui.command.selected_total`
- [ ] 3.5.4 `Sidebar` extracted helpers (`SidebarNav`, `SidebarSection`) to stay under the AGENTS.md §3 300-line limit
- [ ] 3.5.5 Batch 3e gate
### 3.6 Batch 3f — Layout (6)
- [ ] 3.6.1 Accordion, Collapsible, Resizable, ScrollArea, AspectRatio, Carousel
- [ ] 3.6.2 Batch 3f gate
### 3.7 Batch 3g — Feedback (4)
- [ ] 3.7.1 Sonner (the toast surface), ConfirmDeleteDialog, ErrorBoundary, ThemeToggle
- [ ] 3.7.2 Sonner + ConfirmDeleteDialog suppress animation under `motionTokens.respectReducedMotion: 'strict'`
- [ ] 3.7.3 Batch 3g gate
### 3.8 Batch 3h — Specialised (12)
- [ ] 3.8.1 Calendar, DatePicker, Select, MultiSelect, Combobox, SearchableSelect, Slider, InputOTP, Chart, MoneyInput, LanguagePicker, Item
- [ ] 3.8.2 `Chart` adds keyboard legend nav, `<title>` + `<desc>` for screen readers, `chart.render` span
- [ ] 3.8.3 `MoneyInput` consumes `formatCurrency` from `@suaveplan/i18n` formatters; locale-reactive
- [ ] 3.8.4 `LanguagePicker` reads from `@suaveplan/locale-data`; props Zod-validated via `LanguagePickerSchema`
- [ ] 3.8.5 Batch 3h gate
## 4. Wave 4 — Registry generation + deployment links
(Deployment to OVH owned by sibling `react-ui-registry-deployment`; this change owns the generation script + manifest schemas only.)
- [ ] 4.1 `scripts/build-registry.ts` — reads `src/components/<component>/<component>.tsx`, strips internal-only OTel hooks (replaced with no-op shims in the ejected source), rewrites `@suaveplan/react-i18n` imports to a configurable consumer alias (default `@/lib/i18n`), captures Tailwind/cva dependencies into `registryDependencies`, validates manifests against the shadcn registry JSON schema, emits to `registry/r/ui/<component>.json`
- [ ] 4.2 Theme presets: emit `registry/r/themes/<theme>.json` for `light`, `dark`, `highContrast`
- [ ] 4.3 Pack manifests: emit `registry/r/packs/<pack>.json` (used by sibling `enterprise-react-ui-pack-auth` to register its own packs against this generator)
- [ ] 4.4 `registry/registry.json` top-level index — consumer queries via `npx shadcn@latest search @suaveplan`
- [ ] 4.5 `scripts/build-registry.test.ts` — round-trip test: every generated manifest validates against the shadcn schema; ejected source compiles against a sample Vite project
- [ ] 4.6 Snapshot test: regenerated `registry/r/*.json` matches the checked-in snapshot byte-for-byte (CI gate per cross-cutting design §10.1 row "Registry build")
## 5. CSS
- [ ] 5.1 `src/styles/global.css` — Tailwind `@layer base` + the variables `<ThemeProvider>` writes (`--background`, `--foreground`, `--primary`, etc.). The CSS file is the *substrate*; the variable *values* come from the active `ThemePlugin.cssVariables`. Document required consumer import: `import '@suaveplan/react-ui/styles/global.css'`.
- [ ] 5.2 `src/styles/global.css.md` documenting the substrate vs theme distinction
## 6. Tests + E2E
- [ ] 6.1 `tests/integration.test.tsx` — full provider composition: mount `<ReactUiProvider i18n={i18n} themes={[light, dark, highContrast, dracula]} packs={[authPack]}>`; switch theme; switch locale; register / unregister a pack at runtime; assert no FOUC, no hydration mismatch
- [ ] 6.2 `tests/no-hardcoded-strings.spec-conformance.test.ts` — runs `scripts/lint/check-no-hardcoded-strings.ts` against `src/`, asserts zero diagnostics
- [ ] 6.3 `tests/translation-keys.spec-conformance.test.ts` — runs `scripts/lint/check-translation-keys.ts` against `src/`, asserts zero diagnostics
- [ ] 6.4 `e2e/a11y.spec.ts` — `@axe-core/playwright` against every Storybook story; zero-violation gate
- [ ] 6.5 `e2e/keyboard-nav.spec.ts` — Dialog, Drawer, Sheet, Command, DropdownMenu, ContextMenu, Menubar, SearchableSelect keyboard paths
- [ ] 6.6 `e2e/rtl.spec.ts` — every component's `WithI18n` story under `ar-AE`; mirror layout; chevrons swapped
- [ ] 6.7 `e2e/theme-switch.spec.ts` — Playwright performance trace; `setTheme` returns within 16ms (one frame)
- [ ] 6.8 `e2e/locale-switch.spec.ts` — locale-switch under 100ms (warm) / 500ms (cold); no Suspense flash on warm
## 7. CI gates (no-hardcoded-strings + translation-keys)
- [ ] 7.1 `scripts/lint/check-no-hardcoded-strings.ts` — Bun script using TypeScript compiler API; walks `packages/react/react-ui/src/` `.tsx`/`.ts`; flags JSX text nodes that aren't whitespace-only and aren't wrapped in `{t(...)}` / `{<Trans>...}` / hook call returning a translation; flags watchlisted attribute literals (`aria-label`, `aria-description`, `aria-roledescription`, `aria-valuetext`, `aria-keyshortcuts`, `placeholder`, `title`, `alt`); flags object-literal properties on the field-config watchlist (`label`, `message`, `description`, `tooltip`, `placeholder`, `errorMessage`, `helperText`, `validationMessage`) when value is a string literal; tests / spec / stories files exempt
- [ ] 7.2 `scripts/lint/check-translation-keys.ts` — collects every `t('...')` call across `src/`, cross-checks against the union of all locale JSON files; missing key fails the build with structured diff
- [ ] 7.3 Wire both into `bunx turbo lint` for `@suaveplan/react-ui` filter
- [ ] 7.4 Each script ships with `--help` text, exits non-zero on diagnostics, and emits `<file>:<line>:<col> <diagnostic>` line per violation per cross-cutting design §11.3
- [ ] 7.5 `scripts/lint/check-no-hardcoded-strings.test.ts` — fixtures cover JSX text node, allowed `{t(...)}`, banned `aria-label="x"`, allowed `aria-label={t('x')}`, banned object-literal `label: 'x'`, allowed `label: 'react-ui.foo.label'`
- [ ] 7.6 `scripts/lint/check-translation-keys.test.ts` — fixture with one valid key + one typo; asserts the typo fails
## 8. Storybook
- [ ] 8.1 Storybook 8 wired with `@storybook/addon-a11y`
- [ ] 8.2 Per-component stories per Wave 3 task §3.X.Y.5
- [ ] 8.3 `tests/stories.smoke.test.tsx` — `composeStories` smoke-renders every story; matches snapshot of accessible name + role tree
- [ ] 8.4 Storybook static build emits to `storybook-static/`; sibling `react-ui-registry-deployment` rsyncs it to OVH at `https://ui.suaveplan.com/storybook/`
## 9. Wiring and exports
- [ ] 9.1 `src/index.ts` — explicit named exports for every public symbol; NO `export *` per CLAUDE.md §10
- [ ] 9.2 `src/index.ts` imports `./locales/augmentation` as a side-effect-only entry so consumers' type system picks up the `ContributedTranslationKeys` augmentation automatically
- [ ] 9.3 Subpath `exports` map declared in `package.json` for every public module
- [ ] 9.4 `README.md` at the package root with: install, quickstart (`<ReactUiProvider>` 10-line example), every component listed with one example, every hook with one example, link to each co-located `.md`, locale-bundle override note, theme contract pointer, pack contract pointer
- [ ] 9.5 `tests/dep-graph.test.ts` — verifies no `i18next*`, no `react-i18next`, no `@suaveplan/react-translate` deps
- [ ] 9.6 Add to `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts` per CLAUDE.md "Package Lifecycle"
## 10. Pre-archive verification (the 5-gate triple, AGENTS.md §13)
- [ ] 10.1 `bun run check` — Biome 0/0/0 across the monorepo
- [ ] 10.2 `bunx turbo lint typecheck test --filter=@suaveplan/react-ui` — green
- [ ] 10.3 `bunx turbo test:coverage --filter=@suaveplan/react-ui` — 100/100/100/100 (`provider: 'istanbul'`)
- [ ] 10.4 `bunx turbo build --filter=@suaveplan/react-ui` — green
- [ ] 10.5 `bun scripts/openspec-conformance.ts @suaveplan/react-ui` — exit 0
- [ ] 10.6 `bun scripts/check-locale-completeness.ts` — green
- [ ] 10.7 `bun scripts/lint/check-no-hardcoded-strings.ts` — zero diagnostics
- [ ] 10.8 `bun scripts/lint/check-translation-keys.ts` — zero diagnostics
- [ ] 10.9 `bun scripts/build-registry.ts` — green; manifests match checked-in snapshot
- [ ] 10.10 Verify only one React version installed: `find . -path "*/node_modules/react/package.json" | head -5`
- [ ] 10.11 Banned-pattern scan (zero output): `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/react/react-ui/src/ --include="*.ts" --include="*.tsx"`
- [ ] 10.12 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any `<feature>/index.ts`
- [ ] 10.13 Test-runner import scan per AGENTS.md §13 step 4b — zero `from "vitest"` / `from "bun:test"` in `src/`
- [ ] 10.14 Missing-`.md` scan per AGENTS.md §13 step 5 — zero output
- [ ] 10.15 Bare-version scan per AGENTS.md §13 step 7 — zero output
- [ ] 10.16 Subpath-export count matches feature-module count per AGENTS.md §13 step 8
## 11. Pre-archive 7-row checklist (AGENTS.md §13 §"Quality bar")
- [ ] 11.1 Generics threaded; `expectTypeOf` assertions on `useThemedVariants` (base vs narrowed), `usePackComponent`, `<ThemePlugin>` augmentation, `<ComponentPack>` augmentation
- [ ] 11.2 Zod schemas at every public boundary (`ReactUiProviderPropsSchema`, `ThemePluginSchema`, `ComponentPackSchema`, `LocaleBundleSchema`, every component's props schema where the legacy package had one)
- [ ] 11.3 All nine error codes thrown from at least one production path AND covered by a `*.test.ts` assertion
- [ ] 11.4 Strict-mode tested: dev no-provider throw, prod soft-fail, theme-not-found, pack-collision, pack-version-mismatch warn, locale-bundle-load-failed
- [ ] 11.5 OTel histograms / counters / spans emitted from production paths and asserted with a real in-memory `MeterProvider` in `*.spec-conformance.test.ts`
- [ ] 11.6 Lifecycle dedup: pack `onRegister` / `onUnregister` fire exactly once per mount/unmount; locale-bundle dynamic import deduped via in-flight Promise
- [ ] 11.7 No retroactive tick — every box reflects work done in this change
## 12. Behavior-change propagation (AGENTS.md §13a)
- [ ] 12.1 `## Migration` section in `proposal.md`: lists every consumer-visible behaviour change vs the legacy proposal (component drops, theme contract introduction, branded `TranslationKey`, locale auto-registration). Currently zero genesis consumers, so the section's "search command" output is empty — but it MUST exist for future agents reading the archive
- [ ] 12.2 Sibling change `enterprise-react-i18n` amendment includes the `ContributedTranslationKeys` augmentation pattern this package uses
- [ ] 12.3 Sibling change `enterprise-react-forms-v2` documents the re-export of `Field` / `FieldGroup` / `FieldSet` / `InputGroup` from this package
## 13. Root-doc refresh + markdownlint (AGENTS.md §13b)
- [ ] 13.1 `bun scripts/folder-reorg/generate-manifest.ts` — refreshes `MANIFEST.md`
- [ ] 13.2 `bun scripts/generate-missing.ts` — refreshes `MISSING.yml`
- [ ] 13.3 Hand-edit `STATUS.md`: move `@suaveplan/react-ui` row from `Pending` → `Completed`
- [ ] 13.4 Hand-edit `SUMMARY.md`: per-package paragraph reflecting the shipped surface
- [ ] 13.5 `bunx markdownlint-cli2 --fix` across touched files; `git status` clean
- [ ] 13.6 Second pass `bunx markdownlint-cli2` — exit 0
## 14. Publish + archive
- [ ] 14.1 `bun scripts/publish.ts --package @suaveplan/react-ui`
- [ ] 14.2 Fold spec into `openspec/specs/react-ui/spec.md` (new capability folder)
- [ ] 14.3 Move `openspec/changes/enterprise-react-ui-v2/` → `openspec/archive/<yyyy-mm-dd>-enterprise-react-ui-v2/`
- [ ] 14.4 Move `openspec/changes/enterprise-react-ui/` → `openspec/archive/<yyyy-mm-dd>-enterprise-react-ui-superseded/`
- [ ] 14.5 Refresh `MANIFEST.md`, `STATUS.md`, `SUMMARY.md` to mark `@suaveplan/react-ui` shipped