Ship the renderer-independent Components 4 foundation - #177
Conversation
|
@woksin @einari — this comment records why this major takes this direction, why now, what was proven, and what we are deliberately not claiming yet. Why this is not merely a renderer swapComponents 3 placed Cratis application behavior behind PrimeReact runtime, provider configuration, public types, pass-through slots, theme variables, DOM assumptions, and licensing. PrimeUI's published terms explicitly say that an internal wrapper/design system does not remove developer-seat requirements, and SDK/framework-style redistribution is OEM territory requiring separate clarification. That meant the commercial renderer was not actually an implementation detail. It was an invisible condition of using Arc-bound dialogs, fields, tables, pages, and notifications. The technical cost matched the licensing cost:
We shipped Components 3 stabilization first (#169, #171, #172 → v3.5.0) so correctness and accessibility were not held hostage by this major migration. The long-lived boundaryComponents 4 now owns:
React Aria remains internal for focus, overlays, collections, keyboard behavior, and internationalized dates. It is Apache-2.0 and style-free, but its types/classes/DOM are not the consumer contract. Native HTML is used when simpler. Arc and Why no TanStack or compatibility package nowTanStack Table is a good headless engine, but current Components tables do not yet need enough grouping/pinning/faceting complexity to justify another abstraction. #109 should extract query/table state into Arc React only after a second renderer or real consumer proves the contract. A Prime compatibility package is not published without written licensing/OEM clarity. Components 3 is the compatibility line while consumers migrate. Baseline appearance and stylingThe default UI is intentionally familiar to Components 2/3 users rather than looking like an unrelated redesign:
We intentionally differ where exact Lara reproduction would retain weaker contrast or semantics. Actions/statuses, focus rings, control boundaries, disabled state, forced colors, and reduced motion are stronger. The stylesheet contract is now real rather than aspirational:
Products can override unlayered CSS without specificity wars. Mixed Prime products receive explicit Cratis z-index controls while configuring Prime independently. Consumer evidenceCustom-token design systemA representative custom-token design system maps its canonical tokens directly to Its locale-aware Prime InputNumber remains an honest bounded Prime island until #175 supplies proven parity. We do not claim this archetype is fully migrated. Deeply customized canvas/compositor hostRepresentative Dialog, CommandDialog, Stepper, Toolbar, and stable-part usage compiles. Stable parts, state attributes, and product-owned compositor markers preserve deep customization without making product vocabulary part of the Components contract. Broad direct Prime and local canvas usage make this a staged migration, with existing obligations retained while those surfaces remain. Components 2/PrimeReact 10 applicationThis archetype can move directly to Components 4 while retaining PrimeReact 10 as a bounded direct-use island. It does not need an intermediate PrimeReact 11 migration. Components 3/PrimeReact 11 multi-entry application with an advanced table adapterThis archetype can start baseline-first with Components light/dark while retaining its separate PrimeReact 11 provider and application-owned advanced table adapter. Components does not force canvas-specific Toolbar composition onto ordinary page actions or claim that its default table implements advanced server state. VerificationThe exact candidate has:
What remains deliberately open
A future Vue/Svelte frontend should use Arc transport/generated-client contracts and framework-native state/components. We should extract cross-framework contracts only after another implementation proves them, not pre-abstract React details today. Requested review focus
Please treat renderer leakage, inaccessible behavior, undocumented migration edits, misleading compatibility, or a custom-design-system dead end as blockers. This PR should remain unmerged until you are both comfortable with the major and the RC pilot plan. |
|
Final review update: all latest checks on |
Additional licensing research — independent verification of the PrimeReact 11 consumer impactWe have done additional research to be certain about what PrimeReact 11 means for Components 3 consumers, prompted by downstream reports that apps on Components 3 show a license notice. Everything below was verified against primary sources: the published Verified findings
What this means
Recommended path forward
None of this blocks the PR; items 2–5 are documentation-scoped and could land as a follow-up commit on this branch or a fast-follow. The standing disclaimer applies: this summarizes public terms and shipped code behavior for architectural transparency; it is not legal advice. |
pre-stable subset) Implements the pre-stable subset of #174 for PR #177: fixes confirmed bypass defects, wires six new provider message groups (prop -> provider -> English fallback), and adds regression/precedence/sentinel specs. Framework/library profile change; every existing public prop and its precedence is preserved. Bypass fixes (Fix 1): - FilterPanel.tsx: header clear-button "title" hardcoded "Clear filter" / "Clear range" next to a correctly-resolved aria-label from the same prop; title now mirrors the same value. A searchable filter group with no own searchPlaceholder now falls back to the panel-level searchPlaceholder prop instead of a second hardcoded "Search..." literal. - SchemaEditor.tsx: the back-navigation Tooltip's content hardcoded "Navigate back" while the same button's aria-label correctly resolved labels.navigateBack; both now come from the same source. - RangeHistogramFilter.tsx: the bar tooltip's unit word ("...42 items") was a hardcoded, unoverridable English literal; added itemsLabel (default "items"). Dropdown (Fix 2): "Show options" (2 sites) and "Clear selection" (4 sites) now resolve pt override -> CratisComponentsProvider messages.dropdown -> English fallback across all six render paths (native/filtered x single/multiple), preserving per-instance pt overrides. New provider message groups (Fix 3), each documented with TSDoc and wired prop -> provider -> English fallback: - dialog (ok/cancel/yes/no/close) - resolved once in Dialog.tsx, so CommandDialog and StepperCommandDialog (which forward these props straight through) share the same precedence without duplicating it. - stepper (next/previous/submit) - CommandStepperContent (shared by CommandStepper and StepperCommandDialog) and StepperCommandDialog's own Cancel button (which reuses dialog.cancel). - notifications (dismiss/region) - Toaster. - dataTable (selectRow/search/searchAriaLabel) - DataTableCore. - columnFilter (filterTriggerAriaLabel/valueAriaLabel/matchModeAriaLabel/ matchModeLabel/clear/apply/true/false) - ColumnFilterMenu, added as a provider tier between its existing labels prop and English defaults. - datePicker.label - the orphaned "Date" aria-label fallback in DatePickerInput used when neither an explicit aria-label nor a placeholder is given. Docs (Fix 4): English and nb-NO CratisComponentsProvider examples for every group in Documentation/Common/cratis-components-provider.md, plus precedence notes in Dialog/Dropdown/Notifications/CommandStepper/StepperCommandDialog/ DataTables docs. No React Aria locale data copied into messages. Specs (Fix 5): regression specs for every bypass fix; a dedicated precedence spec per new provider group (English fallback -> provider message -> prop override, in that order); and a sentinel-provider spec (Source/Common/for_CratisComponentsProvider/when_every_owned_label_is_overridden.tsx) that renders every audited surface under one provider with distinct sentinel messages and asserts none render their English default. No new ESLint rule: documented in ESLint/README.md why a hardcoded-label rule would need semantic judgment an AST visitor can't make reliably, producing either high noise or an allowlist that stops catching new bypasses; the sentinel-provider spec is the reliable gate instead. Explicitly out of scope, unless a bypass surfaces there later: PivotViewer, ObjectContentEditor, SchemaEditor's own generated cell/header copy, TimeMachine's relative/plural text, and identifier-derived formatTitle/formatPropertyName. #174 is not fully closed by this change. Verification: tsc -b (Source/tsconfig.json) clean; eslint clean; vitest run 275 files / 846 tests passed.
- package.json: add 'Codemods' to the root workspaces array (yarn install now symlinks node_modules/@cratis/components-codemods -> Codemods, and 'yarn workspace @cratis/components-codemods <script>' / 'cd Codemods && yarn <script>' resolve). No new dependency was declared - Codemods only uses the already-installed 'typescript' devDependency - and yarn.lock is gitignored in this repo, so there is no lockfile diff to commit. - README.md: a short 'Codemods' section pointing at Codemods/README.md and the ESLint rule, with the exact invocation 'node Codemods/scripts/remove-root-namespace-imports.js <paths...>'. Verified after registering the workspace: - 'cd Codemods && yarn test' -> 36/36 passing (previously only reachable by invoking vitest directly, since yarn refused to run an unregistered workspace's scripts). - 'cd ESLint && yarn test' -> 75/75 passing. - 'yarn lint' (root) -> exits 0 (Source lints clean; ESLint and Codemods are both skipped by run-task-on-workspaces.js - no 'lint' script and private:true respectively - which is pre-existing behavior for the ESLint workspace, unchanged by this commit).
Running the repo's own eslint config directly against Codemods/ (as part of verifying the root lint gate for the new workspace) flagged both 'unsupported-require' fixtures: the disable comment named the old '@typescript-eslint/no-var-requires' id, but this repo's config also has '@typescript-eslint/no-require-imports' enabled (from the typescript-eslint recommended preset) and that is the rule id that actually fires on a bare require() call - no-var-requires fires too, from this repo's own explicit rule entry, once the first is silenced. Disabling only the stale id left the fixture failing lint. Both ids are now listed on the eslint-disable-next-line comment, in both input.ts and expected.ts (they must stay byte-identical - this is the 'leave completely untouched' fixture pair). Verified: 'eslint Codemods' exits 0, and Codemods' 36/36 tests still pass.
Build and pack Core plus each selected adapter once, then reuse those immutable archives across every peer-boundary and package-manager profile. This matches release semantics and removes shared-dist races.
…ation # Conflicts: # Source/package.json # package.json
Owner review summary — Components 4 final candidateCandidate: The PR body is intentionally limited to consumer-facing release notes. This comment is the short owner/reviewer entry point; the detailed certification record is in #177 (comment). What Components 4 changes
Shared release scopeAll seven public packages use the repository/GitHub release version 4.0.0:
Renderer ABI major Stable renderer boundaryThe stable profile contains exactly:
Dialogs, Dropdown, DatePicker, Tooltip, paginator, atomic behavior, mixed-renderer islands, discovery, and lazy preload remain Core-owned and/or explicitly unstable. Important migration decisions
Final verification
What reviewers should decidePlease focus review on:
A line-by-line review of the entire 317-commit branch should not be necessary; this review summary, the technical certification comment, the migration guide, release policy, and ADRs are the decision record. Current stop pointComponents 4.0.0 is merged and published. All seven public packages use |
Technical certification record — Components 4 final candidateThis comment retains implementation, evidence, limitations, and merge/publication details that do not belong in the consumer-facing release note. Exact source state
Repository-wide package versionAll public packages are
Renderer ABI major Verification evidence
Reproducible release evidenceAll seven registry tarballs were downloaded after publication, matched their npm integrity metadata and package identities, and carry SLSA provenance attestations. Prepublication archives, CycloneDX 1.6 SBOMs, and checksum manifests remain retained as release evidence. Core archive:
Other archive SHA-256 values:
Decision records to review
Explicitly tracked limitations
Issue auto-close planGitHub permits at most ten manually linked closing issues per pull request. PR #177 is manually linked to #130, #159, #162, #163, #170, #173, #174, #191, #192, and #195; those will close automatically when the PR is merged into the default branch. The authorized real merge commit body must carry these remaining closing references: After merge, all fifteen issue states must be re-read and verified closed. Merely closing the PR without merging closes none of them. Merge and publication controls
|
Publish the Components 3-to-4 tool as @cratis/components.migrator and rename its workspace accordingly. Keep codemod as the internal implementation term while documenting Migrator and Conformance by audience and purpose.
Time out dynamic package imports and explicitly exit after flushing a successful one-shot probe so Pixi background handles cannot consume the hosted job timeout.
Prevent consumers from depending on internal transform modules while preserving the documented executables and package metadata contract.
Make installation status, package audiences, public helper APIs, and interaction limits explicit so consumers and adapter authors can evaluate the major release without inferring behavior from source.
Avoid formatter-sensitive nested list indentation and link package guidance to immutable source evidence until the Components 4 documentation is deployed.
Summary
Components 4 replaces the PrimeReact-backed Components 3 foundation with Cratis-owned React APIs, semantic markup, typed parts, state attributes, and design tokens while preserving the established component catalog.
React Aria supplies selected interaction primitives internally; it is not part of the public prop, DOM, styling, provider, or type contract. Native semantic HTML and Components-owned implementations remain the default where they are simpler.
This is a major release. Applications should follow the Components 3 to 4 migration guide.
Added
data-cratis-partvalues, canonical state attributes, provider messages, strict public declarations, and standalone Common controls. (Complete TSDoc coverage for intentional public exports #173)variant,tone, andshapeprops and value-firstChangeHandler<T>callbacks.stable-presentation/v1renderer profile for Button, IconButton, TextInput, TextArea, Checkbox, Radio, Switch, ProgressBar, and Surface.@cratis/components.mui,@cratis/components.primereact, and@cratis/components.primereact10as optional presentation adapters. (Library Agnostic components #191, Support MUI #192)@cratis/components.conformance, a development-only verification harness for renderer adapter authors covering behavior, parts, state, SSR, and accessibility. Ordinary applications do not install it.@cratis/components.migrator, an optional development CLI for upgrading Components 3 source imports, Button appearance props, and value-first callbacks. It uses syntax-aware codemods internally and is not an application runtime dependency.@cratis/components/Chatsubpath with sidebar, topic, conversation, message-body, action, mention, emoji, and observable-query surfaces.ItemAddedToRegion,ItemRemovedFromRegion,NoteTextChanged, andChatMessageAddedmessaging contracts.Changed
@cratis/components/*subpaths. (Components has no UI-kit-free entry point: the Arc binding surface only ships inside the PrimeReact package #162)pixi.jsto an optional peer used only by Canvas and PivotViewer.@cratis/components/Chatwhile retaining Canvas ChatBubble compatibility exports.1separate from the repository package version.CratisComponentsProvider.Fixed
CratisComponentsProvider. (Expand typed provider messages across Components-owned labels #174)clientFilteringprop as a no-op; complete-result behavior belongs to explicit server query arguments. (clientFiltering is declared on DataTableForQuery, DataTableForObservableQuery and DataPage, forwarded by DataPage, and never read by any implementation #159)@deprecatedguidance without shipping declaration-only prose in runtime JavaScript.Removed
@cratis/components.styled,compatibility, andprimereact-v10-paletteexports.