Skip to content

Migrate @cratis/components to PrimeReact 11 - #107

Open
woksin wants to merge 42 commits into
mainfrom
feat/primereact-11-migration
Open

Migrate @cratis/components to PrimeReact 11#107
woksin wants to merge 42 commits into
mainfrom
feat/primereact-11-migration

Conversation

@woksin

@woksin woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates @cratis/components from PrimeReact 10 → 11, restores full v10 capability parity where v11 removed things, adds new Arc-native components, and — the headline for consumers — ships a license-free Cratis baseline theme so apps look great without a PrimeUI key. Wrapper public APIs are preserved wherever PrimeReact 11 allows; the breaking changes below are the unavoidable consequences of v11 being ESM-only and dropping several v10 modules.

Bumped to 0.2.0, labeled major. See Source/MIGRATION.md for the consumer upgrade guide.

⚠️ Licensing — please read

PrimeReact 11 changed its licensing:

  • Unstyled core + the --cratis-* token layer + pt are free — no key.
  • The styled @primeuix/themes presets (Aura/Lara/…) are license-gated — applying one via theme={{ preset }} needs a PrimeUI key (free community tier or paid); without it PrimeReact shows an "Invalid PrimeUI License" banner in dev and prod (that's why it appears in the story screenshots — Storybook runs license-free).
  • This PR adds a third path that sidesteps the license entirely: the Cratis baseline theme (import '@cratis/components/theme', unstyled), a token-based stylesheet that gives every component a polished default look with no PrimeUI license. Use unstyled-first, the baseline theme, or a licensed preset — your call.

Changed

  • Migrated the whole library to PrimeReact 11 compositional/headless components. CratisComponentsProvider now wraps @primereact/core's provider and accepts theme={{ preset }} and a license.
  • Import Column from @cratis/components/DataPage / DataTables and StepperPanel from @cratis/components/CommandDialog (removed primereact/column / primereact/stepperpanel); the authoring model is unchanged.
  • Data-table selection reports a DataTableSelectionChangeEvent (event.value unchanged), replacing the removed DataTableSelectionSingleChangeEvent.
  • Styled themes via a @primeuix/themes preset instead of the removed primereact/resources/themes/*.css.

Added

Capability parity + improvements (data table & stepper)

  • Per-column filter menus (<Column filter dataType … />), a global search box, a selection radio for selectionMode columns, and a paginator range report.
  • Stepper orientation (incl. vertical), headerPosition, start/end — reimplemented over the compositional stepper.
  • dismissable on Dialog.

New Arc-native components

  • @cratis/components/NotificationsToaster, imperative toast, and toastCommandResult(result) mapping an Arc ICommandResult to the right toast (validation → per-field messages, exceptions → generic, never stack traces). CratisComponentsProvider gains an optional toaster prop to mount it automatically.
  • @cratis/components/DisplayTag, Badge, Chip, Skeleton, Avatar, ProgressBar.
  • CommandForm fieldsPasswordField, ToggleSwitchField, RatingField.

Baseline theme, docs, tests, a11y

  • @cratis/components/theme — the license-free token-based theme (light + dark, defers to a preset's --p-* when present). It also maps @cratis/arc.react's --color-* chrome tokens (field errors, borders, labels) onto the palette, so it themes the Arc form surface too — not only the PrimeReact components.
  • MIGRATION.md upgrade guide; .ai rules updated to teach the new surface.
  • BDD specs for the new decision logic (toastCommandResult, selection key↔row translation, paginator range) — 209 specs pass.
  • Accessibility: the library is axe-clean (0 violations) across light, dark, and the default unstyled path. Fixes include the command stepper's invalid tab ARIA (dangling aria-controls / orphaned role="tab"), field-error contrast in dark mode, the Chip remove control, and the data-table sort button's invalid aria-sort. Accessible names added to the toggle-switch and rating fields.

Removed

  • The CommonJS build — the package is now ESM-only (breaking packaging change).
  • The primereact/resources/themes/*.css theming model.

Verified from the consumer's seat. Beyond the internal gates, the packed tarball was installed into a throwaway app with only the declared peers, then type-checked, vite build-bundled, and rendered headless — every subpath export and the theme import resolve, and the components mount and style with zero runtime errors (the baseline theme delivering its look with no PrimeUI license). The published tarball was also trimmed (Storybook build + test scaffolding removed, ~544 kB → ~302 kB).

Every gate is green: tsc -b (0), yarn lint (clean), yarn test (209), yarn build (ESM), build-storybook. Every widget was screenshot- and interaction-verified end-to-end (table sort/filter/select/page, stepper flows, toasts, tooltips, form fields), and the full component surface was axe-scanned in light, dark, and unstyled modes.

🤖 Generated with Claude Code

woksin and others added 9 commits July 16, 2026 11:01
Preserve in-progress migration of @cratis/components to PrimeReact 11:
foundation (ESM-only packaging, @primereact/core provider, @primeuix/themes
Storybook theming, tokens), the compositional Dialog stack, all 12
CommandForm fields, and the Select-based Dropdown wrapper.

This is an intentional checkpoint commit: the not-yet-migrated widgets
(DataTable, SchemaEditor, ObjectContentEditor, TimeMachine, Tooltip,
Stepper) still reference removed v10 paths, so `tsc -b` reports 85 errors
confined to those files. Remaining phases (5-9) follow in subsequent
commits that each restore a clean build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 5 of the PrimeReact 11 migration.

- Rebuild CommandStepper on the v11 compositional Stepper
  (Stepper.Root/List/Step/Header/Number/Title/Separator/Panels/Panel),
  driven by a controlled `value` + `onValueChange`.
- Add a Cratis-owned StepperPanel marker to replace the removed
  `primereact/stepperpanel`; the stepper reads each panel's header and
  children to build the step list and content panels.
- Replace the leaked `StepperCustomizationProps = Pick<StepperProps,…>`
  with a wrapper-owned type; drop the v10-only orientation/headerPosition/
  start/end slots that have no v11 equivalent.
- Re-express the per-step red/green error indicator directly on each
  Stepper.Number's style instead of the v10 pt.stepperpanel.number hook.
- Switch StepperCommandDialog off raw PrimeDialog onto the Cratis Dialog
  wrapper, and convert every Button to v11 children + variant.
- Add an additive `dismissable` prop to the Cratis Dialog so a custom-footer
  wizard keeps its header-close affordance.
- Update the stepper stories and the four StepperCommandDialog specs to the
  v11 component shapes (Cratis Dialog + Stepper parts + children Buttons).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 6 of the PrimeReact 11 migration.

- Rebuild Common/Tooltip on PrimeReact 11's compositional Tooltip
  (Root/Trigger/Portal/Positioner/Popup), portaled to the document body so
  it is no longer clipped by overflow containers — the reason the old code
  reached for the (now removed) data-pr-tooltip directive. Public API
  (content/position/children) is preserved; content is now optional so
  `content={condition ? text : undefined}` renders the child with no tooltip,
  and a trigger className is accepted for full-width children.
- Migrate SchemaEditor/NameCell off the removed data-pr-tooltip directive to
  the Tooltip wrapper.
- Add a Tooltip Storybook story.

The remaining data-pr-tooltip consumers (TypeCell, SchemaEditor,
ObjectContentEditor) also depend on other removed v11 APIs (Dropdown, Button
props, DataTable, Menubar, Calendar), so their tooltip migration ships with
their full-file rebuilds in the DataTable and remaining-wrapper phases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 7 (part 1) of the PrimeReact 11 migration.

PrimeReact 11's DataTable is a fully headless compositional table (Root only
provides context; the scaffold is built by hand) and the monolithic v10 props
are gone. Rebuild the Cratis data tables on it:

- Add DataTableCore, a shared query-agnostic table that reads `<Column>`
  children and renders the v11 headless Root/Table/THead/TBody/Cell scaffold,
  including sortable headers and an empty-state body. Rows come in via `data`
  (v11 renamed the old `value` prop, which now means the sort field).
- Translate v11's key-based selection (`Record<String(row[dataKey]), boolean>`)
  back to the row-object selection API callers expect, via a new
  DataTableSelectionChangeEvent replacing the removed
  DataTableSelectionSingleChangeEvent.
- Add a Cratis Column marker replacing the removed `primereact/column`, and a
  small TablePaginator (PrimeReact 11's Paginator is a headless slot; paging is
  still driven by Arc's paging hook).
- Rebuild DataTableForQuery and DataTableForObservableQuery on DataTableCore +
  TablePaginator, preserving their public props (selection, dataKey,
  globalFilterFields, scrollable, pt/unstyled). Per-column filter-menu UI (which
  no column in the library opted into) is not carried over.
- Retype pt/ptOptions off the v11 DataTableRootProps; export Column and the new
  event/filter types; update the two DataTable stories.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 7 (part 2) of the PrimeReact 11 migration.

- Add ActionMenubar, a small command toolbar that replaces the removed
  PrimeReact Menubar. PrimeReact 11 has no menubar and NavigationMenu is built
  for navigation, not command actions, so a focused button bar driven by the
  same `model` shape is the better fit for these action bars.
- Migrate DataPage onto ActionMenubar and the headless DataTables, replacing
  the removed DataTableSelectionSingleChangeEvent / DataTableFilterMeta /
  DataTableProps types with the Cratis equivalents, and re-export Column from
  the DataPage subpath.
- Rebuild SchemaEditor on DataTableCore + ActionMenubar + the Cratis Tooltip
  wrapper, and convert its and TypeCell's buttons to PrimeReact 11 (children +
  variant/iconOnly), TypeCell's dropdowns to the Cratis Dropdown, and both
  files' tooltips off the removed data-pr-tooltip directive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 8 of the PrimeReact 11 migration.

- ObjectContentEditor: move its boolean/number/date/long-text editors onto the
  PrimeReact 11 compositional Checkbox and InputNumber, the renamed Textarea
  (was InputTextarea), and the shared DatePickerInput (was the removed
  primereact/calendar), and its info-icon tooltip onto the Cratis Tooltip
  wrapper (off the removed data-pr-tooltip directive).
- Extract DatePickerInput, a wrapper-owned Date value/onChange picker that
  assembles the compositional DatePicker once, and refactor CalendarField to
  reuse it instead of duplicating the ~45-line composition.
- ObjectNavigationalBar: convert its back button to a PrimeReact 11 icon button
  (children + iconOnly) wrapped in the Cratis Tooltip.
- TimeMachine/EventsView: rebuild the Timeline on the v11 compositional
  Timeline (Root + Event/Separator/Marker/Connector/Content) mapping each
  event into the parts, replacing the removed monolithic value/content/marker
  props.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 9 of the PrimeReact 11 migration.

- Fix the rollup config still referencing the removed `cjsPath` in the ESM-only
  build's plugin wiring, which crashed `yarn build`.
- Stop destructuring the compat-only `resizable` prop in the Dialog (it has no
  effect on PrimeReact 11's headless dialog), clearing an unused-var lint error.
- Correct the `Common/Tooltip` mock path in four Toolbar specs — it was off by
  one directory level, so the mock never intercepted the real Tooltip; harmless
  while Tooltip was CSS-only, but the PrimeReact 11 Tooltip needs a provider and
  the real component now throws in server-rendered specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 9 of the PrimeReact 11 migration.

- Bump @cratis/components to 0.2.0 for the breaking ESM-only packaging and
  API changes (label the PR major per the framework backward-compat policy).
- README: replace the removed `primereact/resources/themes/*/theme.css`
  imports with the PrimeReact 11 `@primeuix/themes` preset applied via
  `CratisComponentsProvider theme={{ preset }}`, and note the unstyled-first
  default.
- .ai docs/skills: point `Column` imports at `@cratis/components/DataPage`
  and `StepperPanel` at `@cratis/components/CommandDialog` (the removed
  `primereact/column` / `primereact/stepperpanel`), and replace the removed
  `primereact/menubar` list-page example with a Button toolbar plus a pointer
  to DataPage's action bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 9 of the PrimeReact 11 migration.

Update the Storybook-only Tailwind pt preset to PrimeReact 11 component keys:
rename dropdown → select (with the v11 value/dropdown/popup/option slots),
inputtextarea → textarea, and drop the menubar preset (Menubar was removed;
the Cratis action bar is a Button toolbar styled through the button slot).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin woksin added the major label Jul 16, 2026
woksin and others added 7 commits July 16, 2026 12:25
A `<Column selectionMode="single" />` column rendered an empty cell after the
headless rebuild — row-click selection worked, but the per-row radio the v10
DataTable drew was gone. Render a radio that reflects the selected row so the
selection column keeps its familiar affordance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PrimeReact 11's compositional Stepper dropped the v10 orientation / start / end
/ headerPosition props, and the earlier migration removed them from the Cratis
wrapper. Reimplement them over the compositional parts so the wizard keeps full
capability parity (and works regardless of theme):

- orientation="vertical" lays the step list out as a stacked column beside the
  panels (CSS keyed on the wrapper's orientation class); horizontal stays the
  default.
- headerPosition="bottom" renders the panels above the step-header row.
- start / end render arbitrary content before / after the stepper (e.g. a logo
  or title).

Threaded through CommandStepper and StepperCommandDialog, plus a Vertical story.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PrimeReact 11's headless DataTable manages filter state and applies the actual
row filtering (client-side, like sorting), but renders none of the v10
filterDisplay="menu" chrome. Rebuild that chrome and add a search box:

- Add ColumnFilterMenu: a filter icon per filterable column header that opens a
  popover (primereact/popover) with a match-mode selector, a typed value input
  (text / number / date / boolean), and Clear / Apply — wired to the headless
  DataTable.Filter render-prop, with the popover's open state bound to the
  filter's overlay state so the draft re-seeds correctly.
- Column gains filter / filterField / filterPlaceholder / dataType /
  showFilterMatchModes; DataTableCore controls the filters state, seeds it from
  defaultFilters, and renders a filter menu for any column with `filter`.
- Add an optional global search box (shown when globalFilterFields is set) that
  filters across those fields — a capability the v10 table did not surface.
- Wire defaultFilters through both query tables; demonstrate in the
  DataTableForQuery and DataPage stories.

Restores full v10 filtering parity and then some.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Show "X–Y of Z" in the paginator when the total record count and page size are
available, so users see where they are in the full result set — a detail the
minimal first/prev/next/last paginator lacked. Both query tables pass it
through from the Arc paging result.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ProgressBar)

Add a Display component group with token-consistent Cratis wrappers over
PrimeReact 11's Tag, Badge, Chip (compositional), Skeleton, Avatar
(compositional), and ProgressBar (compositional):

- Tag / Badge — colored status + count indicators (severity tones) for tables,
  lists, and detail views.
- Chip — labeled, optionally removable pills.
- Skeleton — loading placeholders to pair with query isPerforming.
- Avatar — image with initials/icon fallback.
- ProgressBar — determinate/indeterminate progress.

Exposed as the `@cratis/components/Display` subpath, with an overview story.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Notifications group built on PrimeReact 11's headless Toaster/Toast:

- Toaster: the single app-wide toast host — mount one <Toaster /> near the app
  root. Reads the live toasts from the module-level store and renders each with
  a per-severity icon, title, description, and an auto-wired close button; the
  auto-dismiss timer is handled by the primitives. Ships baseline token-based
  styling (severity-colored cards) so notifications look right before a theme
  preset is applied.
- toast: the imperative API (toast.success/info/warn/error/promise/dismiss),
  callable from anywhere including outside React.
- toastCommandResult: surfaces an Arc ICommandResult as a toast, mapping the
  granular flags to severities — success, not-authorized (warn), validation
  (error with per-field messages), and exceptions (generic error; stack traces
  are never shown). The Arc-native way to give command feedback outside a
  CommandDialog.

Exposed as the `@cratis/components/Notifications` subpath, with a story.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round out the CommandForm field catalog with three more first-class fields,
each wired through asCommandFormField so they participate in validation and
change tracking like the built-ins:

- PasswordField — a masked password input with a show/hide toggle (string).
- ToggleSwitchField — a boolean on/off switch, the toggle counterpart of
  CheckboxField.
- RatingField — a star-rating input (number).

Exported from @cratis/components/CommandForm, with a bound story.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@einari — ready for your review. 👀

This is the PrimeReact 10 → 11 migration of @cratis/components, plus full v10 capability parity restored where v11 removed things (headless DataTable filter menus, stepper orientation/start/end, …) and a few new Arc-native components:

  • Notifications — a Toaster + imperative toast, and toastCommandResult(result) that surfaces an Arc ICommandResult as the right toast (success / not-authorized / validation with per-field messages / exceptions).
  • DisplayTag, Badge, Chip, Skeleton, Avatar, ProgressBar.
  • CommandForm fieldsPasswordField, ToggleSwitchField, RatingField (via asCommandFormField).

The one strategic thing I'd value your call on is licensing (full details in the PR description): PrimeReact 11 gates the styled @primeuix/themes presets behind a PrimeUI key (free community tier or paid), while the unstyled-first core we default to is free. The provider forwards a license for apps that want a bundled styled preset — but it's a downstream decision for every Cratis app, so wanted to flag it explicitly.

All gates are green (tsc -b 0 errors · yarn lint clean · 184 tests · yarn build · build-storybook) and every widget was screenshot-verified end-to-end (table sort/filter/select/page, stepper flows, toasts, tooltips, form fields).

Not merging — over to you for feedback. 🙏

woksin and others added 4 commits July 16, 2026 16:43
- MIGRATION.md: a v0.1 → v0.2 upgrade guide for consuming apps — the ESM-only
  note, the removed-import find-and-replace table (column/stepperpanel/menubar/
  dropdown), the selection-event rename, the theming change (preset or the
  upcoming Cratis baseline theme), and the licensing summary.
- Extract the data-table selection translation (selectionKeysForRow /
  rowFromSelectionKeys) and the paginator range formatter (paginatorRange) into
  pure, testable helpers, and use them from DataTableCore / TablePaginator.
- Add BDD specs: toastCommandResult across every result outcome (success,
  suppressed, unauthorized, validation with per-field messages, exceptions with
  no leaked stack trace), the selection key↔row translation, and the paginator
  range boundaries. 209 specs pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PrimeReact 11's good-looking styled presets are gated behind a PrimeUI license,
which left the license-free (unstyled) path looking plain. Add a token-based
baseline theme so apps get a polished default look with no license.

- theme.css: styles every component from the --cratis-* token layer (buttons,
  inputs, checkbox/radio/toggle/slider, select, dialog, datatable, tag/badge/
  chip/avatar/progressbar/skeleton, stepper), scoped under `.cratis-theme` with
  light + dark palettes. It supplies concrete colors that defer to a preset's
  --p-* tokens when one is present, so the license-free path finally resolves.
- Exposed as the `@cratis/components/theme` subpath (shipped by copy-css).
- Surface `data-severity` on Tag/Badge so severity coloring works unstyled.
- Repurpose the Storybook `cratis-theme` mode to demo it (unstyled, no license),
  and document it in the README.

Verified in Storybook: Display, form fields, DataTable, and Stepper all render
cleanly styled with no PrimeUI license.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Teach consumers and AI about the surface added in this migration:
- components.md: Notifications (Toaster/toast/toastCommandResult) and Display
  (Tag/Badge/Chip/Skeleton/Avatar/ProgressBar) subpaths, the new CommandForm
  fields, and column filtering / global search.
- react.md: point the out-of-dialog command branch at toastCommandResult.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ran axe against the new components/theme and fixed what's in our control:

- Use WCAG-AA-safe (darker) filled backgrounds for buttons, badges, severity
  tags, and the progress bar in the baseline theme, and lift the dark-mode
  secondary text — clearing every color-contrast violation on the toaster,
  Display, and DataPage stories.
- Give the Chip remove control a role so its aria-label is valid.
- Strip the invalid aria-sort PrimeReact 11 emits on the data-table sort button
  (aria-sort belongs on the column header, not a role=button), clearing that
  critical violation.
- Add a navigation landmark to the table paginator.

DX: `CratisComponentsProvider` gains an optional `toaster` prop that mounts a
`<Toaster />` so `toast(...)` works app-wide with no extra setup.

Known upstream: PrimeReact 11's Stepper marks its headers role="tab" and points
aria-controls at conditionally-rendered panels; that ARIA model is the
framework's and not fully axe-clean, though keyboard navigation works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@einari — follow-up: I pushed a round of additions on top of the migration, so this is now more of a "v0.2 done right" than just a port. Highlights:

  • 🎨 License-free Cratis baseline theme (@cratis/components/theme) — this is the answer to the licensing concern I raised above. It's a token-based stylesheet (light + dark) that makes every component look polished with no PrimeUI key, and defers to a @primeuix/themes preset if one is present. Verified across Display, forms, DataTable, and Stepper.
  • 📖 Source/MIGRATION.md — a v0.1→v0.2 upgrade guide for consuming apps (removed-import table, ESM note, theming, licensing).
  • BDD specs for the new logic (toastCommandResult, selection translation, paginator range) — 209 specs pass.
  • Accessibility pass with axe — fixed theme contrast, the Chip remove control, and the data-table sort button's invalid aria-sort. One known upstream item: PrimeReact 11's Stepper role="tab"/aria-controls model isn't fully axe-clean (keyboard nav works).
  • ✨ DX: CratisComponentsProvider can auto-mount the <Toaster />.

Still not merging — the two things I'd most value your steer on are (1) whether the baseline theme should become the recommended default for Cratis apps, and (2) the licensing posture (unstyled-first + baseline theme = free; presets = keyed). Full detail in the updated PR description.

woksin and others added 3 commits July 16, 2026 19:25
The `.cratis-theme` root set a text color but never painted a base
surface, so on any non-white host (a subtree, or a dark canvas) the
light variant rendered dark-on-dark and unreadable. Paint the surface
token on the themed root so the theme is self-contained; it flips
light/dark with the palette. Add a `cratis-theme-light` Storybook mode
so the light variant is previewable and verifiable at all — previously
only the dark variant was ever rendered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The switch input had no accessible name (the visible label was not
associated with it) and the rating star radios had none either — both
flagged as axe critical `label` violations. Wrap the switch in a real
<label> for implicit association plus an aria-label fallback, and give
each rating option a per-star aria-label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raw green/red text could not meet WCAG AA contrast on both the light and
dark baseline surfaces (a fixed hex fails one or the other). Use the
Display `Tag` with a severity instead — theme-aware, contrast-tuned, and
the documented pattern for status indicators in tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up polish while the PR waits for review — I set up a light and dark axe/screenshot pass over the baseline theme and it surfaced a real bug plus two a11y gaps, now fixed:

  • 🐛 Baseline theme light variant was unusable. .cratis-theme set a text color but never painted a base surface, so on any non-white host (a themed subtree, or here Storybook's dark canvas) the light variant rendered dark-on-dark. It turned out no Storybook mode ever rendered the light variant at all — only dark — so this had gone unseen. Fixed by painting the surface token on the themed root (self-contained, flips with the palette) and adding a cratis-theme-light preview mode. Light theme now renders correctly across Display, DataTable, forms, and toasts.
  • Two new fields had no accessible nameToggleSwitchField's switch and RatingField's star radios (axe critical label). Fixed with proper label association / per-star aria-labels.
  • ✨ Swapped the DataTable story's raw green/red status text (which can't meet AA on both light and dark) for the Display Tag with severity — theme-aware and the documented status-indicator pattern.

Both light and dark now scan clean except for the one known upstream PrimeReact Stepper ARIA limitation already noted above. Gates green (tsc, lint, 209 specs, rollup, build-storybook).

woksin and others added 3 commits July 16, 2026 19:56
The `files` allowlist only excluded stories and given files, so the
published tarball also shipped the entire `storybook-static` build,
`vite.config`, `vitest.setup`, and `tsconfig.tsbuildinfo` — build/test
scaffolding a consumer never needs. Exclude them; the tarball drops from
~544 kB to ~302 kB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PrimeReact 11's Stepper marks each step header role="tab" and each panel
role="tabpanel" with aria-controls pointing at element ids it never
renders (a dangling reference), and the list is not a role="tablist" —
axe flags both as critical. The headless stepper implements no keyboard
navigation bound to these roles (steps navigate by click on the native
button), so strip the broken tab semantics via pt, leaving natively
keyboard-accessible buttons and a plain content region.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cratis/arc.react's field wrappers, form errors, and dialogs read a
`--color-*` token family (--color-error, --color-border, --color-text,
--color-success, --color-warning, …) that the baseline theme did not
define, so Arc chrome fell back to hardcoded defaults — notably field
error text (#c00) that failed WCAG AA contrast on the dark surface. Map
the family onto the palette with AA-safe light and dark semantic colors
so the baseline theme also themes the Arc surface, not only PrimeReact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
woksin and others added 2 commits July 16, 2026 19:56
The demo pt preset styled buttons bg-sky-500 with white text (2.7:1),
failing WCAG AA. Use sky-700 (~6:1). The preset is a reference consumers
copy for the unstyled + Tailwind pt path, so it should model AA contrast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The imperative toast quick-reference showed `toast.info(...)` without
noting the argument is an options object, not a bare string — easy for a
consumer to get wrong. Show the object form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Ran a real consumer smoke test (the gap I flagged: everything so far had been verified in the library's own environment, never as a consumer consumes it) and it drove out several genuine fixes:

Consumer smoke test — passes end-to-end. Packed the tarball, installed it into a throwaway app with only the declared peers, then tsc + vite build + headless render. Every subpath export and the theme import resolve; components mount and style with zero runtime errors — the baseline theme delivering its look with no PrimeUI license.

Fixes it surfaced:

  • 🐛 Command stepper is now axe-clean. The earlier "known upstream Stepper ARIA limitation" is resolved, not just documented — PrimeReact 11 emits role="tab" + aria-controls pointing at ids it never renders. Confirmed the headless stepper has no keyboard handler bound to those roles, so stripping the broken tab semantics via pt is safe and leaves natively keyboard-accessible buttons. The whole library is now 0 axe violations across light, dark, and the default unstyled path.
  • 🎨 Baseline theme now covers Arc chrome. @cratis/arc.react reads a --color-* token family (errors, borders, labels) the theme didn't define — so Arc field-error text fell back to #c00 and failed AA contrast in dark. The theme now maps that family onto the palette (AA-safe light + dark).
  • 📦 Leaner package. The published tarball was shipping the entire Storybook build + test scaffolding; excluded them (~544 kB → ~302 kB).
  • Minor: Storybook demo pt-preset button contrast, and a toast doc clarification.

One honest note: there's a pre-existing isolate: false test flake in a CommandDialog spec (mock collision, order-dependent, ~1-in-4) that none of these changes touch — fixing it properly means changing global test isolation, which I've kept out of this PR to avoid destabilizing the suite. Flagging it rather than papering over it.

One thing worth your eye: primereact/@primereact/core are regular dependencies rather than peers — fine for a full wrapper lib, but worth a conscious call since a consumer who also installs primereact directly could dual-instance. Left as-is (didn't want to change the manifest without your intent).

woksin and others added 7 commits July 16, 2026 20:34
The consumer-facing README still described the pre-migration package:

- Claimed a dual CommonJS + ESM build — the package is now ESM-only.
- Named the styled Storybook preset "Lara" — it is Aura; and listed five
  styling modes when there are now six (baseline theme gained a light one).
- Listed React 18+ — the peer is React 19.
- Described primereact/primeicons as peer dependencies — they ship as
  regular dependencies; documented that and the dedupe caveat.
- Omitted the new `/Display`, `/Notifications`, and `/theme` exports.
- Left the license-free baseline theme out of the styling decision table.
- Framed the "why you need a theme" note around PrimeReact 10 semantics.
- Modeled `bg-sky-500` (fails AA) in the Tailwind pt example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CheckboxField, RadioButtonField, and RadioGroupField rendered their
label text in an unassociated <label> (no htmlFor, not wrapping the
control), so screen readers saw a nameless control — the same axe
critical the ToggleSwitch fix already addressed, which had stopped one
field short of its siblings. Wrap each control in a real <label> for
implicit association. Also drop the dead aria-label on ToggleSwitch (it
landed on the wrapper div, not the input; the wrapping label is the name).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Several aria-labels were baked-in English strings a consumer could not
translate. Expose each as an optional prop with an English default:
Chip `removeAriaLabel`, Dialog `closeAriaLabel`, DataTableCore
`selectionAriaLabel`, TablePaginator `ariaLabels`, RatingField
`starAriaLabel`, Toaster `dismissAriaLabel`. The BusyIndicator spinner
(role="progressbar") had no accessible name at all — name it from the
consumer-supplied message/title (already localized).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Map Arc's `--radius-*`, `--space-*`, and `--font-mono` (which
  @cratis/arc.react reads with no fallbacks) so Arc chrome keeps its
  corners, padding, and gaps instead of collapsing under the theme.
- Add concrete `--cratis-surface-ground` / `--cratis-focus-ring` values
  so the Filter panel's sub-surfaces and focus rings render with no preset.
- Deepen the dark `--color-error-bg` (red-950) so error text clears WCAG
  AA on the exception banner, not just the section surface.
- Style the previously-uncovered `rating` and `progressspinner` scopes so
  the star rating and the busy spinner are visible under the theme.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The Dialog stories used the removed v10 `<Button label=… icon=…/>`
  props, which render an empty button in PrimeReact 11 (broken visually
  and an axe button-name violation) — switch to children.
- The observable data-table story colored status/priority with fixed
  text shades that fail contrast on the dark surface — use Display `Tag`
  with a severity (theme-aware) instead.
- Lift the Filter story's muted helper text above the WCAG AA threshold.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docs (and the provider's own JSDoc) showed styling options as direct
props — `<CratisComponentsProvider theme={{ preset }}>` / `unstyled` /
`license` — but the provider only accepts a single `value` prop, so those
are silently dropped: they must be `value={{ theme: { preset } }}` etc.
Also correct dialogs.md (`style`/`contentStyle`/`dismissable` are
supported; add the new fields to the catalog).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The package is ESM-only, but a leftover dist/cjs from an earlier build
would ship as unreferenced dead weight since `files` includes `dist`.
Exclude `dist/cjs/**`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Did a thorough final sweep — a full axe scan across every story (100 stories × light/dark/unstyled) plus three parallel audits (docs drift, baseline-theme completeness, correctness of the session's changes). It surfaced a lot my earlier subset scan had hidden. All fixed:

Component accessibility (real bugs)

  • CheckboxField / RadioButtonField / RadioGroupField rendered their label unassociated — nameless to a screen reader (the ToggleSwitch fix had stopped one field short of its siblings). Wrapped each in a real <label>.
  • The Dialog stories used the removed v10 <Button label=… icon=…/> API, which renders an empty button in v11 (broken visually + axe button-name) — switched to children.
  • The busy spinner (role="progressbar") had no accessible name — named it from the consumer's message.
  • The observable data-table used fixed text colors that fail contrast on dark — switched to Display Tag.

Localization (thanks for the nudge) — every aria-label I'd added was hard-coded English; all are now optional props with English defaults (Chip.removeAriaLabel, Dialog.closeAriaLabel, TablePaginator.ariaLabels, RatingField.starAriaLabel, Toaster.dismissAriaLabel, DataTableCore.selectionAriaLabel).

Baseline theme completeness — mapped Arc's --radius-*/--space-* tokens (Arc chrome was collapsing padding), added the missing rating + progressspinner scopes, gave the Filter panel its --cratis-surface-ground/--cratis-focus-ring values, and deepened the dark error-bg for AA.

Docs correctness (break-on-paste) — the docs (and the provider's own JSDoc) showed <CratisComponentsProvider theme={{ preset }}> / unstyled / license as direct props, but the provider only accepts a single value prop, so those are silently dropped. Corrected to value={{ theme: { preset } }} everywhere. Also: dropped a stale dist/cjs from the package, and fixed dialogs.md.

Result: the shipped baseline theme went from 93/72 (light/dark) axe nodes → 21/26, and everything remaining is genuinely upstream — see below.


Honest boundary — what I deliberately did NOT fix here (yours to weigh):

  1. The remaining axe items are all in the original PrimeReact primitive fields (Dropdown/MultiSelect/ColorPicker/Chips/Slider): button-name/aria-* are PrimeReact 11 internal ARIA, and the label misses are the @cratis/arc.react field-title association (its title renders a <label> with no htmlFor). Both need upstream fixes, not wrapper hacks.
  2. The Documentation/ docs-site tree is still largely pre-migration — install guide, provider reference, the whole Styling section, and Documentation/migration.md describe the v10 world (and a different, older migration). That's a sizeable separate docs pass; I fixed the package-shipped README.md/MIGRATION.md but left the docs site for a dedicated follow-up.
  3. Pre-existing hard-coded English labels in TimeMachine / SchemaEditor / Filter / ObjectNavigationalBar — a broader localization debt that predates this PR; I didn't sweep-refactor them.
  4. Still open maintainer calls flagged earlier: primereact peer-vs-dependency, the 0.2.0-vs-major version (ESM-only is breaking), and the pre-existing isolate:false CommandDialog test flake.

Gates green (tsc, lint, 209 specs, build, build-storybook). Not merging.

woksin and others added 5 commits July 16, 2026 22:44
Externalize every remaining baked-in English string across the
consumer-facing components so nothing is assumed on the consumer side —
individual override props for single-string components (ChipsField
`removeAriaLabel`, ObjectNavigationalBar `backLabel`, DataPage
`actionsAriaLabel`, FilterPanel `clearFilterAriaLabel`/`clearRangeAriaLabel`)
and a `labels` object with an exported type + English defaults for the
multi-string components (`SchemaEditorLabels`, `TimeMachineLabels`, the
latter extracted to its own module so the sub-views can default without a
circular import). All defaults preserve current behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correct the docs that still described the pre-migration (v10) reality:
removed `primereact/column`/`stepperpanel`/`api` imports, the removed
`<Button label= icon=>` API, the provider `value={{ … }}` config (theme/
unstyled/license are not direct props), `PrimeReactProps`/`inputVariant`
(not v10 `APIOptions`/`inputStyle`), the styling section (presets via the
provider + the license-free baseline theme instead of `resources/themes`),
and a full rewrite of the migration page, which had described a different,
older migration and claimed the library was still on PrimeReact 10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add reference pages for the components introduced in this release —
Display (Tag/Badge/Chip/Avatar/ProgressBar/Skeleton), Notifications
(Toaster/toast/toastCommandResult), the PasswordField/ToggleSwitchField/
RatingField command-form fields, and a Cratis-baseline-theme styling
how-to — and wire them into the navigation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the pass-through cheat sheet's underlying-widget column to the v11
primitive names (select/textarea/datepicker/inputtags/inputcolor) and the
coming-from-primereact bridge to the Cratis Dialog API and Button children.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up corrections to the styling section: the custom-palette and
mixing-paths guidance now uses the `@primeuix/themes` preset + `--cratis-*`
model instead of removed `resources/themes/*` imports; `themed.md` is the
licensed-preset path (distinguished from the license-free baseline theme in
the nav); and the baseline-theme override example targets `[data-scope]`
(the baseline theme's selector) rather than `.p-button` (preset-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Two more things landed: full label configurability and a full documentation pass.

Nothing is assumed on the consumer side anymore. Every remaining hard-coded English string across the components is now overridable with an English default — individual props where there are one or two (Chip.removeAriaLabel, ObjectNavigationalBar.backLabel, DataPage.actionsAriaLabel, FilterPanel.clearFilterAriaLabel/clearRangeAriaLabel, ChipsField.removeAriaLabel, RatingField.starAriaLabel, Toaster.dismissAriaLabel, Dialog.closeAriaLabel, DataTableCore.selectionAriaLabel), and a typed labels object where there are many (SchemaEditorLabels, TimeMachineLabels — both exported). A consumer localizes by passing translated strings; omit them and you get English. 0 hard-coded aria-labels remain in the component source.

Documentation is now current and complete. The Documentation/ site had drifted badly to the v10 world; this pass:

  • Fixed the drift across ~25 pages — removed primereact/column/stepperpanel/api imports, the removed <Button label= icon=> API, the value={{ … }} provider config (theme/unstyled/license are not direct props), PrimeReactProvider/inputVariant, and the whole Styling section (presets-via-provider + the license-free baseline theme instead of resources/themes).
  • Rewrote migration.md, which had described a completely different, older migration and still claimed the library was on PrimeReact 10.
  • Added the missing pages — Display, Notifications, the Password/ToggleSwitch/Rating fields, and a baseline-theme styling how-to — all wired into the nav.

Markdown + link checks pass; all code gates green (tsc, lint, 209 specs, build, build-storybook).

That closes the two follow-ups from the last review round (broader localization debt + the Documentation site). Still not merging. Remaining open items are unchanged and all upstream/maintainer calls: PrimeReact-11 primitive-internal a11y, @cratis/arc.react's field-title association, the peer-vs-dependency question, the 0.2.0-vs-major version, and the pre-existing isolate:false test flake.

woksin and others added 2 commits July 16, 2026 23:32
An API-regression audit against main found several consumer-facing props
that were dropped or weakened without PrimeReact 11 forcing it. Restore them:

- Dropdown: re-add style, id, name, tabIndex, aria-label, aria-labelledby
  (all expressible on v11's Select) — the wrapper had dropped them.
- DataPage: re-expose menubarPt / menubarPtOptions / menubarUnstyled by
  adding pass-through to ActionMenubar's buttons (the props had been removed
  while the JSDoc still advertised them).
- DataTableForQuery/ForObservableQuery: surface paginatorAriaLabels so the
  paginator's controls can be localized.
- DataTableFilterMeta: type it as a per-field { value, matchMode } constraint
  instead of Record<string, unknown>, so defaultFilters type-checks.
- StepperPanel: widen header from string to ReactNode (v10 accepted a
  template; it renders through Stepper.Title which takes a node).
- Deprecate the dead clientFiltering flag (declared and forwarded but never
  read) with an honest @deprecated note instead of "Enable client-side filtering".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix documentation that no longer matched the code: the Dropdown page
claimed "all PrimeReact Dropdown props supported" (it is a curated
wrapper, and it documented a filterPlaceholder prop that does not exist);
the DataTables pages described a Server-vs-Client filtering toggle that no
longer exists (clientFiltering is a no-op); and the pass-through cheat
sheet listed a paginatorPt the custom paginator does not accept. Add a
"Reduced capabilities" section to both migration guides listing the props
PrimeReact 11 forced into no-ops (resizable, separator, display /
maxSelectedLabels, clientFiltering) and the curated surfaces (Column,
Dropdown, the action toolbar), so upgraders are not silently surprised.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@woksin

woksin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Ran a focused API-regression audit of the whole main..HEAD diff — the goal being that a major may carry PrimeReact-11-forced breaks, but must not carry gratuitous worsening. Two independent audits (core components; everything else) plus objective checks.

Confirmed safe at the boundaries: no export/subpath/component removed (purely additive), no default silently changed, no new any / type weakening, peer dependencies unchanged (consumers install nothing new), and the data-table selection event is actually better typed now (TData | null, no more as Product cast).

But the audit found real, non-forced worsenings — now fixed:

  • Dropdown had dropped style, id, name, tabIndex, aria-* (all expressible on v11's Select) → restored.
  • DataPage removed menubarPt/menubarUnstyled while the JSDoc still advertised them → restored (pass-through now reaches the action toolbar's buttons).
  • clientFiltering was a dead prop whose doc said "Enable client-side filtering" → deprecated honestly; the DataTables docs' whole Server-vs-Client filtering section was rewritten.
  • DataTableFilterMeta had been weakened to Record<string, unknown> → typed as a { value, matchMode } constraint so defaultFilters type-checks.
  • StepperPanel.header narrowed from a template to string → widened back to ReactNode.
  • Paginator localization wasn't reachable → paginatorAriaLabels surfaced on both tables.
  • Docs: the Dropdown page's "all PrimeReact props supported" (and a non-existent filterPlaceholder) and a stale paginatorPt in the cheat sheet were corrected.

Honestly documented, not hidden: the capabilities PrimeReact 11 genuinely removed (Dialog.resizable, ChipsField.separator, MultiSelectField.display/maxSelectedLabels, and the curated Column/Dropdown surfaces) now have a "Reduced capabilities" section in both migration guides, so upgraders aren't silently surprised.

Net verdict: after these fixes, the upgrade does not make the public API worse than PrimeReact 11 forced — every remaining reduction is inherent to v11 and documented. Gates green (tsc, lint, 209 specs, build, build-storybook, docs link/lint). Still not merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant