29 KiB
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 statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. 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 itsproposal.mdheader pointing here. Move it toopenspec/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.mdandSUMMARY.mdto reflect@suaveplan/react-uiv2 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.mdandspecs/react-ui/spec.mdend-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-v2exits with no errors
1.2 Wave 0 — Component-list reconciliation
- 1.2.1 Run
npx shadcn@latest search @shadcnfrom 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.mdwith the final list and lock the count before any Wave 3 code lands - 1.2.5 Reconcile the
## Parity Checklisttable 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
expectTypeOfassertions 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/withpackage.jsonat0.1.0,tsconfig.lib.jsonextending@suaveplan/typescript-config/react-library.json,vitest.config.tsusingcreateTestConfig()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 theMessageParserimport 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
- Internal:
- 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, NOreact-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
exportsentry 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
ReactUiErrorand codes viadefineCodes("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
.mdfor the error class
2.3 Locale bundles
- 2.3.1 Author
src/locales/en-GB.jsonas the source-of-truth bundle. Single namespacereact-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 exercisezero / one / two / few / many / other),ru-RU.json(Slavic plurals) - 2.3.3
src/locales/schema.ts— ZodLocaleBundleSchemaderived fromen-GB.jsonkeys; every other locale validated against this schema at module load - 2.3.4
src/locales/index.ts— explicit named export ofreactUiBundles: Record<SupportedLocale, () => Promise<LocaleBundle>>andSUPPORTED_LOCALES = ['en-GB', 'zh-CN', 'ar-AE', 'cy-GB', 'ru-RU'] as const. NOexport *. - 2.3.5
scripts/check-locale-completeness.ts— Bun script asserting every key inen-GB.jsonexists in every other locale file; structured diff output on failure - 2.3.6
scripts/generate-translation-types.ts— Bun script generatingsrc/locales/augmentation.d.tsfromen-GB.jsonkeys; output module-augmentsContributedTranslationKeysper 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 againstLocaleBundleSchema - 2.3.8
src/locales/cy-plural.spec-conformance.test.ts— assertsIntl.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 bothbun testandvitest 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); ZodThemePluginSchema/ThemeTokenSetSchema/MotionTokenSetSchema - 2.4.2
src/theme/theme-provider/theme-provider.tsx— acceptsthemes,defaultTheme,storageKey,enableSystem,attribute = 'data-theme',scriptNonce. Injects an inline FOUC-prevention script into<head>before hydration (CSP-nonce-aware). Subscribes toprefers-reduced-motionand updates--motion-duration-*/--motion-easing-*CSS variables. Emitstheme.changedon the bus and incrementsreact_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 }; throwsREACT_UI.NO_PROVIDERoutside a<ThemeProvider>in dev, soft-fails to identity in prod - 2.4.4
src/theme/use-theme-tokens/use-theme-tokens.ts— returns the liveThemeTokenSetfor 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 mergesvariantOverrides[componentName]with the base cva config. Memoised per(themeName, componentName)tuple. Histogramreact_ui.themed_variants.resolve_duration_mson 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 implementingThemePlugin; each with sibling.mddocumenting tokens - 2.4.7
src/theme/theme-switcher/theme-switcher.tsx— headless render-prop mirror of<LanguageSwitcher>; readstheme.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,setThemehappy 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
.mdper implementation file
2.5 PackRegistryProvider + pack primitives
- 2.5.1
src/pack/pack.types.ts—ComponentPack,PackComponents(empty interface — augmented per-pack),PackContext; ZodComponentPackSchema - 2.5.2
src/pack/pack-registry-provider/pack-registry-provider.tsx— acceptspacks: ComponentPack[], optionalcomponentsfor runtime override per pack; throwsREACT_UI.PACK_ALREADY_REGISTEREDwhen two packs sharename. Calls each pack'sonRegister(ctx)in registration order,onUnregister(ctx)in reverse order. Emitspack.registered/pack.unregistered. Per-pack scoped React context. Forwards each pack'stranslationsto the active i18n instance viaaddResourceBundle(pack.name, pack.translations[locale]). - 2.5.3
src/pack/use-pack-component/use-pack-component.ts— generic-typedusePackComponent<TName, TComponent>(packName, componentName); throwsPACK_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 runtimecomponentsprop on<ReactUiProvider> - 2.5.5
src/pack/version-check/version-check.ts— semverpeerRangeagainst the runningreact-uiversion; mismatch logspack.version_mismatchwarning + incrementsreact_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
.mdper 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 lazyreactUiBundles[locale]()import on mount and passes the result toi18n.addResourceBundle('react-ui', bundle). Honours thereactUiOverridesprop documented in cross-cutting design §8.4. - 2.6.2 OTel span
react_ui.provider.mountwith 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): stringreadingwindow.matchMedia('(prefers-reduced-motion: reduce)'); SSR guard; sibling.md; tests - 2.7.3
src/hooks/use-mobile/use-mobile.ts—useMediaQuery('(max-width: 768px)')viauseState + useEffect; sibling.md; tests - 2.7.4
src/hooks/use-toast/use-toast.ts— toast queue backed bysonner; emitstoast.showspan 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— producessrc/locales/augmentation.d.tsmatchingen-GB.jsonexactly; 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,asChildsupport,useThemedVariants(componentName)for variant resolution,useTranslation('react-ui')for any default user-visible copy, every prop carrying user text typed asTranslationKey(NEVERstring) - 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 everyit()per CLAUDE.md - 3.X.Y.5
<component-name>.stories.tsx— Default + Playground + Variants + States + WithI18n (5 locales incl. RTLar-AE) + WithTheme (4 themes incl.draculaTheme) + A11y + Composition (where composable) - 3.X.Y.6
index.tsbarrel using explicit named re-exports (CLAUDE.md §10; noexport *) - 3.X.Y.7 Add the component's translation keys to
src/locales/en-GB.jsonfirst, then port to the other four locales - 3.X.Y.8 Re-run
scripts/generate-translation-types.ts; commit the regeneratedaugmentation.d.ts - 3.X.Y.9 Re-run
scripts/lint/check-no-hardcoded-strings.tsandscripts/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-uigreen
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-formswithout 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.tsxMUST 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.emptyMessageprop acceptsTranslationKey | ReactNodeper 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
Paginationswaps chevron icons underuseDirection() === 'rtl' - 3.5.3
Commandemitscommand.selectspan event +react_ui.command.selected_total - 3.5.4
Sidebarextracted 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
Chartadds keyboard legend nav,<title>+<desc>for screen readers,chart.renderspan - 3.8.3
MoneyInputconsumesformatCurrencyfrom@suaveplan/i18nformatters; locale-reactive - 3.8.4
LanguagePickerreads from@suaveplan/locale-data; props Zod-validated viaLanguagePickerSchema - 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— readssrc/components/<component>/<component>.tsx, strips internal-only OTel hooks (replaced with no-op shims in the ejected source), rewrites@suaveplan/react-i18nimports to a configurable consumer alias (default@/lib/i18n), captures Tailwind/cva dependencies intoregistryDependencies, validates manifests against the shadcn registry JSON schema, emits toregistry/r/ui/<component>.json - 4.2 Theme presets: emit
registry/r/themes/<theme>.jsonforlight,dark,highContrast - 4.3 Pack manifests: emit
registry/r/packs/<pack>.json(used by siblingenterprise-react-ui-pack-authto register its own packs against this generator) - 4.4
registry/registry.jsontop-level index — consumer queries vianpx 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/*.jsonmatches 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 activeThemePlugin.cssVariables. Document required consumer import:import '@suaveplan/react-ui/styles/global.css'. - 5.2
src/styles/global.css.mddocumenting 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— runsscripts/lint/check-no-hardcoded-strings.tsagainstsrc/, asserts zero diagnostics - 6.3
tests/translation-keys.spec-conformance.test.ts— runsscripts/lint/check-translation-keys.tsagainstsrc/, asserts zero diagnostics - 6.4
e2e/a11y.spec.ts—@axe-core/playwrightagainst 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'sWithI18nstory underar-AE; mirror layout; chevrons swapped - 6.7
e2e/theme-switch.spec.ts— Playwright performance trace;setThemereturns 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; walkspackages/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 everyt('...')call acrosssrc/, 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 lintfor@suaveplan/react-uifilter - 7.4 Each script ships with
--helptext, 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(...)}, bannedaria-label="x", allowedaria-label={t('x')}, banned object-literallabel: 'x', allowedlabel: '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—composeStoriessmoke-renders every story; matches snapshot of accessible name + role tree - 8.4 Storybook static build emits to
storybook-static/; siblingreact-ui-registry-deploymentrsyncs it to OVH athttps://ui.suaveplan.com/storybook/
9. Wiring and exports
- 9.1
src/index.ts— explicit named exports for every public symbol; NOexport *per CLAUDE.md §10 - 9.2
src/index.tsimports./locales/augmentationas a side-effect-only entry so consumers' type system picks up theContributedTranslationKeysaugmentation automatically - 9.3 Subpath
exportsmap declared inpackage.jsonfor every public module - 9.4
README.mdat 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 noi18next*, noreact-i18next, no@suaveplan/react-translatedeps - 9.6 Add to
MANIFEST.mdviabun scripts/folder-reorg/generate-manifest.tsper 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"insrc/ - 10.14 Missing-
.mdscan 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;
expectTypeOfassertions onuseThemedVariants(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.tsassertion - 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
MeterProviderin*.spec-conformance.test.ts - 11.6 Lifecycle dedup: pack
onRegister/onUnregisterfire 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
## Migrationsection inproposal.md: lists every consumer-visible behaviour change vs the legacy proposal (component drops, theme contract introduction, brandedTranslationKey, 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-i18namendment includes theContributedTranslationKeysaugmentation pattern this package uses - 12.3 Sibling change
enterprise-react-forms-v2documents the re-export ofField/FieldGroup/FieldSet/InputGroupfrom this package
13. Root-doc refresh + markdownlint (AGENTS.md §13b)
- 13.1
bun scripts/folder-reorg/generate-manifest.ts— refreshesMANIFEST.md - 13.2
bun scripts/generate-missing.ts— refreshesMISSING.yml - 13.3 Hand-edit
STATUS.md: move@suaveplan/react-uirow fromPending→Completed - 13.4 Hand-edit
SUMMARY.md: per-package paragraph reflecting the shipped surface - 13.5
bunx markdownlint-cli2 --fixacross touched files;git statusclean - 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.mdto mark@suaveplan/react-uishipped