# 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/-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:` 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 `` 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 Promise>` 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 `` 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 `` 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 ``; 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(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 `` - [ ] 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 ` → → `. ~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 `.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 `.types.ts` — every public prop interface, no inline shapes (AGENTS.md §11) - [ ] 3.X.Y.3 `.md` — ≥200 words; Purpose + Features + Basic + Advanced + API + Implementation Notes - [ ] 3.X.Y.4 `.test.tsx` — public-API contract, RHF integration where applicable, callback firing, a11y attributes (`role`, `aria-*`), variant classes, theme-override flow under `` ; `{ timeout: 5000 }` on every `it()` per CLAUDE.md - [ ] 3.X.Y.5 `.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 `.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, `` + `<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