From ed44e4407562213de56bf75f1076f94da949adf2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:24:06 +0000 Subject: [PATCH] chore: release packages --- ...on-dialog-param-values-as-option-record.md | 29 - .../address-field-locale-labels-and-order.md | 32 - ...dr36-server-fault-policy-narrowing-3888.md | 45 - .../anchor-drift-cumulative-report-4974.md | 12 - .changeset/app-shell-docs-nav-examples.md | 11 - .changeset/app-shell-props-block-4808.md | 8 - .../appshell-drop-dead-sidebar-import.md | 8 - .../attachments-api-disabled-no-retry.md | 34 - .changeset/attachments-denied-vs-empty.md | 21 - .../attachments-unavailable-vs-empty.md | 37 - .../audit-lock-state-zh-vocabulary-5004.md | 37 - .changeset/audit-log-action-retirement.md | 6 - ...uth-invitation-status-closed-union-3879.md | 41 - .changeset/ban-package-self-import.md | 21 - .changeset/brave-pandas-invent.md | 4 - ...on-dialog-param-values-as-option-record.md | 32 - ...ade-clear-normalize-prefixed-field-type.md | 30 - ...ascade-option-allow-table-single-source.md | 37 - .../chart-unprojected-series-dimension.md | 28 - .../cli-app-generator-lucide-range-4968.md | 27 - .../cli-workspace-alias-derivation-3890.md | 25 - .../color-variant-picker-radiogroup-name.md | 39 - .changeset/combo-drill-doc-truth.md | 14 - .changeset/combo-mark-drill.md | 36 - .changeset/component-input-type-union-arms.md | 63 - .changeset/config-panel-footer-i18n.md | 30 - .../console-app-list-route-comment-truth.md | 17 - ...ole-index-fallback-matches-index-schema.md | 29 - .../console-objectstack-spec-dist-hook.md | 24 - ...gins-tooling-artifacts-out-of-dist-4836.md | 23 - .../create-plugin-jest-dom-range-4968.md | 21 - .changeset/dashboard-config-panel-i18n.md | 32 - .../data-objectstack-files-drops-src-4847.md | 19 - ...-widget-chart-bucket-drill-render-count.md | 9 - .../dataset-widget-dotted-dimension-race.md | 7 - ...dataset-widget-drill-title-render-count.md | 9 - ...ad-i18n-namespaces-configpanel-renderer.md | 41 - ...18n-namespaces-workflow-publicform-demo.md | 28 - .changeset/dependabot-automerge-gate-4973.md | 35 - .../deregister-app-shell-component-key.md | 41 - .changeset/dirty-pumpkins-shout.md | 61 - .../div-deprecation-provenance-scope.md | 12 - .changeset/doc-component-type-ratchet-4823.md | 32 - .../drawer-field-group-sections-4774.md | 20 - .changeset/drawer-flat-field-rules-4755.md | 19 - .../drop-stale-eslint-disable-directives.md | 23 - .../element-text-button-i18n-arms-4970.md | 43 - .changeset/engine-i18n-carveout-recorded.md | 13 - .changeset/fields-files-drops-src-4856.md | 21 - ...uilder-between-pair-and-operator-labels.md | 50 - ...r-falsy-values-and-strict-numeric-reads.md | 36 - ...er-builder-field-switch-resets-operator.md | 33 - ...lter-builder-field-switch-retypes-value.md | 38 - .../filter-builder-icontains-operator.md | 32 - ...ilter-builder-like-ilike-ruling-harvest.md | 18 - ...ilter-builder-set-operator-value-shapes.md | 40 - .../flow-resume-flow-failed-terminal.md | 38 - .changeset/flowcanvas-network-double.md | 7 - .changeset/flowcanvas-seeds-network-double.md | 7 - ...urce-wiring-reads-core-reference-family.md | 35 - .../form-submit-redirect-in-shell-4190.md | 14 - .changeset/form-view-retired-key-reads.md | 14 - .changeset/formula-column-sort-header-3950.md | 39 - .changeset/four-plans-refuse.md | 21 - ...lscreen-dialog-validation-and-name-4824.md | 97 -- .changeset/ga-five-honoured-inputs-4668.md | 54 - .../ga-object-block-authoring-surfaces.md | 31 - ...esidue-quickref-and-like-exemption-4977.md | 21 - .../gantt-map-calendar-shared-sort-sink.md | 37 - .changeset/gantt-source-comments-english.md | 16 - .changeset/great-hounds-sing.md | 11 - .../grid-column-declared-name-spelling.md | 27 - .changeset/guide-layout-app-shell-4827.md | 21 - .changeset/guide-layout-sidebar-nav-4840.md | 32 - .../i18n-bracket-marker-probe-unicode-3866.md | 12 - ...i18n-fallback-placeholder-spelling-gate.md | 26 - .changeset/issue-4661-doubled-doc-opener.md | 10 - .changeset/kanban-row-cap-top-level-top.md | 42 - .changeset/khaki-cars-attack.md | 23 - .changeset/layered-read-declared-path-4016.md | 43 - .../layout-readme-registration-keys-pin.md | 23 - ...ist-filter-existence-operators-withheld.md | 51 - .changeset/lucky-jars-fetch.md | 25 - .changeset/lucky-pumas-repeat.md | 30 - .changeset/map-camera-fits-queried-records.md | 39 - ...marketplace-runtime-tier-spec-enum-3846.md | 30 - ...metadata-admin-inspector-client-doubles.md | 6 - ...data-admin-writable-package-banner-4308.md | 15 - .changeset/mobile-nav-mode-enum-3985.md | 37 - .changeset/nervous-pugs-worry.md | 44 - .changeset/no-console-debug-infra-comments.md | 12 - .changeset/no-console-lint-rule.md | 38 - .changeset/olive-donkeys-repeat.md | 7 - .changeset/olive-donkeys-shave.md | 4 - .changeset/olive-moons-gather.md | 54 - .changeset/overlay-scope-vocabulary-4982.md | 27 - .../owner-retired-published-contract-twins.md | 45 - .changeset/page-accordion-item-icon.md | 14 - .../page-header-description-alias-retired.md | 17 - .../paginated-result-vacuous-block-4712.md | 14 - .../permissions-capabilities-reported.md | 28 - ...in-guide-skeleton-react-plugin-dep-4961.md | 27 - .../plugin-guide-toolchain-anchor-3855.md | 21 - .changeset/plugin-map-readme-truth-5002.md | 25 - .changeset/plugin-view-explain-double.md | 7 - ...blic-forms-redirect-authoring-door-4990.md | 17 - .../published-dist-tooling-gate-4846.md | 28 - .changeset/quickfilterbar-jsdoc-english.md | 9 - .changeset/quiet-moons-decide.md | 54 - .changeset/quiet-moons-listen.md | 63 - .changeset/readme-app-shell-navbar-example.md | 14 - ...eadonly-registered-field-a11y-container.md | 41 - .../record-alert-predicate-pipeline-pins.md | 20 - ...remove-dead-objectview-appdesigner-keys.md | 18 - .changeset/report-chart-authored-chrome.md | 44 - .changeset/report-chart-measure-label-4020.md | 21 - .../report-chart-null-category-bucket.md | 31 - ...required-when-yields-to-runtime-default.md | 42 - ...resource-edit-reset-delete-verdict-4886.md | 37 - .../retire-basefield-metadata-indexed.md | 19 - ...etire-dashboard-modal-prefix-convention.md | 36 - .changeset/retire-field-indexed-toggle.md | 30 - .../retire-modal-target-object-fallback.md | 43 - .changeset/retire-owner-widget-alias.md | 35 - ...retire-v3-deep-integration-modules-4241.md | 69 -- ...vectorfield-indexed-and-distance-metric.md | 29 - ...s-close-the-inline-edit-delegation-road.md | 50 - .changeset/richtext-editable-host-plumbing.md | 33 - .changeset/selectfirst-gate-joiner-locale.md | 29 - .changeset/settle-accordion-item-keys.md | 42 - .../shadcn-slider-delivery-anchor-4976.md | 23 - .changeset/shadcn-sync-readme-manifest-pin.md | 19 - .../sidebar-nav-readme-real-navitem-shape.md | 31 - ...ideeffects-declaration-consistency-gate.md | 32 - .changeset/silly-jars-drill.md | 42 - .../single-brace-downstream-holes-4135.md | 32 - .../slider-signature-host-aria-channels.md | 36 - .changeset/spotty-moons-repeat.md | 38 - .changeset/spotty-pugs-refuse.md | 17 - .changeset/strict-mode-parity-probes-4910.md | 27 - .../studio-interfaces-action-nav-surface.md | 34 - .changeset/tidy-buttons-repeat.md | 20 - .changeset/tidy-donkeys-attack.md | 18 - .changeset/tidy-donkeys-shave.md | 16 - .changeset/tidy-owls-explain.md | 28 - .changeset/tidy-poems-shave.md | 18 - .../tombstone-aware-spec-parity-3809.md | 41 - .changeset/types-files-drops-src-4851.md | 21 - .../unify-modal-target-refusal-diagnostic.md | 35 - .changeset/valueless-filter-row-drop.md | 11 - .changeset/view-override-masquerade.md | 20 - .changeset/wild-owls-repeat.md | 35 - .changeset/write-warning-reason-table-3935.md | 37 - apps/console/CHANGELOG.md | 62 + apps/console/package.json | 2 +- packages/app-shell/CHANGELOG.md | 1101 +++++++++++++++++ packages/app-shell/package.json | 2 +- packages/auth/CHANGELOG.md | 83 ++ packages/auth/package.json | 2 +- packages/cli/CHANGELOG.md | 89 ++ packages/cli/package.json | 2 +- packages/collaboration/CHANGELOG.md | 33 + packages/collaboration/package.json | 2 +- packages/components/CHANGELOG.md | 917 ++++++++++++++ packages/components/package.json | 2 +- packages/core/CHANGELOG.md | 515 ++++++++ packages/core/package.json | 2 +- packages/create-plugin/CHANGELOG.md | 22 + packages/create-plugin/package.json | 2 +- packages/data-objectstack/CHANGELOG.md | 191 +++ packages/data-objectstack/package.json | 2 +- packages/fields/CHANGELOG.md | 569 +++++++++ packages/fields/package.json | 2 +- packages/i18n/CHANGELOG.md | 473 +++++++ packages/i18n/package.json | 2 +- packages/layout/CHANGELOG.md | 188 +++ packages/layout/package.json | 2 +- packages/mobile/CHANGELOG.md | 17 + packages/mobile/package.json | 2 +- packages/permissions/CHANGELOG.md | 55 + packages/permissions/package.json | 2 +- packages/plugin-ai/CHANGELOG.md | 52 + packages/plugin-ai/package.json | 2 +- packages/plugin-calendar/CHANGELOG.md | 138 +++ packages/plugin-calendar/package.json | 2 +- packages/plugin-charts/CHANGELOG.md | 269 ++++ packages/plugin-charts/package.json | 2 +- packages/plugin-chatbot/CHANGELOG.md | 63 + packages/plugin-chatbot/package.json | 2 +- packages/plugin-dashboard/CHANGELOG.md | 274 ++++ packages/plugin-dashboard/package.json | 2 +- packages/plugin-designer/CHANGELOG.md | 129 ++ packages/plugin-designer/package.json | 2 +- packages/plugin-detail/CHANGELOG.md | 275 ++++ packages/plugin-detail/package.json | 2 +- packages/plugin-editor/CHANGELOG.md | 52 + packages/plugin-editor/package.json | 2 +- packages/plugin-form/CHANGELOG.md | 247 ++++ packages/plugin-form/package.json | 2 +- packages/plugin-gantt/CHANGELOG.md | 106 ++ packages/plugin-gantt/package.json | 2 +- packages/plugin-grid/CHANGELOG.md | 316 +++++ packages/plugin-grid/package.json | 2 +- packages/plugin-kanban/CHANGELOG.md | 143 +++ packages/plugin-kanban/package.json | 2 +- packages/plugin-list/CHANGELOG.md | 222 ++++ packages/plugin-list/package.json | 2 +- packages/plugin-map/CHANGELOG.md | 120 ++ packages/plugin-map/package.json | 2 +- packages/plugin-markdown/CHANGELOG.md | 52 + packages/plugin-markdown/package.json | 2 +- packages/plugin-report/CHANGELOG.md | 162 +++ packages/plugin-report/package.json | 2 +- packages/plugin-timeline/CHANGELOG.md | 64 + packages/plugin-timeline/package.json | 2 +- packages/plugin-tree/CHANGELOG.md | 63 + packages/plugin-tree/package.json | 2 +- packages/plugin-view/CHANGELOG.md | 115 ++ packages/plugin-view/package.json | 2 +- packages/providers/CHANGELOG.md | 17 + packages/providers/package.json | 2 +- packages/react-runtime/CHANGELOG.md | 2 + packages/react-runtime/package.json | 2 +- packages/react/CHANGELOG.md | 112 ++ packages/react/package.json | 2 +- packages/runner/CHANGELOG.md | 90 ++ packages/runner/package.json | 2 +- packages/sdui-parser/CHANGELOG.md | 70 ++ packages/sdui-parser/package.json | 2 +- packages/types/CHANGELOG.md | 313 +++++ packages/types/package.json | 2 +- packages/vscode-extension/CHANGELOG.md | 60 + packages/vscode-extension/package.json | 2 +- 233 files changed, 7881 insertions(+), 4376 deletions(-) delete mode 100644 .changeset/action-dialog-param-values-as-option-record.md delete mode 100644 .changeset/address-field-locale-labels-and-order.md delete mode 100644 .changeset/adr36-server-fault-policy-narrowing-3888.md delete mode 100644 .changeset/anchor-drift-cumulative-report-4974.md delete mode 100644 .changeset/app-shell-docs-nav-examples.md delete mode 100644 .changeset/app-shell-props-block-4808.md delete mode 100644 .changeset/appshell-drop-dead-sidebar-import.md delete mode 100644 .changeset/attachments-api-disabled-no-retry.md delete mode 100644 .changeset/attachments-denied-vs-empty.md delete mode 100644 .changeset/attachments-unavailable-vs-empty.md delete mode 100644 .changeset/audit-lock-state-zh-vocabulary-5004.md delete mode 100644 .changeset/audit-log-action-retirement.md delete mode 100644 .changeset/auth-invitation-status-closed-union-3879.md delete mode 100644 .changeset/ban-package-self-import.md delete mode 100644 .changeset/brave-pandas-invent.md delete mode 100644 .changeset/bulk-action-dialog-param-values-as-option-record.md delete mode 100644 .changeset/cascade-clear-normalize-prefixed-field-type.md delete mode 100644 .changeset/cascade-option-allow-table-single-source.md delete mode 100644 .changeset/chart-unprojected-series-dimension.md delete mode 100644 .changeset/cli-app-generator-lucide-range-4968.md delete mode 100644 .changeset/cli-workspace-alias-derivation-3890.md delete mode 100644 .changeset/color-variant-picker-radiogroup-name.md delete mode 100644 .changeset/combo-drill-doc-truth.md delete mode 100644 .changeset/combo-mark-drill.md delete mode 100644 .changeset/component-input-type-union-arms.md delete mode 100644 .changeset/config-panel-footer-i18n.md delete mode 100644 .changeset/console-app-list-route-comment-truth.md delete mode 100644 .changeset/console-index-fallback-matches-index-schema.md delete mode 100644 .changeset/console-objectstack-spec-dist-hook.md delete mode 100644 .changeset/core-plugins-tooling-artifacts-out-of-dist-4836.md delete mode 100644 .changeset/create-plugin-jest-dom-range-4968.md delete mode 100644 .changeset/dashboard-config-panel-i18n.md delete mode 100644 .changeset/data-objectstack-files-drops-src-4847.md delete mode 100644 .changeset/dataset-widget-chart-bucket-drill-render-count.md delete mode 100644 .changeset/dataset-widget-dotted-dimension-race.md delete mode 100644 .changeset/dataset-widget-drill-title-render-count.md delete mode 100644 .changeset/dead-i18n-namespaces-configpanel-renderer.md delete mode 100644 .changeset/dead-i18n-namespaces-workflow-publicform-demo.md delete mode 100644 .changeset/dependabot-automerge-gate-4973.md delete mode 100644 .changeset/deregister-app-shell-component-key.md delete mode 100644 .changeset/dirty-pumpkins-shout.md delete mode 100644 .changeset/div-deprecation-provenance-scope.md delete mode 100644 .changeset/doc-component-type-ratchet-4823.md delete mode 100644 .changeset/drawer-field-group-sections-4774.md delete mode 100644 .changeset/drawer-flat-field-rules-4755.md delete mode 100644 .changeset/drop-stale-eslint-disable-directives.md delete mode 100644 .changeset/element-text-button-i18n-arms-4970.md delete mode 100644 .changeset/engine-i18n-carveout-recorded.md delete mode 100644 .changeset/fields-files-drops-src-4856.md delete mode 100644 .changeset/filter-builder-between-pair-and-operator-labels.md delete mode 100644 .changeset/filter-builder-falsy-values-and-strict-numeric-reads.md delete mode 100644 .changeset/filter-builder-field-switch-resets-operator.md delete mode 100644 .changeset/filter-builder-field-switch-retypes-value.md delete mode 100644 .changeset/filter-builder-icontains-operator.md delete mode 100644 .changeset/filter-builder-like-ilike-ruling-harvest.md delete mode 100644 .changeset/filter-builder-set-operator-value-shapes.md delete mode 100644 .changeset/flow-resume-flow-failed-terminal.md delete mode 100644 .changeset/flowcanvas-network-double.md delete mode 100644 .changeset/flowcanvas-seeds-network-double.md delete mode 100644 .changeset/form-data-source-wiring-reads-core-reference-family.md delete mode 100644 .changeset/form-submit-redirect-in-shell-4190.md delete mode 100644 .changeset/form-view-retired-key-reads.md delete mode 100644 .changeset/formula-column-sort-header-3950.md delete mode 100644 .changeset/four-plans-refuse.md delete mode 100644 .changeset/fullscreen-dialog-validation-and-name-4824.md delete mode 100644 .changeset/ga-five-honoured-inputs-4668.md delete mode 100644 .changeset/ga-object-block-authoring-surfaces.md delete mode 100644 .changeset/ga-pin-residue-quickref-and-like-exemption-4977.md delete mode 100644 .changeset/gantt-map-calendar-shared-sort-sink.md delete mode 100644 .changeset/gantt-source-comments-english.md delete mode 100644 .changeset/great-hounds-sing.md delete mode 100644 .changeset/grid-column-declared-name-spelling.md delete mode 100644 .changeset/guide-layout-app-shell-4827.md delete mode 100644 .changeset/guide-layout-sidebar-nav-4840.md delete mode 100644 .changeset/i18n-bracket-marker-probe-unicode-3866.md delete mode 100644 .changeset/i18n-fallback-placeholder-spelling-gate.md delete mode 100644 .changeset/issue-4661-doubled-doc-opener.md delete mode 100644 .changeset/kanban-row-cap-top-level-top.md delete mode 100644 .changeset/khaki-cars-attack.md delete mode 100644 .changeset/layered-read-declared-path-4016.md delete mode 100644 .changeset/layout-readme-registration-keys-pin.md delete mode 100644 .changeset/list-filter-existence-operators-withheld.md delete mode 100644 .changeset/lucky-jars-fetch.md delete mode 100644 .changeset/lucky-pumas-repeat.md delete mode 100644 .changeset/map-camera-fits-queried-records.md delete mode 100644 .changeset/marketplace-runtime-tier-spec-enum-3846.md delete mode 100644 .changeset/metadata-admin-inspector-client-doubles.md delete mode 100644 .changeset/metadata-admin-writable-package-banner-4308.md delete mode 100644 .changeset/mobile-nav-mode-enum-3985.md delete mode 100644 .changeset/nervous-pugs-worry.md delete mode 100644 .changeset/no-console-debug-infra-comments.md delete mode 100644 .changeset/no-console-lint-rule.md delete mode 100644 .changeset/olive-donkeys-repeat.md delete mode 100644 .changeset/olive-donkeys-shave.md delete mode 100644 .changeset/olive-moons-gather.md delete mode 100644 .changeset/overlay-scope-vocabulary-4982.md delete mode 100644 .changeset/owner-retired-published-contract-twins.md delete mode 100644 .changeset/page-accordion-item-icon.md delete mode 100644 .changeset/page-header-description-alias-retired.md delete mode 100644 .changeset/paginated-result-vacuous-block-4712.md delete mode 100644 .changeset/permissions-capabilities-reported.md delete mode 100644 .changeset/plugin-guide-skeleton-react-plugin-dep-4961.md delete mode 100644 .changeset/plugin-guide-toolchain-anchor-3855.md delete mode 100644 .changeset/plugin-map-readme-truth-5002.md delete mode 100644 .changeset/plugin-view-explain-double.md delete mode 100644 .changeset/public-forms-redirect-authoring-door-4990.md delete mode 100644 .changeset/published-dist-tooling-gate-4846.md delete mode 100644 .changeset/quickfilterbar-jsdoc-english.md delete mode 100644 .changeset/quiet-moons-decide.md delete mode 100644 .changeset/quiet-moons-listen.md delete mode 100644 .changeset/readme-app-shell-navbar-example.md delete mode 100644 .changeset/readonly-registered-field-a11y-container.md delete mode 100644 .changeset/record-alert-predicate-pipeline-pins.md delete mode 100644 .changeset/remove-dead-objectview-appdesigner-keys.md delete mode 100644 .changeset/report-chart-authored-chrome.md delete mode 100644 .changeset/report-chart-measure-label-4020.md delete mode 100644 .changeset/report-chart-null-category-bucket.md delete mode 100644 .changeset/required-when-yields-to-runtime-default.md delete mode 100644 .changeset/resource-edit-reset-delete-verdict-4886.md delete mode 100644 .changeset/retire-basefield-metadata-indexed.md delete mode 100644 .changeset/retire-dashboard-modal-prefix-convention.md delete mode 100644 .changeset/retire-field-indexed-toggle.md delete mode 100644 .changeset/retire-modal-target-object-fallback.md delete mode 100644 .changeset/retire-owner-widget-alias.md delete mode 100644 .changeset/retire-v3-deep-integration-modules-4241.md delete mode 100644 .changeset/retire-vectorfield-indexed-and-distance-metric.md delete mode 100644 .changeset/retired-field-types-close-the-inline-edit-delegation-road.md delete mode 100644 .changeset/richtext-editable-host-plumbing.md delete mode 100644 .changeset/selectfirst-gate-joiner-locale.md delete mode 100644 .changeset/settle-accordion-item-keys.md delete mode 100644 .changeset/shadcn-slider-delivery-anchor-4976.md delete mode 100644 .changeset/shadcn-sync-readme-manifest-pin.md delete mode 100644 .changeset/sidebar-nav-readme-real-navitem-shape.md delete mode 100644 .changeset/sideeffects-declaration-consistency-gate.md delete mode 100644 .changeset/silly-jars-drill.md delete mode 100644 .changeset/single-brace-downstream-holes-4135.md delete mode 100644 .changeset/slider-signature-host-aria-channels.md delete mode 100644 .changeset/spotty-moons-repeat.md delete mode 100644 .changeset/spotty-pugs-refuse.md delete mode 100644 .changeset/strict-mode-parity-probes-4910.md delete mode 100644 .changeset/studio-interfaces-action-nav-surface.md delete mode 100644 .changeset/tidy-buttons-repeat.md delete mode 100644 .changeset/tidy-donkeys-attack.md delete mode 100644 .changeset/tidy-donkeys-shave.md delete mode 100644 .changeset/tidy-owls-explain.md delete mode 100644 .changeset/tidy-poems-shave.md delete mode 100644 .changeset/tombstone-aware-spec-parity-3809.md delete mode 100644 .changeset/types-files-drops-src-4851.md delete mode 100644 .changeset/unify-modal-target-refusal-diagnostic.md delete mode 100644 .changeset/valueless-filter-row-drop.md delete mode 100644 .changeset/view-override-masquerade.md delete mode 100644 .changeset/wild-owls-repeat.md delete mode 100644 .changeset/write-warning-reason-table-3935.md diff --git a/.changeset/action-dialog-param-values-as-option-record.md b/.changeset/action-dialog-param-values-as-option-record.md deleted file mode 100644 index 4e5fa7b7ba..0000000000 --- a/.changeset/action-dialog-param-values-as-option-record.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -An action dialog's per-option `visibleWhen` predicates now read the dialog's own in-progress param values. - -A field's per-option `visibleWhen` reaches a dialog param's control (objectui#3559), -but the dialog supplied no record to evaluate it against: it passed no -`dependentValues`, so the shared cascading-options evaluator fell through its -chain (`dependentValues ?? formValues ?? data`) to the host page's record, or to -nothing at all. A predicate written against a SIBLING PARAM — `record.country == -'cn'` on a province option, next to a `country` param in the same dialog — could -therefore never see the value the user had just entered. Authored cascades were -dead on this surface, in the safe direction: an unresolvable predicate offers the -option rather than hiding it, so nobody was shown a wrongly-narrowed list. - -Per the maintainer's 2026-08-11 ruling (Option B on objectui#3765) the dialog is -a small form, and its in-progress values are that record. The dialog now passes -them as `dependentValues` to the option widgets (`select`, `multiselect`, -`radio`, `checkboxes` — the same allow-list the object form threads the live -record to). The evaluator is unchanged; this is the supply half that was missing. - -Ruled consequence, stated because it is a behavior change and not only a fix: a -supplied record wins that chain outright, so an option predicate naming a field -of the underlying ROW that the dialog has no param for no longer resolves inside -a dialog. It becomes unresolvable, which fails open — the option stays offered, -never wrongly hidden. Merging the two records was the alternative reading and was -deliberately not taken: it would introduce a third scope dialect that has to be -written into the contract before anything can rely on it. diff --git a/.changeset/address-field-locale-labels-and-order.md b/.changeset/address-field-locale-labels-and-order.md deleted file mode 100644 index c311b74bfb..0000000000 --- a/.changeset/address-field-locale-labels-and-order.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@object-ui/fields': patch -'@object-ui/i18n': patch ---- - -`AddressField` is translatable, shows no US example placeholders, and formats its readonly line in the reader's address order. - -The five sub-labels ("Street Address", "City", "State / Province", "ZIP / -Postal Code", "Country") were English string literals with no i18n key. On a -non-English console every address field showed five English words in the middle -of an otherwise fully translated form, and an app had no way to reach them: the -parts are not fields on the object (`billing_address` is a single `address` -column), so a translation bundle had nothing to key on, there is no `subLabels` -property to declare, and the widget cannot be replaced from metadata. They now -resolve through `fields.address.street` / `.city` / `.state` / `.postalCode` / -`.country`, added to all ten locale packs. The `en` values are byte-identical to -the literals they replace, and `FIELD_DEFAULTS` carries the same five, so -English and provider-less rendering are unchanged. - -The five input placeholders (`123 Main St`, `San Francisco`, `CA`, `94102`, -`United States`) are **removed** rather than keyed. They were untranslated and -US-specific — a zh/ja/ar user was shown an American address as the example of -what to type — and the right example is a function of the address's country, -not the reader's language, which no channel in the stored value can supply -today. Each box keeps the visible label that names it. - -The readonly line's part order now follows the reader's display locale -(`useDisplayLocale()`): `zh`, `ja` and `ko` read largest-first (`Country, ZIP -State, City, Street`), every other locale keeps the unchanged small-to-large -order (`Street, City, State ZIP, Country`). The display cell renderer takes the -same locale through the same shared `formatAddress`, so a stored address reads -identically in a readonly form and in a grid cell. diff --git a/.changeset/adr36-server-fault-policy-narrowing-3888.md b/.changeset/adr36-server-fault-policy-narrowing-3888.md deleted file mode 100644 index 97bbee707f..0000000000 --- a/.changeset/adr36-server-fault-policy-narrowing-3888.md +++ /dev/null @@ -1,45 +0,0 @@ ---- ---- - -Docs-only correction, no behaviour and no authoring-surface change (objectui#3888). - -ADR-0036's `## Server enforcement (framework)` section stated the server's fault -policy unconditionally: "A predicate that fails to evaluate is **fail-open** and -logged". That covered both server-enforced predicates in one line, and it has been -false for one whole fault class since objectstack#4889: a `readonlyWhen` predicate -that faults because it names a scope ROOT the write never bound (`parent.status == -'paid'` with no master-detail header in hand) is fail-CLOSED — -`isReadonlyWhenLocked` warns `… treating the field as LOCKED` and resolves the -field to locked, and `stripReadonlyWhenFields` / `stripReadonlyWhenFieldsMulti` -then delete that key from the UPDATE payload, surfacing as a `droppedFields` entry -with `reason: 'readonly_when'`. - -This was the last uncorrected copy of that stale assertion. The code-comment copy, -in `packages/core/src/evaluator/fieldRules.ts`, was narrowed by objectui#3828; the -ADR is where a reader looks FIRST when asking what the server does, so leaving it -preserved the wrong mental model in the more authoritative place. - -The summary bullet now carries the exception and points at a new subsection that -states it in full: why the unbound-root case is not a broken predicate, what the -server does with it, and — since the client deliberately does NOT mirror it — the -silent symptom that divergence produces (field renders editable, save reports -success, value never lands) plus which end to debug. The `## Client enforcement -(objectui)` section's "the same posture as the server" clause is narrowed the same -way, since it asserted the same parity. - -Two things verified as still accurate and left alone: the `requiredWhen` half -(objectstack#4977 bound the same `parent` scope but deliberately kept fail-open -semantics, so an unevaluable requirement is skipped on both ends) and the -`visibleWhen` bullet (the server never evaluates it). Both are now stated as -explicit non-exceptions rather than implied by an over-broad summary. - -The ADR-0057 D10 citation is marked as the **framework's** numbering: this repo -carries an unrelated `docs/adr/0057-console-ai-chat-one-conversation-docked.md`, -so an unqualified "ADR-0057" resolves to the wrong document for a reader in this -tree. It is also written as an attribution — the shorthand the framework's -rule-validator, lint diagnostics and QA runner all use — rather than as a claim -that the D-anchor resolves, because verification could not confirm that it does -(filed against the framework, not fixed here). - -No package is declared because nothing published changed: the diff is one -markdown file under `docs/adr/`. diff --git a/.changeset/anchor-drift-cumulative-report-4974.md b/.changeset/anchor-drift-cumulative-report-4974.md deleted file mode 100644 index 6fe43e103f..0000000000 --- a/.changeset/anchor-drift-cumulative-report-4974.md +++ /dev/null @@ -1,12 +0,0 @@ ---- ---- - -Test-only: the two generator anchor rules — `packages/cli`'s app/init manifests and -`packages/create-plugin`'s template devDependencies — now collect every drifted range and -report them from a single assertion, instead of one `expect` per name that threw on the -first mismatch. A dependabot batch that moves several in-repo ranges is one round of -repair rather than one round per name, and which name got reported no longer depends on -its position in the anchor table. The preconditions (the anchor must resolve; in-repo -manifests must agree) still fail fast and are kept in a separate pass, so a repo-state -failure is never reported as, or beside, template drift. No published behaviour changes -(objectui#4974). diff --git a/.changeset/app-shell-docs-nav-examples.md b/.changeset/app-shell-docs-nav-examples.md deleted file mode 100644 index 66184f299b..0000000000 --- a/.changeset/app-shell-docs-nav-examples.md +++ /dev/null @@ -1,11 +0,0 @@ ---- ---- - -Docs + test-only (objectui#4793). `content/docs/layout/app-shell.mdx`'s two `SidebarNav` -examples now spell the real `NavItem` keys — `title` instead of `label`, and the imported -Lucide **component** instead of a quoted icon name — and stop teaching a `header` / -`footer` prop that `SidebarNavProps` never declared. The examples are pinned to the real -types by `packages/layout/src/__tests__/app-shell-docs-nav-example.test.ts`. - -No published behaviour changes: nothing in any package's runtime source was touched, only -the documentation page and the test that now compiles it. diff --git a/.changeset/app-shell-props-block-4808.md b/.changeset/app-shell-props-block-4808.md deleted file mode 100644 index 14ecb882ed..0000000000 --- a/.changeset/app-shell-props-block-4808.md +++ /dev/null @@ -1,8 +0,0 @@ ---- ---- - -Docs-only alignment plus its pin: `content/docs/layout/app-shell.mdx` now reprints all -seven props `AppShellProps` declares (adding `branding` and `rightRail`), and the existing -app-shell docs pin test compares that block against `packages/layout/src/AppShell.tsx` on -every run. No published behaviour changes — the MDX page is not part of any released -package, and the only source file touched is a test (objectui#4808). diff --git a/.changeset/appshell-drop-dead-sidebar-import.md b/.changeset/appshell-drop-dead-sidebar-import.md deleted file mode 100644 index 2123d91f3e..0000000000 --- a/.changeset/appshell-drop-dead-sidebar-import.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@object-ui/layout': patch ---- - -AppShell: drop the unused `Sidebar` import. `AppShell` renders the node the caller passes -in the `sidebar` prop and never constructs a `Sidebar` itself, so the import was dead -(tree-shaken out of every bundle) and only suggested otherwise to readers. No runtime -behaviour changes. diff --git a/.changeset/attachments-api-disabled-no-retry.md b/.changeset/attachments-api-disabled-no-retry.md deleted file mode 100644 index 3258613901..0000000000 --- a/.changeset/attachments-api-disabled-no-retry.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@object-ui/app-shell': patch -'@object-ui/i18n': patch -'@object-ui/plugin-list': patch -'@object-ui/react': patch ---- - -RecordAttachmentsPanel no longer offers a Retry for an api-disabled `sys_attachment` read. - -`OBJECT_API_DISABLED` (404, `enable.apiEnabled: false`) and its sibling -`OBJECT_API_METHOD_NOT_ALLOWED` (405, the operation is absent from -`enable.apiMethods`) are pure functions of the object's metadata — no user, no -session, no request body — so every retry of every persona re-fetches the -identical refusal. Before this change both landed in `RecordAttachmentsPanel`'s -`unavailable` state and offered a Retry that was guaranteed to change nothing, -the same wrong advice `ListView`'s error panel already stops giving for list -reads. - -The panel gains a fifth status, `api-unavailable`: no Retry button, and honest -copy ("The attachments list is not available on this object.", new -`detail.attachmentsApiUnavailable` key in all ten locale packs) instead of -"We couldn't load the attachments for this record." The pre-existing `denied` -(authorization) and `unavailable` (network/5xx/expired-session) states and -their affordances are unchanged. - -`ListView.classifyLoadError` — the classifier that already separated this case -into its own `api-disabled` kind for list views — is lifted out of -`packages/plugin-list/src/ListView.tsx`'s module scope into -`@object-ui/react` (`classifyLoadError`, `LoadErrorKind`), so both surfaces -consume one classification instead of `RecordAttachmentsPanel` re-deriving it. -`ListView`'s own behavior is unchanged — it now imports the function it -previously defined locally. The classifier delegates its api-disabled check to -`isApiAccessDeniedError` (`@object-ui/data-objectstack`), removing a second, -independently-maintained copy of the same code list. diff --git a/.changeset/attachments-denied-vs-empty.md b/.changeset/attachments-denied-vs-empty.md deleted file mode 100644 index 5c7571dd81..0000000000 --- a/.changeset/attachments-denied-vs-empty.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@object-ui/app-shell': patch -'@object-ui/i18n': patch ---- - -RecordAttachmentsPanel distinguishes a DENIED attachment list from an empty one. - -A `sys_attachment` list read refused for authorization reasons (HTTP 403 / -`PERMISSION_DENIED` / `FORBIDDEN` / a row-level-security denial) was swallowed -into the panel's empty state, so a member denied the parent record was told -"No attachments yet. Upload a file to get started." about a record holding -2095+ attachments — and was offered an Upload the server would refuse. - -The panel now classifies that refusal with the same `isPermissionError` -predicate the kanban, calendar and form surfaces branch on, renders a distinct -denied state ("You don't have access to these attachments.", new -`detail.attachmentsAccessDenied` key in all ten locale packs), and withdraws -the Upload affordance. The denied state renders the translated sentence and -nothing sourced from the error — no status code, no server text, no row count. -The empty state is now reserved for a genuine 200-with-zero-rows; non-authz -failures keep their pre-existing handling. diff --git a/.changeset/attachments-unavailable-vs-empty.md b/.changeset/attachments-unavailable-vs-empty.md deleted file mode 100644 index 5b94ddfc8d..0000000000 --- a/.changeset/attachments-unavailable-vs-empty.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@object-ui/app-shell': patch -'@object-ui/i18n': patch ---- - -RecordAttachmentsPanel distinguishes an UNLOADED attachment list from an empty one. - -A `sys_attachment` list read that failed for any non-authorization reason — a -network failure (server unreachable, DNS, aborted request), a 5xx, or a 401 / -`AUTH_REQUIRED` (an expired session is authentication, not authorization, so the -denied predicate deliberately does not claim it) — was swallowed into the -panel's empty state. All three rendered "No attachments yet. Upload a file to -get started.": an affirmative claim about the record's contents, made by a panel -that never got an answer, over a record that may hold thousands of files. - -The panel now carries the same four-way status vocabulary its siblings use — -`loading` / `loaded` / `denied` / `unavailable` — and every state that means -"the panel does not know" is answered before `rows.length === 0` is allowed to -mean "the record holds nothing". A failed read renders a distinct unavailable -state ("We couldn't load the attachments for this record.", new -`detail.attachmentsLoadFailed` and `detail.retryLoadAttachments` keys in all ten -locale packs) with a **retry** — unlike the denied state, an outage and a lapsed -session are both things a second attempt can fix — and withdraws the Upload -affordance, because offering an upload against a list the panel could not reach -is the same over-assertion as the empty state it replaces. Like the denied -state, it renders the translated sentence and nothing sourced from the error: no -status code, no server message, no host. - -The empty state is now reserved for a genuine 200-with-zero-rows, and the -denied state (403 / `PERMISSION_DENIED` / `FORBIDDEN` / a row-level-security -denial) is unchanged. The "table not provisioned on an older stack" case is -unaffected: the ObjectStack adapter degrades a bare 404 to `{ data: [], total: 0 }`, -so it resolves through the success path and still renders the empty state. - -This restores a house rule that had already landed twice as a bug fix — -`HomeActionCenter` may only say "You're all caught up" once the inbox has -answered, and an unloadable app list is UNKNOWN rather than "no default app". diff --git a/.changeset/audit-lock-state-zh-vocabulary-5004.md b/.changeset/audit-lock-state-zh-vocabulary-5004.md deleted file mode 100644 index b67f1180ee..0000000000 --- a/.changeset/audit-lock-state-zh-vocabulary-5004.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -The metadata Audit panel's lock column now shows Chinese for every lock state it can print. - -`AuditPanel` renders `MetadataAuditEntry.lockState` through -`translateConsoleValue('lock', …)`, but `CONSOLE_VALUE_ZH.lock` held -`draft` / `locked` / `published` / `none` — a draft-status vocabulary, not the -ADR-0010 §3.6 four-state lock. The misalignment was total rather than partial -(objectui#5004): `draft` / `locked` / `published` matched no value `lockState` -can hold; `none`, the only entry that did match, is excluded by the call site's -own guard (an unlocked row renders an em dash) and so was unreachable; and the -three values that actually reach the helper — `no-overlay` / `no-delete` / -`full` — had no entry at all. Hit rate 0/3. A zh-CN admin opening any locked -item's Audit panel read bare English tokens in a column headed 锁状态. - -The table is now the lock vocabulary it claims to be — `禁止编辑` / `禁止删除` / -`完全锁定`, wording tracked to the lock-banner sentences a reader meets on the -same screen — and the three draft-status entries are gone. They were dead in the -measured sense: `translateConsoleValue('lock', …)` has exactly one call site -repo-wide and `CONSOLE_VALUE_ZH` is module-private, so nothing else could read -them. - -Following objectui#4982's `LAYER_SCOPE_ZH`, the keys are bound to their -producer's union — `Record< NonNullable< MetadataAuditEntry['lockState'] >, -string >`. A fifth lock state therefore fails `type-check` naming the label that -is missing, instead of the column silently shipping a raw English value. The -union is this repo's own hand-written one in `@object-ui/data-objectstack`, not a -`@objectstack/spec` enum; whether the spec should own the lock vocabulary is a -separate question and is deliberately not answered here. - -`none` is kept in the record even though the call site never asks for it, so the -key set stays complete over the producer's type rather than tracking a `!==` -guard in another file. Unchanged on purpose: `translateConsoleValue` is still -zh-only, and the em-dash branch for `none` / `null` still renders exactly as -before. diff --git a/.changeset/audit-log-action-retirement.md b/.changeset/audit-log-action-retirement.md deleted file mode 100644 index e5e7088958..0000000000 --- a/.changeset/audit-log-action-retirement.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@object-ui/console": patch -"@object-ui/plugin-detail": patch ---- - -Retire `permission_change`, `export`, and `restore` from the audit-log action filter (`AuditLogPage`'s `ACTION_OPTIONS`) and badge maps (`AuditLogPage` and `HistoryTimeline`'s `ACTION_VARIANT`). These three values never had a writer anywhere on the platform, so the filter always returned zero rows for them and the badges never rendered — a visible product defect (audit surface should be narrow-but-honest, not broad-but-lying). `import`, `login`, and `config_change` are kept: `import` has a real writer (`plugin-auth`'s `admin-import-users.ts`) and is still declared by the server enum and filtered by the `config_changes` list view; `login`/`config_change` gained real writers in objectstack#8144/#8145. diff --git a/.changeset/auth-invitation-status-closed-union-3879.md b/.changeset/auth-invitation-status-closed-union-3879.md deleted file mode 100644 index f150019a76..0000000000 --- a/.changeset/auth-invitation-status-closed-union-3879.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -'@object-ui/auth': minor -'@object-ui/app-shell': minor ---- - -`AuthInvitation.status` is the closed four-member union it always documented, enforced at the auth client's wire boundary. - -The field was declared `status: string` while its own doc comment enumerated -`'pending' | 'accepted' | 'rejected' | 'canceled'`, and `createAuthClient` cast -better-auth's `any` responses straight to `AuthInvitation[]` with no check. The -enumeration was therefore advisory: any value a backend stored reached the -console untouched, where the invitations screen coloured it through a -`default: 'secondary'` badge arm and printed it with -`defaultValue: inv.status` — the raw wire string rendered as interface copy, in -all ten packs (objectui#3879). Nobody could trigger it today, because the four -are what better-auth writes; it was the contract that was loose, and a loose -consumer is where an unexpected value would have hidden. - -`AuthInvitationStatus` now carries the union and **binds better-auth's own -`InvitationStatus`** rather than restating it, the way `org-roles.ts` binds the -spec's `BUILTIN_MEMBERSHIP_ROLES` — a member-for-member copy is the state one -upstream release away from drifting silently. The runtime list -(`AUTH_INVITATION_STATUSES`) is derived from a total map keyed by that union, so -a fifth status upstream is a build failure here rather than a gap in the guard. -`isAuthInvitationStatus` is exported alongside, and all four invitation-returning -client methods (`listInvitations`, `listUserInvitations`, `getInvitation`, -`inviteMember`) narrow their wire rows through it. - -Behaviour change, stated because it is one: an invitation whose `status` is -outside the set now **fails loudly** — the call rejects with a message naming the -value it refused and the four it expected — instead of resolving into a badge. -Both call paths already render a rejection with a retry, so the throw lands on a -designed surface. Degrading quietly was the alternative and was deliberately not -taken: a neutral label is how the raw value shipped in the first place, and -dropping the row would delete an invitation from an administrative ledger without -saying so. The console's badge switch is exhaustive over the union now and has no -`default:` arm, so if better-auth ever adds a member the type-check gate stops the -build at the one place a human has to choose a colour. - -Consumers assigning an arbitrary string to `AuthInvitation.status` (a hand-built -fixture, a mock) will need one of the four members. diff --git a/.changeset/ban-package-self-import.md b/.changeset/ban-package-self-import.md deleted file mode 100644 index d7247c752e..0000000000 --- a/.changeset/ban-package-self-import.md +++ /dev/null @@ -1,21 +0,0 @@ ---- ---- - -Tooling + test-only (objectui#4801). No package may name itself in a module specifier inside -its own `src/`, and a new CI gate — `pnpm check:self-import`, -`scripts/check-package-self-import.mjs` — enforces it. - -A file inside a package that imports the package's own name resolves, legally, through that -package's `exports` map to `dist/`. What does not exist is an ordering: turbo gives -`type-check` `dependsOn: ["^build"]`, the DEPENDENCIES' builds, never the package's own. So on -a cold CI cache the declarations the specifier points at have not been produced yet and the -file fails with `TS2307`. PR #4789's first CI run was red on exactly one line of that shape in -`packages/fields/src`, and no local workflow can see it: every one of them builds before it -type-checks, so a stale `dist/` makes the tree green on any machine that has ever run a build. - -The two remaining sites were converted to relative imports — -`packages/core/src/__benchmarks__/core.bench.ts` and -`packages/components/src/__tests__/snapshot-critical.test.tsx` — and the `paths` entry in -`packages/components/tsconfig.test.json` that existed only to redirect that self-import away -from `dist/` is gone with it. No published behaviour changes: a benchmark file, a snapshot test -and repo tooling, so this declares "no release" rather than a bump. diff --git a/.changeset/brave-pandas-invent.md b/.changeset/brave-pandas-invent.md deleted file mode 100644 index d02a2309fa..0000000000 --- a/.changeset/brave-pandas-invent.md +++ /dev/null @@ -1,4 +0,0 @@ ---- ---- - -Test-only change: adapts the spec parity gates to `@objectstack/spec` 17.0.0 GA ahead of the pin bump — pin-aware coverage for the four `object-*` blocks GA adds, GA-pending exemptions for five keys the renderers already honour, a reasoned exemption for `FormFieldSchema.publicPicker`, and the `record:details` / `record:highlights` gates now reading GA's `unrecognized_keys` refusal instead of rc.6's silent strip. No renderer, no registry `inputs` and no published authoring surface changes; declared as releasing nothing. diff --git a/.changeset/bulk-action-dialog-param-values-as-option-record.md b/.changeset/bulk-action-dialog-param-values-as-option-record.md deleted file mode 100644 index 40b3112215..0000000000 --- a/.changeset/bulk-action-dialog-param-values-as-option-record.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@object-ui/plugin-grid': patch ---- - -A bulk action dialog's per-option `visibleWhen` predicates now read the dialog's own in-progress param values. - -The second landing site of the same gap objectui#3765 closed for the -single-record action dialog. A field's per-option `visibleWhen` reaches a bulk -param's control, but the dialog supplied no record to evaluate it against: it -passed no `dependentValues`, so the shared cascading-options evaluator fell -through its chain (`dependentValues ?? formValues ?? data`) to whatever record -the host grid page happened to publish, or to nothing at all. A predicate -written against a SIBLING PARAM — `record.country == 'cn'` on a province option, -next to a `country` param in the same dialog — could therefore never see the -value the user had just entered. Authored cascades were dead on this surface, in -the safe direction: an unresolvable predicate offers the option rather than -hiding it, so nobody was shown a wrongly-narrowed list. - -Per the maintainer's 2026-08-11 ruling (Option B on objectui#3765) the dialog is -a small form, and its in-progress values are that record. The bulk dialog now -passes them as `dependentValues` to the option widgets (`select`, `multiselect`, -`radio`, `checkboxes` — the same allow-list the object form and the single-record -action dialog thread the live record to). The evaluator is unchanged; this is the -supply half that was missing. - -Bulk is the cheap case for that ruling, which is why it needed no separate one: -an action over N selected rows has no single row record for the dialog's values -to displace — the selection was never offered to these predicates, so the -dialog's values are the only record there has ever been. What the supplied record -does displace is the host page's, since it wins the chain outright: a predicate -naming a column the dialog has no param for stays unresolvable, which fails open -— the option is offered, never wrongly hidden. diff --git a/.changeset/cascade-clear-normalize-prefixed-field-type.md b/.changeset/cascade-clear-normalize-prefixed-field-type.md deleted file mode 100644 index 30bb5033a4..0000000000 --- a/.changeset/cascade-clear-normalize-prefixed-field-type.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@object-ui/components': patch ---- - -The form's cascade clear now recognises object-form fields, so a narrowed option list no longer submits a stale value. - -`field:select` and `select` name the SAME field kind: the object-form path -(`mapFieldTypeToFormType`) emits the prefixed widget id, hand-written SDUI -schemas the bare one. The form host's cascade-clear effect (objectui#2284) -compared the RAW type string against the bare-name set, so every option field -coming from an OBJECT schema fell out of the effect entirely. Its controlling -field could change, its option list narrow, and the no-longer-offered value was -never dropped — the form submitted exactly the stale "china + california" pair -the effect exists to prevent. Only genuinely cascading fields were affected -(those carrying a `dependsOn` or a per-option `visibleWhen`); a plain picklist -has nothing to recompute either way. - -The comparison now normalizes the type before the lookup, which is what the -render path a few hundred lines below has done for `isOptionField` since -objectui#3231 — the two readers of "is this an option field?" no longer -disagree about what a `select` is. This half was the one missed then. - -Stated because it is a behavior change and not an equivalent refactor: the -object-form path gains cascade clearing for the FIRST time. A form whose stored -value is genuinely excluded by its chosen parent will now clear that value where -it previously kept it. The narrowing is bounded by the rules already in place -for the bare-name path, both of which the object path now inherits unchanged: a -GATED list (a declared `dependsOn` parent still empty) is treated as unknown -rather than invalid and never deletes anything (objectui#4247), and a field with -no `dependsOn` and no per-option predicate is never recomputed at all. diff --git a/.changeset/cascade-option-allow-table-single-source.md b/.changeset/cascade-option-allow-table-single-source.md deleted file mode 100644 index b47ff78cb0..0000000000 --- a/.changeset/cascade-option-allow-table-single-source.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@object-ui/core': patch -'@object-ui/fields': patch -'@object-ui/components': patch -'@object-ui/app-shell': patch -'@object-ui/plugin-grid': patch ---- - -The allow-list of option widgets that are fed the live record is now one exported constant, `CASCADE_OPTION_WIDGET_TYPES`, instead of three private copies. - -`select` / `multiselect` / `radio` / `checkboxes` are the widgets whose OFFERED -option set is re-resolved against a record (per-option `visibleWhen`, plus the -`dependsOn` gate), so they are the widgets a surface must thread its live record -to. Three surfaces feed that one evaluator — the object form, the single-record -action dialog and the bulk action dialog — and until now each carried its own -private `new Set([...])` of the same four keys, with a comment in each asking the -next person to change all three together. Nothing could have reported them -drifting: every copy passed its own behavioural tests, and a divergence would -have shown up only as one surface silently disagreeing with another about what -"the record" is. - -The set now lives in `@object-ui/core`, next to `resolveCascadingOptions` — the -evaluator that reads that record — because core is the one package all three -surfaces already depend on, and it is re-exported from `@object-ui/fields` next -to `resolveFormWidgetType`, whose output is the vocabulary the keys are written -in. Both are the same object, pinned by test; each consumer keeps its own -normalization (`normalizeFieldType` in the form, `resolveFormWidgetType` in the -dialogs), which agree on these four members. - -No behaviour changes: the members are identical on all three surfaces, and the -existing pins for each surface still assert the same records reaching the same -widgets. The rationale that was repeated in the three copies — including the -note that the widget-hint picker family (`filter-condition`, `recipient-picker`, -the lookup family) reads a different sibling key off the same channel and is -deliberately NOT in this set — is now stated once, in the constant's own -documentation. Whether the action and bulk dialogs should ever feed those -pickers stays an open question (objectui#4771), unchanged by this convergence. diff --git a/.changeset/chart-unprojected-series-dimension.md b/.changeset/chart-unprojected-series-dimension.md deleted file mode 100644 index 0d7e8c6139..0000000000 --- a/.changeset/chart-unprojected-series-dimension.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@object-ui/plugin-charts': patch ---- - -A grouped chart whose SECOND dimension was never projected says so instead of -drawing an empty frame. - -"Cannot know refuses loudly" was answered on the first dimension only. -`AdvancedChartImpl`'s `hasNoCategoryKey` (framework#4033) names an unprojected -x-axis dimension rather than drawing a bare axis; the series axis had no -counterpart, so a pivot whose second dimension was absent from the result rows -produced `series: []` and rendered axes, grid, tooltip and legend around zero -marks — indistinguishable, to the author, from "no data matched". - -Such a chart now renders the same explanatory placeholder -(`data-chart-error="no-plottable-series"`) and logs the same diagnostic pair the -category-axis guard logs: the axis it did plot, and the keys its rows actually -carry. - -The three-way distinction is unchanged and pinned: null / empty-string group -values still DRAW (they are real groups with real buckets), a partially -projected group key still draws what projects — mirroring the category axis, -which refuses only when NOT ONE row carries the key — and an ordinary pivot -renders unchanged. The refusal is limited to the families whose marks come from -`series` and nothing else (bar, horizontal-bar, line, area, combo); pie, donut, -funnel, radar and scatter draw from a `value` column with no series declared, so -they are untouched. A caller that computed no series binding at all -(`series === undefined`) is also untouched. diff --git a/.changeset/cli-app-generator-lucide-range-4968.md b/.changeset/cli-app-generator-lucide-range-4968.md deleted file mode 100644 index ca32e4ed0c..0000000000 --- a/.changeset/cli-app-generator-lucide-range-4968.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@object-ui/cli': patch ---- - -The routed temp app's generated manifest now asks for the same `lucide-react` range this repo installs. - -`utils/app-generator.ts` writes the routed variant's `dependencies` with two -quoted third-party ranges, and `lucide-react` had fossilised a minor behind the -22 sibling manifests that declare it: the generated manifest said `^1.29.0` -while the repo had moved to `^1.31.0`. A generated app therefore asked npm for -an icon library older than the one every `@object-ui/*` package it installs -alongside was built against. - -The drift was not silent — `app-generator.test.ts` derives its expectation from -the in-repo range precisely so a bump on one side and not the other fails a -test, and both of its pins were red. What went wrong is that they went red too -late to stop anything: the dependency PR that moved the repo range merged while -those shards were still running, so the failure surfaced on `main` and then on -the merge ref of every unrelated open PR. The range is now caught up; the -reporting hole and the merge-ordering hole are filed separately (objectui#4968). - -The remaining eleven anchored ranges were swept against the same dependabot -batch and are all in sync, so this is the batch's only consumer-side follow-up. -Deriving the value from the workspace instead of quoting it was considered and -rejected: nine of the thirteen anchored ranges quote the repo root manifest, -which is not published with this CLI, so no single derivation can serve the -table and a bespoke one for this one name would leave the class untouched. diff --git a/.changeset/cli-workspace-alias-derivation-3890.md b/.changeset/cli-workspace-alias-derivation-3890.md deleted file mode 100644 index 13c0d382f9..0000000000 --- a/.changeset/cli-workspace-alias-derivation-3890.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@object-ui/cli': patch ---- - -Inside a pnpm workspace, `objectui dev` / `serve` / `build` now resolve every platform package -from workspace source (objectui#3890). - -The temp app these commands generate installs nothing inside a workspace — it resolves by -hoisting, and the repo root declares no `@object-ui/*` — so a Vite alias table is the only thing -that resolves a platform package there. That table was a hand-kept list of eleven names in -`dev`, which is not a list of what the app imports but of what it imports *transitively*: -measured on the reported commit, the generated entry closes over 21 packages, ten were unlisted, -and every module whose transform hit one of them answered 500 with a blank page behind it. Vite's -dependency scan named only four of the ten, because a scan stops at the first layer it cannot -resolve. - -The table is now derived from `pnpm-workspace.yaml` — every scoped workspace package that exposes -a source barrel, targeting its `src` directory — and a test reconciles it against the manifest so -it cannot drift again. `serve` and `build` had no workspace branch at all (no aliases, and an -unconditional `npm install` against a manifest that is empty here); all three commands now share -one helper. The `lucide-react` entry moved from a resolved entry file to the package root, so -subpath imports of it stop being rewritten into a path that cannot exist. - -Measured with the reported repro, from the repo root: 8 of the first 400 modules a browser walk -reaches answered 500 before, 0 of 2498 after, and the page renders its schema instead of nothing. diff --git a/.changeset/color-variant-picker-radiogroup-name.md b/.changeset/color-variant-picker-radiogroup-name.md deleted file mode 100644 index 7be0d06d29..0000000000 --- a/.changeset/color-variant-picker-radiogroup-name.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -The designer's colour swatch rows now announce WHAT they colour, and the Dashboard widget inspector's "Color Variant" label no longer points at nothing. - -`ColorVariantPicker` renders its swatches inside a `div role="radiogroup"`. Each -swatch was named by its colour ("Blue", "Success"); the group was named by -nothing at all, so focus entering it announced an anonymous radiogroup — eight -colours with no statement of which field they set — while the visible label -beside it belonged to no one. - -`DashboardWidgetInspector` had the worse half of it. Its `Field` wrapper renders -`Label htmlFor={id}` and expects the wrapped control to carry that id; the picker -accepted no id, so `htmlFor="widget-color"` was a DANGLING IDREF — an association -tooling reports as closed while it resolves to no element, which is why a static -"every label has a `for`" check saw nothing wrong (objectui#4010). - -A `for` cannot fix this: `role="radiogroup"` is a container, not a labelable -element, so a `for` aimed at it is inert HTML that names nobody. The picker now -takes its name through ARIA, required at the type level and singular — exactly -one of `ariaLabelledBy` or `ariaLabel`, so an unnamed colour group no longer -compiles (the contract `InspectorComboField` got in objectui#3997): - -- **Dashboard widget inspector** — the "Color Variant" label publishes - `widget-color-label` and drops its `for`; the group answers by IDREF. The - visible text IS the accessible name, in every locale, with no second string to - translate. -- **Page block properties panel** — the "Color" label never carried a `for`, so - nothing dangled there; the group was simply anonymous. Same repair, with the - id minted per instance so two colour rows cannot collide. -- **Generic `color-picker` widget** — its host writes `Label htmlFor` before it - can know whether this field renders a swatch group or a labelable - `input type="color"`, so the group carries its own name, taken from the host's - own label source. - -This is the WAI-ARIA group pattern this repo already applies to group-labelled -field widgets (objectui#3961 → #3990), applied to the three surfaces that were -still outside it. diff --git a/.changeset/combo-drill-doc-truth.md b/.changeset/combo-drill-doc-truth.md deleted file mode 100644 index d7c0d22f48..0000000000 --- a/.changeset/combo-drill-doc-truth.md +++ /dev/null @@ -1,14 +0,0 @@ ---- ---- - -Test-and-docs only: corrects `AdvancedChartImpl`'s `onChartClick` doc comment to what is -actually wired (bar/horizontal-bar/line/area/pie/donut/funnel/scatter/treemap/sankey; the -comment previously listed scatter/treemap/sankey as no-ops though each already has a wired -click handler) and names the derived-family trap — a series' own `type` disagreeing with -the chart's family silently turns the whole chart into a `combo`, which has no click -wiring at all, so an author changing one series' mark can lose drill on the entire chart. -Pins that combo's current no-click-props state with a positive-control test (an -identically-shaped click on a plain bar chart still fires `onChartClick`). - -No renderer behaviour changes — `ComposedChart` still receives no click props. Whether -combo should drill is left open (objectui#4692). diff --git a/.changeset/combo-mark-drill.md b/.changeset/combo-mark-drill.md deleted file mode 100644 index 5feb5feaea..0000000000 --- a/.changeset/combo-mark-drill.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -'@object-ui/plugin-charts': patch ---- - -Combo charts drill from their marks - -objectui#4692, ruled Option B. `AdvancedChartImpl` built `cartesianClickProps` once and -applied it to exactly one element — the final cartesian `ChartComponent`. The `combo` -branch returns earlier, from its own `ComposedChart`, which was rendered with `data` and -no click props at all, so a combo chart fired `onChartClick` never: not on a mark, not on -the axis. Its marks are the same `Bar` / `Line` / `Area` components the drillable branch -renders. - -The trap that made this worth fixing rather than documenting is that the family is -**derived**, not only authored: `effectiveChartFamily` resolves a chart to `combo` -whenever its series declare different families (objectui#2945), so adding `type: 'line'` -to one series of a drillable bar chart silently turned that chart's drill-through off — -nothing in the authored spec said drill had been touched, and nothing errored. - -A combo's `Bar` / `Line` / `Area` marks now emit `{ category, categoryId, series, value }` -with the same semantics the plain cartesian branch gives, reusing the item-level -series-identity machinery from objectui#4672 / objectui#4682: the mark handler records the -series it was rendered with, the chart-level handler composes the one event, so a gesture -still produces exactly one `onChartClick`. Retyping one series now changes that series' -mark and nothing else. - -**Only the marks drill.** A click on a combo's plot surface or axis stays silent, where -the plain cartesian branch falls back to its axis-level answer. A combo plots several -measures on one plot, so a surface click there has no single series to report and the -fallback would have to invent one — the same reasoning objectui#4672's ruling gave the -pivoted case. Combo also carries no chart-wide pointer cursor for that reason; the -affordance sits on the marks that answer. - -Radar is now the one cartesian-adjacent family with no click wiring. The `onChartClick` -doc comment, corrected in objectui#4705 to say combo was a no-op, states the new rule and -its one deliberate exception. diff --git a/.changeset/component-input-type-union-arms.md b/.changeset/component-input-type-union-arms.md deleted file mode 100644 index 07286bf5dd..0000000000 --- a/.changeset/component-input-type-union-arms.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -'@object-ui/types': minor -'@object-ui/core': minor -'@object-ui/sdui-parser': minor -'@object-ui/components': minor -'@object-ui/plugin-detail': minor ---- - -`ComponentInput.type` can declare a UNION, so a block stops warning about legal -writes its own description recommends - -A registration's `type` was one coarse control kind, while a good number of spec -keys accept more than one shape. A declaration therefore had to pick an arm, and -the repo's own manifest gate then reported `type-mismatch` on the other arm's -legal values. Four of the five measured cases were the loud shape: the input's -`description` teaches the author to write an inline translation map -(`{ en, "zh-CN", … }`) while the same input's `type: 'string'` made -`sdui-parser`'s `checkType` warn about exactly that map — one platform authority -contradicting itself on the write it had just recommended. Because these land at -warning severity the page still compiled and rendered; the cost is that noise on -correct authoring trains authors, AI authors included, to dismiss the -`unknown-prop` and `type-mismatch` reports that are real. - -`type` now accepts an ARRAY of coarse kinds as well as a single one (maintainer -ruling on objectui#3832, direction (a)), and a value passes the coarse check when -ANY declared arm accepts it. Both declaration sites in `@object-ui/types` move -together with the registry's own copy in `@object-ui/core`, and -`ComponentInputSchema` enforces the same widening — a non-empty array of -DISTINCT kinds, so an empty arm list or a repeated arm is refused at authoring -time rather than normalized behind the author's back. - -Five declarations now spell their real contract, and the `type-mismatch` warning -on each of these legal writes is gone: - -- `page:header.title`, `page:header.subtitle`, `page:card.title` — - string **or** inline translation map (the spec's union, measured against - `ComponentPropsMap` at the pinned rc.6; the renderers resolve both through - `pickLocalized`); -- `record:alert.title`, `record:alert.body` — the same two shapes, justified - against the RENDERER since the pinned spec carries no `record:alert` props - schema; -- `element:text_input.defaultValue` — `string | number`, the spec's union, - which had been narrowed to `'string'` with the number arm named only in prose. - -**Backward compatible, and measured as such.** The single-kind form stays valid -and is still the canonical spelling for a one-arm key: it validates identically -(the diagnostics for one arm, `invalid-enum` and its `error` severity included, -are byte-identical), and `manifestFromConfigs` collapses a one-element array back -to the bare string, so every entry already in a published `sdui.manifest.json` -serializes unchanged and arrays appear only where a union was really declared. -The JSX authoring surface follows in the same step — `generateDts` emits a -TypeScript union for a union input, so the `.d.ts` an author type-checks against -accepts exactly what the gate accepts. - -A union widens what is legal; it does not switch the check off. A value matching -NO declared arm is still reported, a multi-arm mismatch reports at its strictest -arm's severity (`error` when an `enum` arm is present, so an enum's closed list -does not become dismissible by having a second arm added next to it), and arms -are meant to match the contract rather than relax the gate: -`element:text_input.defaultValue` deliberately gains no `object` arm because the -spec rejects a map there, and `element:record_picker.emptyText` keeps its single -`'string'` arm because that renderer drops the map form (objectui#4163) — an arm -the renderer never honours would advertise a shape that cannot reach the screen. diff --git a/.changeset/config-panel-footer-i18n.md b/.changeset/config-panel-footer-i18n.md deleted file mode 100644 index 7e8ae961c6..0000000000 --- a/.changeset/config-panel-footer-i18n.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@object-ui/components': patch -'@object-ui/i18n': patch ---- - -The config panel footer translates: `ConfigPanelRenderer`'s Save / Discard labels come from the locale pack. - -`saveLabel` and `discardLabel` carried the English literals `'Save'` and -`'Discard'` as parameter defaults, and no caller in the repo passes either prop, -so the sticky footer that appears the moment a config draft is dirty stayed -English in every locale — inside panels whose every other string had already -been routed through `t()`. The fix is in the renderer rather than per-caller: -the footer is the renderer's own chrome, so a caller-side fix would translate -one panel's footer and leave the next host's English. - -Both labels now resolve through `createSafeTranslation` — the mechanism this -package already uses for its built-in copy in `form.tsx`, -`fullscreen-editor.tsx`, `data-table.tsx` and friends. An explicitly passed -`saveLabel` / `discardLabel` still wins, unchanged and untranslated. - -`common.save` is reused rather than twinned: it already ships `Save` in all ten -packs and is what the console's other save buttons read. `common.discard` is -new, because the packs carried no shared spelling of the word — the three that -existed are each scoped to one surface (`form.discard`, -`console.settingsView.discard`, `console.objectView.discard`) and the last of -them diverges from the other two in zh/ko/fr. Its ten values are the majority -spelling, byte-identical to `form.discard` and `console.settingsView.discard`. - -Both English defaults are byte-identical to the literals they replace, so a -host that mounts no `I18nProvider` renders exactly what it did before. diff --git a/.changeset/console-app-list-route-comment-truth.md b/.changeset/console-app-list-route-comment-truth.md deleted file mode 100644 index 506b11e327..0000000000 --- a/.changeset/console-app-list-route-comment-truth.md +++ /dev/null @@ -1,17 +0,0 @@ ---- ---- - -Comments-only truth correction: six comments in the console app surface named the app -LIST endpoint with a plural-collection spelling that no request in this repo constructs -and that no framework route ledger declares as a row of its own. All six now name what -the console actually calls — the generic metadata list route `GET /api/v1/meta/:type`, -requested with the singular type segment `app` (`MetadataProvider` → -`client.meta.getItems('app')`) — and keep the per-session filtering claim, which -verification confirmed: `filterAppForUser` in `packages/rest/src/rest-server.ts` gates the -list inside that `:type` handler once the type segment resolves to `app`. Three of the six -are test-file rationale headers; every assertion is untouched, and one of them -(`appAccessProbe.test.ts`) already pinned the singular address in its own expectation, -which is what made the header's spelling demonstrably wrong. - -No behaviour changes: each touched file transpiles byte-identically with comments stripped -(objectui#4887). diff --git a/.changeset/console-index-fallback-matches-index-schema.md b/.changeset/console-index-fallback-matches-index-schema.md deleted file mode 100644 index cb3cd41dbf..0000000000 --- a/.changeset/console-index-fallback-matches-index-schema.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -The console's embedded index editor no longer offers controls for keys the spec removed. - -`index` is an embedded-only sub-type with no metadata type of its own, so `/meta` has no -slot to publish a schema for it and `EmbeddedItemEditor` ships a hand-copied one in -`FALLBACK_SCHEMAS.index`. That copy had drifted from `IndexSchema` -(objectstack-ai/objectstack#5247), and `@objectstack/spec` 17.0.0 turned the drift from a -silent bug into a hard failure: - -- **`type` ("Algorithm")** and **`partial`** were retired in spec 17.0.0 - (objectstack-ai/objectstack#5248, ADR-0049 enforce-or-remove) and are `retiredKey` - tombstones today — `IndexSchema` rejects them at any value, so every option of the - Algorithm select produced a 422 on the parent object save. Its `brin` option was never - in the spec's enum to begin with. -- **`where` ("Partial-index predicate")** was never a declared key — the spec's spelling - was `partial`, itself now retired — so an administrator's predicate was silently - stripped on every save. - -Both controls are removed. An index method is the driver/dialect's choice and a partial -index is built at the database layer by a runtime migration, so neither is a -declaration-surface concern. - -`unique` is converged onto the ADR-0120 scope union: the console can now author -`'organization'` (one holder per organization, NULL-safe) and `'global'`, instead of only -emitting the deprecated bare `true` that protocol 18 rejects. Indexes already carrying the -boolean keep rendering their existing control and are saved unchanged. diff --git a/.changeset/console-objectstack-spec-dist-hook.md b/.changeset/console-objectstack-spec-dist-hook.md deleted file mode 100644 index 7191fe0138..0000000000 --- a/.changeset/console-objectstack-spec-dist-hook.md +++ /dev/null @@ -1,24 +0,0 @@ ---- ---- - -Build tooling only (objectui#4854). `apps/console/vite.config.ts` now honours -`OBJECTSTACK_SPEC_DIST`, the spec twin of the existing `OBJECTSTACK_CLIENT_DIST` -hook, so a framework build can bundle the console against its own -`@objectstack/spec` instead of the last published one. Mechanism ruled on -objectstack#8134; the framework half (`scripts/build-console.sh`) lands after an -objectui SHA carrying this hook is pinned. - -No release: nothing under a published package's `src/` changed, and with the -variable unset the console's config is byte-for-byte the build it was before — -the alias table, the pre-bundle list, the `vendor-objectstack` chunk test and the -dev server's `fs.allow` all keep their baseline values. - -The client hook is one prefix alias, which is only safe because -`@objectstack/client` exports a single entry. `@objectstack/spec` publishes an -18-entry exports map that redirects every subpath into `dist/`, so the injection -is derived from the override's own map — one alias per entry, subpaths ahead of -the bare specifier — rather than from a hand-written rule. Every failure mode -(path absent, not the spec package, an exports entry naming a file the built -package does not contain, a wildcard pattern) throws with the offending value -named: a lenient fallback to the installed spec would silently rebuild the exact -skew the hook exists to end. diff --git a/.changeset/core-plugins-tooling-artifacts-out-of-dist-4836.md b/.changeset/core-plugins-tooling-artifacts-out-of-dist-4836.md deleted file mode 100644 index bf75dd297e..0000000000 --- a/.changeset/core-plugins-tooling-artifacts-out-of-dist-4836.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@object-ui/core': patch -'@object-ui/plugin-designer': patch -'@object-ui/plugin-grid': patch -'@object-ui/plugin-view': patch ---- - -Four packages stop publishing tooling material in their `dist/` - -Each of these packages spelled its build exclusions as `*.test.*`, while this repo's tooling convention is a directory one — `__tests__` / `__mocks__` / `__benchmarks__`, exactly as `TOOLING_FILE` in `scripts/check-phantom-dependencies.mjs` spells it. Any tooling file whose *name* is not `*.test.*` therefore stayed in the emit program and shipped in the tarball. This is the same shape and the same cause as objectui#4006, which fixed `@object-ui/fields` and `@object-ui/plugin-editor` by the filename criterion and so did not reach these four. - -Measured by building each package from a cleared `dist/` on both sides of the change. Nine files disappear, none appears, and every surviving file is untouched — the totals move by exactly the count removed: - -| package | `dist/` files | removed | -| --- | --- | --- | -| `@object-ui/core` | 176 to 174 | `dist/__benchmarks__/core.bench.js`, `core.bench.d.ts` | -| `@object-ui/plugin-designer` | 70 to 66 | `dist/__tests__/__mocks__/plugin-form.d.ts`, `plugin-grid.d.ts`, and both `.d.ts.map` | -| `@object-ui/plugin-grid` | 62 to 60 | `dist/__tests__/explainDouble.d.ts` and its `.d.ts.map` | -| `@object-ui/plugin-view` | 13 to 12 | `dist/__tests__/explainDouble.d.ts` | - -Only `@object-ui/core`'s had runtime weight. The other eight are declarations nothing resolves, but `core.bench.js` is a real emitted module whose first import is `import { bench, describe } from 'vitest'` — a runtime import of a package a consumer never installs, since `vitest` is a devDependency of `@object-ui/core` and devDependencies are not installed transitively. Nothing resolves it today either (it is not in the `exports` map), so no consumer breaks in either direction; this is the tarball shedding files nothing reached. - -No type coverage leaves with the emit. The three plugins' helper and mock files are already program inputs of the `tsconfig.test.json` that each package's `type-check` chains, reached through the imports in the suites beside them — `tsc --listFiles` names all four files on both sides of the change. `core.bench.ts` had no such edge, since nothing imports a benchmark, so it is now named explicitly in `packages/core/tsconfig.test.json`. That move was deliberate rather than forced: `scripts/check-type-check-coverage.mjs` enumerates `*.test.ts(x)` only, so a benchmark that no program reads is invisible to it, and dropping the coverage silently would have been the "coverage that was right by accident" objectui#4006 recorded. Verified by appending a provably-false annotation to the benchmark, which turns `tsc -p packages/core/tsconfig.test.json` red at exit 2. diff --git a/.changeset/create-plugin-jest-dom-range-4968.md b/.changeset/create-plugin-jest-dom-range-4968.md deleted file mode 100644 index 957db8e70f..0000000000 --- a/.changeset/create-plugin-jest-dom-range-4968.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@object-ui/create-plugin': patch ---- - -A scaffolded plugin's generated manifest now asks for the same `@testing-library/jest-dom` range this repo installs. - -`src/templates.ts`'s `DEV_DEPENDENCIES` had fossilised one patch behind the repo -root: the template said `^7.0.0` while the root manifest had moved to `^7.0.1`. -`templates.test.ts`'s anchor rule caught it and was red on `main`. - -Same defect class, same day and same dependabot wave as the `lucide-react` drift -in `@object-ui/cli`'s app generator, so both templates move together here — which -is how the previous occurrence of this incident was handled too (objectui#4098 / -PR objectui#4099 moved these same two templates in one PR). This one came from -the dev-dependencies group bump rather than the single-package bump, and it was -found only because the two ratchets live in different packages: the anchor rule -throws on its first mismatch, so nothing reports the second template until the -first is green. - -The remaining seven anchored ranges in this template were swept against the same -wave and are all in sync. diff --git a/.changeset/dashboard-config-panel-i18n.md b/.changeset/dashboard-config-panel-i18n.md deleted file mode 100644 index 7e6b44f004..0000000000 --- a/.changeset/dashboard-config-panel-i18n.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@object-ui/plugin-dashboard': patch -'@object-ui/i18n': patch ---- - -The dashboard config sidebar translates: `WidgetConfigPanel` and `DashboardConfigPanel` are wired through `t()`. - -Both panels build a `ConfigPanelSchema` — breadcrumb, section titles, field -labels, placeholders, help text and option labels — and neither imported a -translation hook at all, so all 61 of their user-visible strings were English -literals. Both are exported from the package barrel and mounted by -`DashboardWithConfig` as the dashboard editing sidebar, so a user on any -non-English console opened a panel that stayed English inside chrome that had -translated around it. - -They now resolve through a new `dashboard.config.*` namespace — 75 keys, added -to all ten locale packs. The namespace sits beside `dashboard.trend.*` and -`dashboard.filters.*`, which is where this package's other translated surfaces -already read from, and the panels reach it through -`useConfigPanelTranslation`, a `createSafeTranslation` hook whose -`CONFIG_PANEL_DEFAULT_TRANSLATIONS` map carries the English defaults for hosts -that mount no `I18nProvider`. - -The keys are authored fresh against the wording the panels actually ship rather -than restored from the retired `configPanel.*` block: that vocabulary had no -reader, was never validated against a shipped label, and covered 16 of the 61 -strings. Where the two name the same word the translations are reused. - -Every `en` pack value and every built-in default is byte-identical to the -literal it replaces, so English rendering and provider-less rendering are -unchanged — asserted row by row, in both directions, against a frozen table of -the pre-change literals. diff --git a/.changeset/data-objectstack-files-drops-src-4847.md b/.changeset/data-objectstack-files-drops-src-4847.md deleted file mode 100644 index ab6d8835c2..0000000000 --- a/.changeset/data-objectstack-files-drops-src-4847.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@object-ui/data-objectstack': patch ---- - -`@object-ui/data-objectstack` stops publishing its `src/` tree - -The manifest's `files` array listed `src` alongside `dist`, so every published tarball carried all 43 source files — 38 of them `*.test.ts`. It had been that way since the package's first commit (`780a1b993`), never added for a consumer, and objectui#4006 recorded the same shape without acting on it: its scope was the `*.test.d.ts` half that the build program emitted into `dist`, and its own triage note graded this half as tarball weight rather than a break. - -Nothing in the published surface reached those files, which is why no consumer changes in either direction. Measured on a cleanly rebuilt `dist`, all four ways in are closed: the `exports` map has one entry (`.`) and every condition under it targets `dist`; `main` / `module` / `types` are `./dist/index.js`, `./dist/index.js`, `./dist/index.d.ts`; the repo and the docs teach only the root specifier, and no `@object-ui/data-objectstack/src/...` deep import exists anywhere (the `src` paths in sibling `vite.config.ts` / `vitest.config.mts` files are workspace aliases resolved through `path.resolve()` against the source tree, which no `files` array shapes); and the tarball holds no sourcemap that could point back at `src`, since `tsup.config.ts` sets `sourcemap: false` and its bundled `dts` writes no `.d.ts.map` — the built `dist` contains four files, zero `.map` among them, and zero occurrences of `sourceMappingURL` or `../src/`. - -`npm pack --dry-run` across the change, on the same `dist`: - -| | before | after | -| --- | --- | --- | -| entries | 51 | 8 | -| unpacked | 1356830 B | 719876 B | -| tarball | 393379 B | 222157 B | - -43 files leave, none arrives, and every surviving entry is byte-identical apart from the edited `package.json`: `dist/index.{js,cjs,d.ts,d.cts}`, `README.md`, `CHANGELOG.md`, `LICENSE`, `package.json`. The 43 are the 38 tests plus the five modules they cover (`index.ts`, `errors.ts`, `metadata-client.ts`, `userState.ts`, `cache/MetadataCache.ts`), whose published form remains the bundled `dist/index.js`. diff --git a/.changeset/dataset-widget-chart-bucket-drill-render-count.md b/.changeset/dataset-widget-chart-bucket-drill-render-count.md deleted file mode 100644 index 90c59d9d7f..0000000000 --- a/.changeset/dataset-widget-chart-bucket-drill-render-count.md +++ /dev/null @@ -1,9 +0,0 @@ ---- ---- - -Test-only: fixed a flaky overshoot in `plugin-dashboard`'s DatasetWidget chart-bucket -drill test, where `waitFor` pinned a cumulative drill-filter render count that a late, -unrelated dimension-metadata re-render could push past 1 with no way to recover -(objectui#4718, same defect class as objectui#4706 / PR #4708). The assertion now waits -for the drill to have opened at least once and keeps its content check (which records -the drawer filtered on) as the substantive assertion. No published behaviour changes. diff --git a/.changeset/dataset-widget-dotted-dimension-race.md b/.changeset/dataset-widget-dotted-dimension-race.md deleted file mode 100644 index db6f134b84..0000000000 --- a/.changeset/dataset-widget-dotted-dimension-race.md +++ /dev/null @@ -1,7 +0,0 @@ ---- ---- - -Test-only: fixed a flaky race in `plugin-dashboard`'s DatasetWidget dotted-dimension -tests, where `waitFor` was bound to a rendered cell while the assertion checked a -separately-resolving metadata fetch recording (objectui#4487). No published behaviour -changes. diff --git a/.changeset/dataset-widget-drill-title-render-count.md b/.changeset/dataset-widget-drill-title-render-count.md deleted file mode 100644 index 59e3c817ee..0000000000 --- a/.changeset/dataset-widget-drill-title-render-count.md +++ /dev/null @@ -1,9 +0,0 @@ ---- ---- - -Test-only: fixed a flaky overshoot in `plugin-dashboard`'s DatasetWidget drill-title -tests, where `waitFor` pinned a cumulative drawer-render count that a late, unrelated -dimension-metadata re-render could push past 1 with no way to recover (objectui#4706). -The assertions now wait for the drawer to have opened at least once and keep their -content check (the drawer title/filter) as the substantive assertion. No published -behaviour changes. diff --git a/.changeset/dead-i18n-namespaces-configpanel-renderer.md b/.changeset/dead-i18n-namespaces-configpanel-renderer.md deleted file mode 100644 index bb31bfc3de..0000000000 --- a/.changeset/dead-i18n-namespaces-configpanel-renderer.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -'@object-ui/i18n': patch ---- - -Delete two dead i18n namespaces — `configPanel.*` (16 keys) and `renderer.*` (13 keys) — from all ten locale packs. - -290 translated strings (29 keys x 10 packs) with **no reader anywhere in the -repo**. Measured with `node scripts/check-i18n-dead-keys.mjs`, the reverse sweep -from objectui#4658: it subtracts the referenced key set (the AST walk that -`check-i18n-call-site-keys.mjs` already uses, plus plural suffixes, plus -`returnObjects` branches, plus every dynamic template head) from the pack key -set, then re-checks each survivor with a fixed-string grep over the whole repo. -Both namespaces scored a clean sweep — `configPanel.*` 16/16 CONFIRMED and -`renderer.*` 13/13 CONFIRMED, zero NEEDS-REVIEW, and a fixed-string grep for all -29 keys returns nothing at all outside `packages/i18n/src/locales/`. - -`configPanel.*` is a dashboard-widget config-panel vocabulary (`layout`, -`columns`, `gap`, `rowHeight`, `refreshInterval`, `appearance`, `theme`, -`general`/`advanced`, …). The two components that surface exactly that -vocabulary — `packages/plugin-dashboard/src/WidgetConfigPanel.tsx` and -`DashboardConfigPanel.tsx` — import no translation hook at all and hardcode the -same words in English (`title: 'Layout'`, `label: 'Columns'`, `label: 'Gap'`, -`label: 'Show description'`, `label: 'Theme'`). The two config panels that do -use i18n read other namespaces: `ViewConfigPanel.tsx` reads -`console.objectView.*`, `ReportConfigPanel.tsx` reads `common.*` and -`report.editor.*`. No component reads `configPanel.*`. - -`renderer.*` is SchemaRenderer placeholder/status vocabulary (`noPageSchema`, -`noFormSchema`, `noDashboardSchema`, `pageRendering`, `dashboardRendering`, -`formRenderingMode`, …). It is dead in the stronger sense: the English strings -have no hardcoded twin either — `No page schema provided`, `No form schema -provided`, `No dashboard schema provided`, `Page rendering`, `Dashboard -rendering` and `Form rendering in` each return zero hits repo-wide outside the -packs, and `packages/react/src/SchemaRenderer.tsx` imports no translation hook. -The messages themselves are gone from the product, not merely un-translated. - -No behaviour change is possible: a key with no reader cannot be read. No test -fixture pinned any of the 29 keys, and neither i18n baseline JSON names one, so -nothing else had to move. - -Part of #4730. diff --git a/.changeset/dead-i18n-namespaces-workflow-publicform-demo.md b/.changeset/dead-i18n-namespaces-workflow-publicform-demo.md deleted file mode 100644 index 3626c83f13..0000000000 --- a/.changeset/dead-i18n-namespaces-workflow-publicform-demo.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@object-ui/i18n': patch ---- - -Delete two dead i18n namespaces — `workflow.*` (58 keys) and `publicForm.demo.*` (36 keys) — from all ten locale packs. - -940 translated strings (94 keys x 10 packs) with **no reader anywhere in the -repo**. Measured with `node scripts/check-i18n-dead-keys.mjs`, the reverse sweep -from objectui#4658: it subtracts the referenced key set (the AST walk that -`check-i18n-call-site-keys.mjs` already uses, plus plural suffixes, plus -`returnObjects` branches, plus every dynamic template head) from the pack key -set, then re-checks each survivor with a fixed-string grep over the whole repo. -`workflow.*` scored 54/58 CONFIRMED with the other 4 appearing only in two i18n -test fixtures — mentions, not consumers; `publicForm.demo.*` scored 36/36 with -zero textual footprint anywhere outside the packs. - -`workflow.*` is a complete BPMN/workflow-designer vocabulary (`userTask`, -`serviceTask`, `parallelGateway`, `boundaryEvent`, `importBpmn`/`exportBpmn`, -`undo`/`redo`, …). Its one plausible consumer, -`packages/plugin-designer/src/ProcessDesigner.tsx`, hardcodes English -(`{ label: 'User Task', value: 'user-task' }`) and imports no translation hook -at all, while six sibling components in the same package do use one — the -vocabulary was never wired up. `publicForm.demo.*` is demo content (contact and -support form titles, field labels, industry/issue-type/priority options) for a -public-form showcase page that does not exist in this repo. - -No behaviour changes: a key with no reader cannot be read. The `publicForm.*` -parent namespace and every other namespace are untouched. diff --git a/.changeset/dependabot-automerge-gate-4973.md b/.changeset/dependabot-automerge-gate-4973.md deleted file mode 100644 index a98dff81a1..0000000000 --- a/.changeset/dependabot-automerge-gate-4973.md +++ /dev/null @@ -1,35 +0,0 @@ ---- ---- - -CI + tooling + test only (objectui#4973). No published package changes: the files touched are -`.github/workflows/dependabot-auto-merge.yml`, a new `scripts/` helper with its pin test, and the -CI/CD guide page. - -`dependabot-auto-merge.yml` no longer runs `gh pr merge --auto --squash` unconditionally. `--auto` -lands the merge the moment GitHub considers the pull request mergeable — i.e. the moment the -*branch-protection required set* is satisfied, which is a different set from "the checks this -repository runs". On 2026-08-17 that difference put a red commit on `main`: #4959 merged at -08:13:36Z with nine of its nineteen check runs still in flight, and its shard 3/4 and shard 1/4 -then reported `failure` 5m25s and 8m20s later. The four-way test shard matrix is the slowest job -in the repository by construction (it exists to cut a ~9 minute wall clock), so it is the check -`--auto` systematically outruns; `main` went red for every parallel agent until #4968 repaired it, -the second such block in seven days (#4098). The channel was never specific to lockfile ranges — -any red on a slow job could ride it, which is #3523 and #3243 again. - -The wait is now explicit and this workflow owns it. `scripts/dependabot-merge-gate.mjs` polls the -Checks API for the pull request's head SHA and returns a verdict; approval and enqueue are both -behind `gate == 'green'`, and the merge is pinned to the judged SHA with `--match-head-commit`. A -required context that is missing, still running at the deadline, or anything other than `success` -is not green — nothing merges, the job goes red, and a PR comment names what refused. The semver -policy (patch/minor auto, major comment-only) is unchanged, and `--auto` is still the merge action -because an enforced merge queue rejects a direct merge with 405. - -`scripts/__tests__/dependabot-merge-gate.test.ts` replays #4959's measured check-run timeline and -asserts the counterfactual — `pending` at 08:13:36Z, `red` once shard 3/4 reports — and asserts -that the gate's three declared buckets partition exactly the check names that -`pull_request`-triggered workflows produce, so a renamed or added job fails a test instead of -quietly dropping out of the wait. - -Not addressed here, because it is a repository-**settings** surface this repository can neither -read nor change: the branch-protection / merge-queue required set itself, which provably contains -none of the four shards (a merge happened while all four were `in_progress`). diff --git a/.changeset/deregister-app-shell-component-key.md b/.changeset/deregister-app-shell-component-key.md deleted file mode 100644 index 2a58e1643f..0000000000 --- a/.changeset/deregister-app-shell-component-key.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -'@object-ui/layout': minor ---- - -**Breaking (shipped as `minor`, following the `page-header` `description` retirement):** -`app-shell` is no longer a component key. `registerLayout()` registered `AppShell` under -that key with no `inputs`; the registration is gone (objectui#4841, ADR-0049 -enforce-or-remove, remove side, maintainer ruling 2026-08-16). - -**What it could never do.** Four of `AppShellProps`' seven keys are `React.ReactNode` -slots — `sidebar`, `navbar`, `children`, `rightRail` — and a JSON document can fill none -of them, so `{ "type": "app-shell" }` resolved to a component that had exactly two -outcomes, neither of them a shell. `children` was dropped in silence: `SchemaRenderer` -strips `children` (and `body`) before spreading a node's keys as props, and `AppShell` -reads its `children` prop, never `schema.children`, so the `
` element rendered -empty with nothing logged. A schema written into `sidebar` / `navbar` / `rightRail` -arrived as a plain object React refuses to render, replacing the node with an error box. -Only `className`, `defaultOpen` and `branding` ever survived the JSON path, i.e. the best -result JSON could reach was a shell with no navigation, no top bar and an empty content -area. With no `inputs` declared, `sdui-parser` had no declaration face to compare a node -against either, so neither outcome was diagnosed. - -**What changes for an author.** The middle state — parses, resolves, renders nothing — is -replaced by a named refusal. `SchemaRenderer` now shows its `Unknown component type: -app-shell` panel (`OBJUI-001`) and `sdui-parser` reports an `error`-severity -`unknown-component` diagnostic before render. - -**FROM → TO.** There is no in-place rewrite, because the node never produced a shell. -Schema authors who want the whole shell from metadata use `app-schema-renderer` -(`AppSchemaRenderer`), which declares its `inputs` and builds branding and sidebar -navigation from an `AppSchema` document: `{ "type": "app-shell", … }` → -`{ "type": "app-schema-renderer", "schema": { … } }`. Everyone else composes in React — -`AppShell` is **unchanged and still exported** from `@object-ui/layout`, and remains the -way to build a shell and render JSON pages inside it. - -Repo-wide scan before removal found no `"type": "app-shell"` node anywhere in -`objectstack-ai/objectui` or `objectstack-ai/objectstack` at `origin/main` — no example, -catalog schema, fixture or template authored one. `content/docs/guide/layout.md` is -updated to state the new fact, and -`packages/layout/src/__tests__/app-shell-not-a-component-key.test.tsx` pins it on both -faces (source and live registry) plus the rendered diagnostic. diff --git a/.changeset/dirty-pumpkins-shout.md b/.changeset/dirty-pumpkins-shout.md deleted file mode 100644 index 9fda590a2d..0000000000 --- a/.changeset/dirty-pumpkins-shout.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -'@object-ui/core': minor -'@object-ui/app-shell': minor ---- - -Action param `visible`: one dialect answer, and a fault that is fail-open and LOUD - -`ActionParamDialog`'s `filterVisibleParams` was the last predicate face never -converted to the canonical entry. It evaluated each param's `visible` on a bare -`ExpressionEvaluator` inside `try { … } catch { return true }`, and that produced -two defects at once (objectui#4640, measured on `main`): - -- **Silence.** Three of the four fault shapes emitted nothing at all — an - unparseable source, an unbound identifier and a faulting legacy predicate all - resolved without a word, so a broken `visible` was indistinguishable from an - absent one. The standing 2026-08-06 ruling on objectui#4051 / - objectstack#5149 names silence as the one option that is not available. -- **The fail DIRECTION was decided by the predicate's dialect, not by the - surface.** A bare string ran the legacy JS evaluator (lenient → falsy → param - silently DROPPED); a `{ dialect, source }` envelope ran CEL (fault → param - silently KEPT). One `visible` key, two opposite outcomes, chosen by whether - the authored text happened to contain `${…}` / `===` — the objectui#3314 - shape. Both halves hurt: a dropped param means the dialog never collects a - value the server requires and the action fails at submit with nothing pointing - at the predicate; a kept one offers a field the backend rejects. - -`filterVisibleParams` now routes through `evalRowPredicate`. A param whose -`visible` cannot be evaluated is **shown**, and reported once, with the action -and the param named and the predicate quoted. Fail-open is the ruled direction -for this surface: an extra offered field is rejected by the server with a -message, while a silently hidden required param is undiagnosable. (Row surfaces -keep failing closed — there the harm runs the other way.) Boolean and blank -predicates are answered before the evaluator, so `visible: false` hides the -param and an empty predicate is not reported as broken. - -**Behaviour change worth knowing before you upgrade.** On the canonical CEL -engine an ABSENT key is a runtime fault, not a falsy read. A param gated on -`features.phoneNumber == true` in a deployment whose scope carries no -`phoneNumber` key at all now takes the fail-open branch: the param is SHOWN, -with a warning naming it, where it used to be hidden. The conservative outcome -is still available, in the spelling that is portable to the server's own engine: - -``` -has(features.phoneNumber) && features.phoneNumber == true -``` - -Deployments that DECLARE the flag (`features: { phoneNumber: false }`) are -unaffected — that is a genuine verdict on both engines, and it did not move. - -`@object-ui/core` gains the two evaluator changes this needed: - -- `evalRowPredicate` accepts **`rowless`** — "this surface has no row of its - own", so nothing is bound over the host scope and a `record` / `data` the - scope carries survives instead of being shadowed by an empty row. Row surfaces - are untouched: without the option the row is still the subject (objectui#3796). -- A faulting **`{ dialect, source }` envelope now reports its own source**. - It used to print the literal `"(expression)"`, and because the warn-once key - is (label, predicate), the first faulting envelope under a label silenced - every other one. The envelope is what `@objectstack/spec` normalizes every - authored predicate into, so this was the likeliest shape in served metadata - and the least diagnosable one. diff --git a/.changeset/div-deprecation-provenance-scope.md b/.changeset/div-deprecation-provenance-scope.md deleted file mode 100644 index 4e8cbbb0c1..0000000000 --- a/.changeset/div-deprecation-provenance-scope.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@object-ui/sdui-parser": patch -"@object-ui/components": patch ---- - -`div` 的废弃提示按 provenance 收窄:只对 **JSON 作者面**的节点报,不再对 `kind:'html'` tier 自己解析出的节点开火。 - -html tier 的页面是一段受限 JSX/Tailwind 文本,由引擎自己的解析器编译(只解析、不执行),标签名原样映射成节点 —— 作者在那一层写下的盒子标签,是该 tier 词表里的一等成员,**没有别的拼法可迁移**。提示照旧对他们开火,给的还是 JSON 作者面的替代建议:一条谁都无法执行的提示不是废弃,是噪声;它同时意味着这个类型永远退不掉,因为引擎自己的编译器一直在产出它。 - -判据是**来源**,由生产者确立:解析器给它产出的每个节点打一个 symbol 标记(`Symbol.for` 注册键),渲染器读这个标记。symbol 对 `JSON.stringify` / `Object.keys` / DOM 全部不可见 —— 所以它既不会落进被持久化的文档,也就无法被一份(手写或 AI 生成的)JSON 元数据复制回来给自己买到豁免;通过花括号属性夹带进来的 JSON **不打标记**,那部分本来就是手写的,建议对它成立。 - -迁移建议一字未改,JSON 作者面照旧每次模块加载报一次;提示文案现在写明它针对哪一个作者面。 diff --git a/.changeset/doc-component-type-ratchet-4823.md b/.changeset/doc-component-type-ratchet-4823.md deleted file mode 100644 index 65bd974184..0000000000 --- a/.changeset/doc-component-type-ratchet-4823.md +++ /dev/null @@ -1,32 +0,0 @@ ---- ---- - -Tooling + docs-only (objectui#4823). Every `type` string literal in a `content/docs/**.mdx` -code block must now name a component the repository actually registers, enforced by a new CI -gate — `pnpm check:doc-types`, `scripts/check-doc-component-types.mjs`, in its own -`doc-component-types.yml` workflow. - -The catalog side has had this ratchet since objectui#4616: -`examples/schema-catalog/test/catalog-gallery-render.test.tsx` renders every catalog entry and -fails if any paints the registry's "Unknown component type" panel (OBJUI-001). The teaching -surface had no equivalent — a fenced snippet in `content/docs/**` is not rendered, not parsed -and not compared against anything — so a page could teach a `type` that does not exist and -every check in the repo stayed green. The same defect landed three times on that surface -(objectui#4786 `stats-card`, objectui#4796 `plugin:grid` and `plugin:map`), each found by a -human probe rather than by a check. - -The registered-key universe is derived from the register calls themselves on every run — no -hard-coded list and no build step, so the gate is a checkout plus one `node` call and can -therefore run unfiltered, which matters because the change that introduces this defect is -docs-only and `ci.yml`'s gates skip those by design. - -The first full scan read 558 `type` literals across 143 pages against 661 derived keys and -found three more instances of the same shape, fixed here: `content/docs/utilities/runner.mdx` -and `content/docs/utilities/vscode-extension.mdx` taught `heading`, which nothing registers -(now `h1`, which `html-elements.tsx` registers and which renders the node's `children`), and -`content/docs/plugins/plugin-form.mdx` taught a `multi-step-form` type that appears nowhere in -the repo outside that snippet (now the `object-form` + `formType: 'wizard'` + `sections` shape -that `WizardFormSchema` itself declares). - -No published behaviour changes — repo tooling plus three documentation snippets — so this -declares "no release" rather than a bump. diff --git a/.changeset/drawer-field-group-sections-4774.md b/.changeset/drawer-field-group-sections-4774.md deleted file mode 100644 index d164d0dcbf..0000000000 --- a/.changeset/drawer-field-group-sections-4774.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@object-ui/plugin-form': patch ---- - -Fix: a `drawer` form with no `sections` now renders the object's declared -`fieldGroups` as sections, matching `ObjectForm` and `ModalForm`. - -`deriveFieldGroupSections` had exactly two call sites in the repo — -`ObjectForm` and `ModalForm` — so the same object, with the same metadata, -rendered one section per declared group in the modal create dialog and one -ungrouped flat list in the drawer. The author who laid the groups out in the -object designer saw them honoured on two surfaces out of three. - -`DrawerForm` now runs the same fallback the modal does: gated on "no explicit -`sections`, no `customFields`", over the same auto-layout-filtered field list -(system fields dropped, auto-generated fields dropped in create mode), with the -flat path's inferred column count carried onto the grouped layout. A curated -`sections` list from a form view still wins, and an object whose fields join no -declared group keeps its flat layout untouched. A derived group declaring -ADR-0085 `collapse` renders as a collapsible header, like an authored one. diff --git a/.changeset/drawer-flat-field-rules-4755.md b/.changeset/drawer-flat-field-rules-4755.md deleted file mode 100644 index 7f9cde5dfa..0000000000 --- a/.changeset/drawer-flat-field-rules-4755.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@object-ui/plugin-form': patch ---- - -Fix: a `drawer` form with no `sections` now honours the object's field-level -conditional rules (`visibleWhen` / `readonlyWhen` / `requiredWhen`) and field -`group`. - -`ModalForm` and `DrawerForm` each carried their own copy of the "object-schema -field to runtime FormField" loop for the no-sections case, and the drawer's -copy had fallen behind: it stopped at `multiple`, so the ADR-0036 predicates -never reached the runtime field and `resolveFieldRuleState` had nothing to -resolve. A hidden field rendered anyway, a frozen field stayed editable (with -the server then dropping the write), and a conditionally-required field never -blocked the submit. - -Both containers now build that list through one shared `buildFlatFields`, which -resolves each field through the same `fromObjectSchema` the sectioned path uses -— so the next field-mapping fix lands once and reaches every container. diff --git a/.changeset/drop-stale-eslint-disable-directives.md b/.changeset/drop-stale-eslint-disable-directives.md deleted file mode 100644 index afc92527c1..0000000000 --- a/.changeset/drop-stale-eslint-disable-directives.md +++ /dev/null @@ -1,23 +0,0 @@ ---- ---- - -Comment-only cleanup (objectui#4833). Removed all 49 `eslint-disable` directives that ESLint -itself reports as `Unused eslint-disable directive (no problems were reported from 'X')` — -suppressions whose underlying finding no longer exists, spread over 35 files in 17 packages. - -No published behaviour changes: the diff removes comment lines and nothing else. The single -line that is modified rather than deleted is `apps/console/src/pages/developer/PublicFormsPage.tsx:151`, -where the directive sat inline inside a statement, so only the comment was stripped and the -`useEffect` call itself is byte-identical. - -The judgement was never ours: every site was taken from ESLint's own `ruleId: null` report, -and the whole-repo counts reconcile exactly — warnings 9828 to 9779 (−49, one per directive), -errors 0 to 0, unused directives 49 to 0, and **no other rule's count moved in either -direction**. That last figure is what proves the deletions were inert: had any directive still -been load-bearing, removing it would have surfaced the rule it was suppressing. - -Twenty of the 49 sat on `no-console`, which this repo's config sets to `error` (the objectui#4029 -ratchet) — they were dead because that rule is configured `{ allow: ['warn', 'error'] }` and the -calls beneath them are `console.warn` / `console.error`, or because the file is one the config -turns `no-console` off for. A stale suppression on an error-level ratchet is the kind of comment -a later reader copies as precedent, which is why they are worth removing rather than tolerating. diff --git a/.changeset/element-text-button-i18n-arms-4970.md b/.changeset/element-text-button-i18n-arms-4970.md deleted file mode 100644 index 7fadd87f5c..0000000000 --- a/.changeset/element-text-button-i18n-arms-4970.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -'@object-ui/components': minor -'@object-ui/plugin-detail': minor ---- - -`element:text.content` and `element:button.label` declare the inline translation -map they already accept - -Two more instances of the contradiction objectui#3832 fixed the mechanism for, -measured after that ruling had fixed its scope at five specimens and filed -separately as objectui#4970. Both inputs' own `description` tells the author to -write an inline translation map (`{ en, "zh-CN", … }`), both renderers resolve one -through `pickLocalized`, and both spec props schemas accept one — while the -declaration said `type: 'string'`, so the manifest gate reported -`type-mismatch` on the exact shape the block had recommended. Both blocks are in -`PUBLIC_BLOCKS`, so this reached authors through `sdui.manifest.json` and -`sdui-intrinsics.d.ts` as well as the save gate. - -Each declaration is now `type: ['string', 'object']`, the union form -objectui#3832 introduced, and the arms are the ones the contract accepts — -re-measured on the `@objectstack/spec` 17.0.0 GA pin rather than carried over -from the issue, which was written at the 17.0.0-rc.6 pin: -`ComponentPropsMap['element:text'].content` and -`ComponentPropsMap['element:button'].label` are both -`string | Record< string, string >`, and both refuse a number, a boolean and an -array. Those three refusals are the controls in the acceptance test, which is -what keeps a widening distinguishable from a silenced check. - -Nothing else about the two blocks moves. A plain-string `content` / `label` -validates exactly as before, values matching neither arm are still reported, and -no other manifest entry changes shape — the public manifest now carries seven -array-valued input types, the five from objectui#3832 plus these two, with the -remaining 57 public blocks serializing byte for byte as they did. - -`record:alert`'s renderer-local prop type is corrected in the same pass -(`plugin-detail`): its `title` / `body` were still typed `string` while the same -file resolves both through `pickLocalized` and the block's published `inputs` -have declared `['string', 'object']` since objectui#3832, so the two slots were -narrower than both the renderer and the block's own published surface. The type -is not exported, so no consumer was misled and no published surface changes. The -CTA's `action.label` one level down is left alone on purpose (objectui#4998): -`action` is published as a bare `object` whose member shape lives in prose, so -there are no declared arms for it to be aligned against yet. diff --git a/.changeset/engine-i18n-carveout-recorded.md b/.changeset/engine-i18n-carveout-recorded.md deleted file mode 100644 index 1784dfa100..0000000000 --- a/.changeset/engine-i18n-carveout-recorded.md +++ /dev/null @@ -1,13 +0,0 @@ ---- ---- - -Docs only (objectui#4662): records the `engine.*` carve-out where the i18n -conventions live — `packages/i18n/README.md` gains a "Scope" section stating -that the metadata-admin (Studio) namespace resolves through a module-local -`en`/`zh` table rather than the ten locale packs, why that is Phase 3f design -rather than drift (the server's `/meta/types` `label` is the primary path; the -table is the fallback), the two consequences (eight locales render English -there; the i18n gates cannot see the namespace by construction), and the -condition for revisiting it. `packages/app-shell/src/views/metadata-admin/i18n.ts` -gains a header note pointing at that section — a comment-only edit. No key was -migrated, no locale pack was touched, and no published behaviour changes. diff --git a/.changeset/fields-files-drops-src-4856.md b/.changeset/fields-files-drops-src-4856.md deleted file mode 100644 index b45bfadded..0000000000 --- a/.changeset/fields-files-drops-src-4856.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@object-ui/fields': patch ---- - -`@object-ui/fields` stops publishing its `src/` tree - -The manifest's `files` array listed `src` alongside `dist`, so every published tarball carried all 173 source files — 97 of them `*.test.tsx` / `*.test.ts`. It has been that way since the file's first commit (`780a1b993`), where `files` was already `["dist", "src", "README.md"]` while `exports` named only `dist`, so the entry was never added for a consumer. objectui#4006 recorded this exact shape and did not act on it: its scope was the `*.test.d.ts` half the build program emitted into `dist`, and it noted in passing that the test sources were already in the tarball by this other route. - -Nothing in the published surface reached those files, which is why no consumer changes in either direction. Measured on a cleanly rebuilt `dist`, all four ways in are closed: the `exports` map has two entries and both target `dist` (`.` resolves `types` / `import` / `require` to `./dist/index.d.ts` / `./dist/index.js` / `./dist/index.cjs`, and `./style.css` to `./dist/index.css`); `main` / `module` / `types` are `./dist/index.cjs`, `./dist/index.js`, `./dist/index.d.ts`; no deep import into this package exists anywhere in the repo or the docs — the one that used to, `@object-ui/fields/widgets/MarkdownContent`, was ruled out by objectui#4325 precisely because a package's surface is its index, and the `../fields/src` paths in sibling `vite.config.ts` files are workspace aliases resolved through `resolve()` against the source tree, which no `files` array shapes; and the tarball holds no sourcemap that could point back at `src`. That last one is the check that had to be measured rather than assumed, because this package emits its declarations through `vite-plugin-dts` rather than the `tsup` of objectui#4847 or the bare `tsc` of `@object-ui/types`: a clean rebuild writes 78 files into `dist`, of which zero are `.d.ts.map`, zero are `.js.map`, zero contain a `sourceMappingURL` comment and zero mention `../src`. `src/index.css` is an input to `scripts/build-css.mjs`, not an output anyone resolves; the sheet the `./style.css` export names is the built `dist/index.css`, which still ships. - -`npm pack --dry-run` across the change, on the same `dist`: - -| | before | after | -| --- | --- | --- | -| entries | 255 | 82 | -| unpacked | 2265557 B | 841772 B | -| tarball | 629843 B | 252364 B | - -173 files leave, none arrives, nothing outside `src/` moves, and every surviving entry is byte-identical apart from the edited `package.json` itself. The 173 are the 97 tests plus 76 implementation modules, whose published form remains the bundled `dist/index.js` / `dist/index.cjs` and the 75 declaration files beside them. - -`@object-ui/types` keeps its `src` entry for now, and that is a different judgement rather than an omission: it builds with a bare `tsc` under a `declarationMap` / `sourceMap` config with no `inlineSources`, so its shipped `dist/*.d.ts.map` name `../src/*.ts` with no embedded content and dropping `src` there would leave published maps pointing at files the tarball no longer carries. That trade-off is filed as objectui#4851. diff --git a/.changeset/filter-builder-between-pair-and-operator-labels.md b/.changeset/filter-builder-between-pair-and-operator-labels.md deleted file mode 100644 index 4825d2c66f..0000000000 --- a/.changeset/filter-builder-between-pair-and-operator-labels.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -'@object-ui/components': patch -'@object-ui/plugin-list': patch -'@object-ui/app-shell': patch -'@object-ui/i18n': patch ---- - -Console list filters: a `between` range is submitted only when both bounds are filled, and six operator labels stop rendering as raw i18n keys. - -Two defects in the list-view filter panel (objectstack#8815), both in the Console -render layer, with no workaround available downstream. - -**A half-filled range no longer refuses the whole view.** Picking a date column -and 「介于」 draws two inputs — that part landed in objectui#3958 — but typing -only one bound produced `["2024-01-01", ""]`, and both write paths read "is this -row filled in?" with one shape-blind predicate (`null` / `''` / empty array). -An array of length 2 passed it, so the empty bound went to the server, which -refuses the query outright (`400 INVALID_FILTER`): the list showed -「该视图的查询被拒绝」 and the filters the user had already applied stopped -applying too. The saved-view fold persisted the same half-range, so the refusal -came back on every later read of that view, for every user of it. - -The spec cannot intercept this — `ViewFilterRuleSchema` accepts -`["2024-01-01", ""]` because it counts the two slots rather than what is in -them, while refusing a scalar or a one-element array. Authoring validation is -therefore green on exactly the shape that fails at query time, which makes not -emitting it the producer's job. `@object-ui/components` now exports -`isFilterValueComplete(operator, value)` — arity-aware, so a `pair` row needs -both bounds — and the two consumers that had each kept a copy of the old -predicate (`plugin-list`'s `convertFilterGroupToAST`, `app-shell`'s -`foldFilterGroupToSpecRules`) read it instead. A half-filled range is now -dropped exactly as a half-typed `equals` row already was: no filter, rather than -a filter the server will reject. Bounds of `0` and `false` stay real bounds. - -**Six operator labels are translated in all ten locale packs.** -`startsWith`, `endsWith`, `isNull`, `isNotNull`, `exists` and `notExists` were -missing from every pack, so i18next resolved them to the raw key and the dropdown -showed `filterBuilder.operators.isNull` beside translated entries. The -component's own defaults table could not cover it: that table serves only the -no-provider path, and the Console mounts a provider. The report named four — -a `date` column's bucket offers the four nullness operators; a `text` column -showed all six. - -Because the label key is built dynamically (`t(\`filterBuilder.operators.${op}\`)`), -no existing gate could see the gap: the call-site checker classifies a template -key as `missing-prefix` and only asks whether the prefix resolves, and -cross-pack parity is satisfied when all ten packs are missing a key together. -A new parity test pins the packs against `FILTER_BUILDER_OPERATORS` in both -directions, so an operator added to the dropdown now fails loudly until every -pack labels it. diff --git a/.changeset/filter-builder-falsy-values-and-strict-numeric-reads.md b/.changeset/filter-builder-falsy-values-and-strict-numeric-reads.md deleted file mode 100644 index 6c87ac3cfb..0000000000 --- a/.changeset/filter-builder-falsy-values-and-strict-numeric-reads.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -'@object-ui/components': patch ---- - -`FilterBuilder` shows the falsy values a row actually holds — a boolean `false` and a number `0` are values, not empty boxes. - -The value controls asked `!condition.value` and `String(condition.value || "")`, -which folds `false` and `0` in with the rows nobody has filled in yet. Both rows -saved, persisted and filtered by their value the whole time; only the control -said otherwise: - -- a boolean column filtered `equals false` snapped back to the **Select value** - placeholder the moment the user clicked **False**, while the row carried - `value: false`; -- a number column filtered `equals 0` showed an empty box — and typing `0` into - one looked like the keystroke had never landed, because the row took the value - and the very next render blanked the input; -- a single-select whose option id is `0` showed the placeholder too, even though - the same control's multi-select branch already drew that option as checked. - -"No value" is now one judgement (`undefined` / `null` / `''`), read by every -value control and by the two helpers that already spelled it out correctly, so -"not picked yet" and "picked False" stay two distinguishable states rather than -trading places. - -The three keyed numeric paths — the token input's commit, a range bound, and the -single value input — no longer read with `parseFloat(raw) || 0`, which takes half -of `"42abc"` and turns `"acme"` into `0`: a filter the user never wrote. All -three now use the same strict reading a field switch uses, so this component -holds one answer to "is this string a number" instead of a strict one and a -lenient one. An unreadable entry becomes an unfilled value, except in the token -input, which declines the commit and leaves the text in the draft box to be -fixed. No behaviour a user can reach today changes: those inputs are -``, which never hands a non-numeric string to the component -in the first place — this closes the drift, before a text box, a formula or a -paste path opens it. diff --git a/.changeset/filter-builder-field-switch-resets-operator.md b/.changeset/filter-builder-field-switch-resets-operator.md deleted file mode 100644 index b2dbd1e0b2..0000000000 --- a/.changeset/filter-builder-field-switch-resets-operator.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -'@object-ui/components': patch ---- - -`FilterBuilder` settles a row's operator when its **field** changes, instead of leaving an operator the new field's dropdown does not list. - -The operator buckets are per field type and they do not nest: a `select` column -offers `in` / `notIn`, a `text` column offers none of them, and only a date -column offers `between`. Changing a row's field wrote `{ field }` alone, so the -operator survived into a bucket that no longer contained it. Radix's -`SelectValue` matches against the `SelectItem`s actually mounted, so the -operator trigger rendered **blank** — while the row went on filtering by an -operator the user could neither see nor reach, and could only clear by deleting -the row. - -Changing the field is now one edit with the operator and the value's shape, the -same way objectui#3958 / PR #4762 made changing the operator one edit with the -value's shape: - -- an operator the new field's bucket still offers is **kept** — switching - `contains` from one text column to another must not silently become `equals`, - and the value it carries is left alone; -- one the new bucket cannot offer is replaced by that bucket's **first** entry, - and the row's `value` is then re-shaped for the family it lands in — a list - under `in` collapses to its first entry under `equals`, a `between` range - keeps its lower bound, an untouched `[]` becomes `''`. - -Membership is decided through the spec's own `normalizeFilterOperator`, the fold -`filterValueArity` already uses, so a stored rule that reaches the builder -spelled `not_in` is recognised as the operator the dropdown lists as `notIn` and -is not reset out from under the author. The fold is injective over this -builder's whole operator vocabulary, which is what makes comparing through it -safe; a test pins that, and fails the day an added operator would break it. diff --git a/.changeset/filter-builder-field-switch-retypes-value.md b/.changeset/filter-builder-field-switch-retypes-value.md deleted file mode 100644 index 4f31f3f964..0000000000 --- a/.changeset/filter-builder-field-switch-retypes-value.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -'@object-ui/components': patch ---- - -`FilterBuilder` settles a row's **value** when its field changes, instead of leaving a value the new column's input cannot show. - -objectui#4768 / PR #4779 settled the row's operator on a field switch and -re-shaped the value only when the operator's family changed — scalar to scalar -has no shape question, so what the user typed was carried through on purpose. -But the field's **type** changed too, and the value input is redrawn from it: a -browser renders a non-numeric value in `` as **blank**. A -`text` row filtered `equals "acme"`, pointed at a number column, showed an empty -box while the row went on carrying `"acme"` — `foldFilterGroupToSpecRules` -persisted it and the live grid queried `amount equals "acme"`. The same -invisible-value shape as objectui#4768, one column over. - -Changing the field is now one edit with the operator, the value's shape **and** -the value's type. Convertible values are carried, the rest clear to the family's -empty shape (scalar `''`, list `[]`, range `[]`): - -- `"42"` on a number column becomes the number `42`; `"acme"`, `"42abc"` and - `"1,000"` clear. The reading is deliberately stricter than `parseFloat`, which - would turn `"acme"` into `0` — a filter the user never wrote; -- `"true"` / `"false"` convert on a boolean column, and a boolean becomes - `"true"` / `"false"` on a text column, so the round trip closes; `1` and - `"yes"` are conventions rather than readings, and clear; -- date-like columns take only what their own input can render, plus the one - truncation that loses nothing it could have shown (`"2024-03-05T14:30"` → - `"2024-03-05"` on a date column). A bare date does **not** gain a midnight to - fit a `datetime` column: `equals 2024-03-05T00:00` is a filter that looks - answered and matches almost nothing; -- a value the new column can already hold is left alone — switching between two - text columns, or two numeric ones, still keeps what the user typed, and an - unfilled row stays unfilled. - -The convertibility judgement is defined once, next to `reshapeFilterValue`, -and `getInputType` now reads the same family table it does — so the type a value -is converted **to** and the input it is edited **in** cannot drift apart. diff --git a/.changeset/filter-builder-icontains-operator.md b/.changeset/filter-builder-icontains-operator.md deleted file mode 100644 index 1725df48b9..0000000000 --- a/.changeset/filter-builder-icontains-operator.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@object-ui/components': patch -'@object-ui/fields': patch -'@object-ui/i18n': patch ---- - -FilterConditionField can author the spec's `$icontains` — case-insensitive contains is reachable from the filter UI. - -`@objectstack/spec`'s `FieldOperatorsSchema` gained `$icontains` between -`17.0.0-rc.2` and `rc.5`, and every driver and evaluation face the platform -ships now executes it. `FilterConditionField` had no builder operator that could -author it, so the capability was unreachable from the sharing-rule criteria -builder and sat in that widget's parity test as an explicit `KNOWN_UNREACHABLE` -entry. - -The FilterBuilder gains a `containsCaseInsensitive` operator ("Contains (ignore -case)", translated in all ten locale packs). `condToMongo` emits -`{ field: { $icontains: value } }` and `kvToCondition` reads it back, so a saved -criteria reopens in the visual builder instead of falling into the raw-JSON -editor. Today's `contains` is unchanged and still emits the case-SENSITIVE -`$contains`; whether it should have been case-insensitive all along is a product -question that stays open, and stored filter views keep meaning what they meant. - -The fold is ASCII-only by contract — `café` does not match `CAFÉ`. - -The new operator is **opt-in per consumer**: `FilterBuilder` takes an -`extraOperators` prop, and only `FilterConditionField` passes it. The one -dropdown feeds three at-rest dialects and only the MongoDB-style criteria this -widget writes can carry the operator — the spec's `VIEW_FILTER_OPERATORS` (saved -views) and `VALID_AST_OPERATORS` (the live grid's filter AST) have no -case-insensitive contains, so offering it there would author a filter those -paths cannot execute. Every other FilterBuilder is unchanged. diff --git a/.changeset/filter-builder-like-ilike-ruling-harvest.md b/.changeset/filter-builder-like-ilike-ruling-harvest.md deleted file mode 100644 index c7e805e97c..0000000000 --- a/.changeset/filter-builder-like-ilike-ruling-harvest.md +++ /dev/null @@ -1,18 +0,0 @@ ---- ---- - -Test-comment only: harvests the maintainer ruling on objectui#4911 into the -`KNOWN_UNREACHABLE` entry for `$like` / `$ilike` in -`FilterConditionField.operators.test.ts`. The entry's justification was landed as a CITED -OPEN QUESTION ("undecided — see #4911") to unblock the queue while the authoring-surface -call was pending; it is now rewritten as the decision it became — ruled B on 2026-08-17, -the visual FilterBuilder deliberately does not offer raw pattern-matching authoring, the -constrained intents (`contains` / `containsCaseInsensitive` / `startsWith` / `endsWith`) -are the authorable surface, and the API surface is unaffected since spec goes on accepting -both operators for hand-written ObjectQL and direct JSON authors. The ruling's named -reopen condition (a real user or deployment asks to author wildcard patterns in the UI) is -recorded on the entry, because the exclusion ratchet can only check that a member is still -a spec operator, never that its reason is still the true one. - -The two `KNOWN_UNREACHABLE` members, the reachability sweep and the exclusion ratchet are -unchanged; no operator was added or removed. Declared as releasing nothing. diff --git a/.changeset/filter-builder-set-operator-value-shapes.md b/.changeset/filter-builder-set-operator-value-shapes.md deleted file mode 100644 index 55d1f2f5d0..0000000000 --- a/.changeset/filter-builder-set-operator-value-shapes.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -'@object-ui/components': patch -'@object-ui/i18n': patch ---- - -`FilterBuilder` gives the set and range operators an input that matches the value shape the spec accepts, and stops minting the shape it refuses. - -Three independent paths let one filter row end up with `operator: 'in'` and a -SCALAR `value` — the shape `ViewFilterRuleSchema` refuses at save time since -objectstack#6227, and the shape the query path answered `400 INVALID_FILTER` on -before that (objectstack#5869): - -- Changing the operator dropdown wrote `{ operator }` alone, so the seed `''` - (or whatever the previous family had produced) survived the switch into - `in` / `not_in` / `between`. The operator and the shape of its value are one - edit, so they are now made together: switching families re-shapes the value — - a typed scalar becomes a one-element list, an empty one becomes `[]`, a range - keeps its first bound and leaves the second open, and a list collapsing to a - scalar keeps its first entry. -- A plain text or number column has no static `options`, so `in` fell through to - the single-value input and the user could only ever type a scalar into it. - Those columns now get a token input (type, Enter or comma commits, `×` or - Backspace removes) that always emits an array; `between` gets its two bounds - instead of one box. The lookup picker's no-DataSource fallback, which also - handed back a scalar while `multiple`, emits a list too. -- The multi-value families were decided from a local `["in", "notIn"]` literal, - already one spelling adrift: `notIn` is an alias and the canonical member is - `not_in`, so a stored view read back in canonical form got the single-value - input for a set operator. The families are now read from `@objectstack/spec`'s - exported `VIEW_FILTER_LIST_VALUE_OPERATORS` / `VIEW_FILTER_PAIR_VALUE_OPERATORS` - and folded through `normalizeFilterOperator`, so both spellings of one operator - get one answer and a family the spec widens is picked up without an edit here. - -`foldFilterGroupToSpecRules` is unchanged and needed no change: it normalizes the -operator and carries `value` through verbatim, so the shape that reaches storage -is the producer's to get right. An untouched `in` row arrives as `[]`, which the -fold's existing incomplete-row rule already drops. - -Four locale keys are added to all ten packs for the new inputs -(`filterBuilder.addValue` / `.removeValue` / `.rangeStart` / `.rangeEnd`). diff --git a/.changeset/flow-resume-flow-failed-terminal.md b/.changeset/flow-resume-flow-failed-terminal.md deleted file mode 100644 index 553e2d906a..0000000000 --- a/.changeset/flow-resume-flow-failed-terminal.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -A flow-run failure that arrives as `400 FLOW_FAILED` — and any `404` on the flow route — is now classified as terminal rather than retryable. - -`interpretFlowResponse` tested `!res.ok` first and returned `retryable: true` for -everything it caught, so every non-2xx was treated as a transport failure. That -flag is what `FlowRunner` reads to decide whether to keep the wizard dialog open: -a transport failure did not consume the suspension, so retrying the same run is -meaningful, while a flow failure is terminal because the engine consumes the -suspension before running downstream nodes (resume-once) and a retry can only -reach "No suspended run". - -Two classes were landing on the wrong side of that line: - -- **`400` + `FLOW_FAILED`** — the flow ran and failed. objectstack#8684 moves this - exact event off `200 {data:{success:false}}` onto a real status code, inheriting - the objectstack#3962 ruling that business failures must not ride HTTP 200 inside - a double envelope. Landing this read **first** is the maintainer's explicit - sequencing (ruling of 2026-08-15, sub-decision 3): had the backend flipped - first, a terminal node failure would have kept the wizard open offering a retry - guaranteed to fail. Forward-compatible — nothing sends that body yet, so the arm - is dormant until it does. Only this code qualifies; the route's other 400s - (`INVALID_SIGNAL`, `INVALID_SCREEN_INPUT`) are refused before the signal reaches - the engine's variable map, so the suspension survives and a corrected resubmit - stays meaningful. -- **`404`** — no such suspended run, or no such flow. This half is **not** dormant: - the route already answers `404 No such suspended run` today, and each one had - been keeping the wizard open for a retry that could only 404 again. Keyed on the - status alone, since a proxy or unmounted-route 404 carries no envelope to read a - code from. - -The flow's authorable `errorMessage` is preferred again on the failing path. The -error envelope carries no `data`, so it is read from `error.details` — the -envelope's own declared carrier for structured extras, and the only slot that -survives to the wire once `splitSemanticCode` promotes `details.code`. Absent, the -message degrades to the envelope's own `error.message`, never to silence. diff --git a/.changeset/flowcanvas-network-double.md b/.changeset/flowcanvas-network-double.md deleted file mode 100644 index 80d443c32d..0000000000 --- a/.changeset/flowcanvas-network-double.md +++ /dev/null @@ -1,7 +0,0 @@ ---- ---- - -Test-only: stub `fetch` for the `automation/actions` endpoint in -`FlowCanvas.test.tsx` so the suite no longer escapes to the real network -(`ECONNRESET`/"socket hang up" noise from happy-dom's own `http`-backed fetch -polyfill); no published behaviour changes. diff --git a/.changeset/flowcanvas-seeds-network-double.md b/.changeset/flowcanvas-seeds-network-double.md deleted file mode 100644 index d929a26153..0000000000 --- a/.changeset/flowcanvas-seeds-network-double.md +++ /dev/null @@ -1,7 +0,0 @@ ---- ---- - -Test-only: stub `fetch` for the `automation/actions` endpoint in -`flow-canvas-seeds.spec-parse.test.tsx` so the suite no longer escapes to the -real network (`ECONNRESET`/"socket hang up" noise from happy-dom's own -`http`-backed fetch polyfill); no published behaviour changes. diff --git a/.changeset/form-data-source-wiring-reads-core-reference-family.md b/.changeset/form-data-source-wiring-reads-core-reference-family.md deleted file mode 100644 index 230dc712b8..0000000000 --- a/.changeset/form-data-source-wiring-reads-core-reference-family.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -'@object-ui/components': patch -'@object-ui/core': patch ---- - -A `user` field in a form now receives `dataSource` / `dependentValues` / `dependsOnLabels`, like every other reference field. - -The form renderer decided which registered widget gets those three props from a -module-private `DATA_SOURCE_FIELD_TYPES` set, while `@object-ui/core` kept -`EXPANDABLE_FIELD_TYPES` for the same underlying fact — a field whose stored -value is a foreign key into another object. The core side's TSDoc claimed to -mirror the form's set, and it did for 15 days: the form's copy then gained -`capability-multiselect` (objectui#2403) and the three widget-hint pickers -`object-ref` / `filter-condition` / `recipient-picker` (objectui#2421) on the -same day, after which the two sets were not in a subset relation in either -direction — `user` only in core, the picker names only in the form — with -nothing able to report it. - -The form now derives its rule instead of restating it: the reference half is -core's set, the form-specific half is the three picker names, which are widget -hints and can never be a declarable field `type`. Adding a member to -`EXPANDABLE_FIELD_TYPES` therefore also grants it the form's data-source wiring; -that coupling is intended and is now written down on both sides. - -The user-visible half is `user`. It previously received none of the three props. -`dataSource` and `dependentValues` each have a `SchemaRendererContext` fallback -inside the widget, so the person picker limped along wherever a provider -happened to supply one; `dependsOnLabels` has no fallback, so a -dependency-gated user picker interpolated the raw API name into its -"select ... first" hint in every locale — the leak objectstack#5407 closed for -lookups and left open here. The widget contract's own `dataSource` doc has -always named `user` among the types the form renderer injects for. - -No change to what is expanded, projected or rendered anywhere else: the core -set's members are untouched. diff --git a/.changeset/form-submit-redirect-in-shell-4190.md b/.changeset/form-submit-redirect-in-shell-4190.md deleted file mode 100644 index 6ab4d01c50..0000000000 --- a/.changeset/form-submit-redirect-in-shell-4190.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@object-ui/console": patch ---- - -`FormPage`'s post-submit `redirect` behaviour now consumes the destination the way objectstack#7496 ruled it (objectui#4190): as a **relative in-app path**, navigated to with the router, with `{{record.field}}` interpolation URL-escaped when the redirect is built — and an out-of-contract destination refused on screen instead of followed. - -The url was previously handed to a browser-level, full-page navigation exactly as authored. Two consequences, both fixed here: - -- **A ruled in-app path left the app.** A full-page navigation does not see React Router's `basename`, so on a console served under a mount — which the framework CLI configures for every embedded deployment — an authored `/objects/lead` resolved against the origin root and dropped the submitter out of the SPA. Both mounts of this renderer (`/f/:slug` and `/forms/:name`) live inside the console's router, so the destination is now a router navigation and the mount is applied by the router itself. `withConsoleBase()` is deliberately not used: it prefixes anything not already targeting another absolute SPA mount, so it would have mangled an absolute destination rather than fixing it. -- **`{{record.field}}` tokens were never substituted.** The ruled shape accepts them and assigns the substitution — and the URL-escaping of every interpolated value — to the moment the redirect is built, which is here. The scope is the record the submit just wrote (values as submitted, with whatever the server echoed back layered over them, and the id read by the same one rule the `created-record` behaviour uses). - -The shape verdict is not restated in this app: `resolveSubmitRedirect` asks `@objectstack/spec`'s own `FormViewSchema` at the moment of use, so an absolute URL, a protocol-relative `//host`, a backslash, a control-character smuggle, a malformed token or a document-relative path is refused with the spec's own author-facing prescription, and a later widening of the ruling is followed by the pin rather than by an edit here. A refusal confirms the submit — the write succeeded, only the destination was out of contract — and shows the reason, rather than leaving the submitter watching a redirect that must not happen. - -`delayMs` semantics are unchanged. The wait now lives in an effect tied to the component, so a submitter who navigates away during the delay is no longer yanked back by a timer that outlived the page. diff --git a/.changeset/form-view-retired-key-reads.md b/.changeset/form-view-retired-key-reads.md deleted file mode 100644 index 4f2435f7a8..0000000000 --- a/.changeset/form-view-retired-key-reads.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@object-ui/react': patch ---- - -SpecBridge's form-view bridge stops reading `defaultSort` and `aria`, two keys spec 17 -retired on the FormView carrier (`retiredKey()` tombstones — authoring either is a parse -error). Both guards were unreachable for any FormView that passed validation, and both -ends were measured dead before removal: no form renderer reads either off the node -(`plugin-form` reads neither, and `SchemaRenderer`'s ARIA injection resolves flat -`ariaLabel`/`ariaDescribedBy`/`role`, never a nested `aria` object). Behaviour for -spec-valid metadata is unchanged; a host that fed the exported bridge a raw pre-17 -document no longer gets these two keys copied onto a node slot nothing consumed. The -LIST view's `aria` pass-through is untouched — that carrier stayed live and is applied by -`ListView`. diff --git a/.changeset/formula-column-sort-header-3950.md b/.changeset/formula-column-sort-header-3950.md deleted file mode 100644 index a36c78f8a9..0000000000 --- a/.changeset/formula-column-sort-header-3950.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@object-ui/core': patch -'@object-ui/plugin-grid': patch -'@object-ui/plugin-detail': patch -'@object-ui/plugin-list': patch ---- - -Grid and related-list column headers no longer offer a sort on a `formula` column. - -A `formula` value is computed on read: no driver materialises a column for it, so -a server `$orderby` naming one has nothing to order by. That sort never worked. -Until objectstack#6994 the platform did not say so — the response carried the very -values it had been asked to order by, out of order, under a `200`, with ascending -and descending byte-identical on a real SQL driver — and it now answers -`400 INVALID_SORT`. So the header was wrong before the platform's refusal and is -wrong after it, for the same reason: it offers a sort that cannot be performed. - -`ObjectGrid` withheld the affordance only from reference-bearing columns -(objectui#3096). Unmaterialized types are a SECOND reason a server sort is -impossible, not a different mechanism, so it now reads both — and so do the two -sort entry points of a related list (the embedded table's headers and the -sort-button row a `data-list` card keeps), which each derived that rule -separately. - -Client-side sorting is deliberately unchanged. There the rows are all in the -browser and the formula value is the one the server hydrated on read, so ordering -by what the cell shows is honest — the same split the relational carve-out makes. -A sort DECLARED in view metadata is also unchanged: it still goes out and is still -refused by name, because silently dropping an author's declaration would hide the -authoring error instead of surfacing it (the toolbar's sort picker keeps such a -field listed for exactly that reason — it is the only way to remove it). - -The membership — `formula` alone — moved out of a private set in `ListView` into -`@object-ui/core` (`UNMATERIALIZED_FIELD_TYPES` / `isUnmaterializedFieldType`), -bound to `@objectstack/spec`'s own storage predicate so the renderer cannot drift -from what the drivers actually store. It is deliberately narrower than the spec's -write contract `COMPUTED_VALUE_TYPES`: a `summary` and an `autonumber` each get a -real maintained column and sort correctly, and withholding their headers would -have broken two affordances that work. diff --git a/.changeset/four-plans-refuse.md b/.changeset/four-plans-refuse.md deleted file mode 100644 index 7dd7922946..0000000000 --- a/.changeset/four-plans-refuse.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@object-ui/app-shell': patch ---- - -`RecordDetailView`'s `type: 'api'` action handler now refuses to dispatch a -record-scoped mutation when the record cannot supply the declared -`recordIdParam` key, instead of sending the request anyway with the -parameter silently dropped (objectstack#8018, objectui#4669). - -The seeding read routes through the shared `resolveRecordIdParamSeed` -helper (`@object-ui/core`, already adopted by `useConsoleActionRuntime` in -objectui#4670) so both refusal wordings — an absent key vs. a `null` -value, which point at different repairs — are worded consistently across -call sites. This call site's extra fallback sources (a literal `recordId` -override, and the page-record fallback `record_header` actions rely on -when no row is stashed) are preserved; the refusal only fires once every -source has been tried and none can supply the key. - -Behaviour change worth noting: an action that previously dispatched an -under-specified request now fails visibly instead. That is the point — the -old path could not report the failure it was causing. diff --git a/.changeset/fullscreen-dialog-validation-and-name-4824.md b/.changeset/fullscreen-dialog-validation-and-name-4824.md deleted file mode 100644 index 5424acea69..0000000000 --- a/.changeset/fullscreen-dialog-validation-and-name-4824.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -'@object-ui/components': minor -'@object-ui/fields': patch ---- - -The fullscreen long-text dialog announces the field's validation state and carries the field's name - -objectui#4824, objectui#4832. - -`mobile.fullscreenLongText` is a shipped opt-in, and with it on the phone user -edits long text in this dialog and nowhere else. Measured on all three surfaces -that render the dialog — `TextAreaField`, `RichTextField`, and the form -renderer's built-in `textarea` branch — with the field genuinely invalid at that -moment: - -``` -INLINE richtext aria-invalid= true -DIALOG richtext aria-invalid= false aria-describedby= null -INLINE textarea aria-invalid= true -DIALOG textarea aria-invalid= null aria-describedby= null -``` - -and the accessible name of every dialog control empty, against `F` on every -inline one. The rich-text row is the sharp half: the dialog was not silent about -the failure, it was announcing the OPPOSITE of the inline control for the same -field at the same moment, because `RichTextEditorSurface` computed -`aria-invalid={!!error}` from an `error` prop the dialog rendering never -received. 3 surfaces, 3 broken, one cause: the dialog's control is built from -scratch by the host, so none of the wiring the inline control gets from the form -renderer reaches it. - -**Answered once, in the primitive.** `FullscreenEditor` now takes the field's -`error` and owns what the dialog does with it: it renders the message in a -dialog-local node, and hands `children` a required fourth argument — a -spreadable set of DOM attributes — carrying `aria-labelledby` (the dialog -title's text, i.e. the field label #3393 already put there), `aria-invalid`, and -`aria-errormessage` naming that node. The host spreads it; the host never learns -an id, so it cannot name the wrong node, cannot compose the attributes subtly -wrong, and cannot compute its own `aria-invalid` from a prop it forgot to plumb. -Three hosts hand-answering this is the shape that produced three identical -holes. - -**On objectui#3222's "the text belongs to `FormMessage`".** The maintainer's -ruling of 2026-08-16 restates that rule as what it was always protecting — -only one copy of the error text is in the accessibility tree at any moment — -which the dialog-local node satisfies: it exists only while the dialog is open, -and for exactly that window Radix `aria-hidden`s everything outside the modal, -`FormMessage` included. The shortcut of pointing the dialog control's -`aria-describedby` / `aria-errormessage` at the host's `FormMessage` id is -forbidden rather than merely unused: it resolves to a node that is `aria-hidden` -for the whole time the reference is live (an ARIA MUST violation), and neither -happy-dom nor jsdom can see the difference — which is why every new pin asserts -that the named node is inside THIS dialog, not merely that it exists. - -`aria-errormessage` carries a single IDREF and is emitted only alongside -`aria-invalid="true"`. It is deliberately not folded into the host's -`aria-describedby` chain, which on the textarea surface already carries the -fullscreen character counter's sentence. - -**The name reuses the visible title rather than minting a second author for it** -(#3978): `aria-labelledby` points at a span inside `DialogTitle`, not at -`DialogTitle` itself — Radix renders the title as `h2` with the id its own -`DialogContent` `aria-labelledby` names, so putting an id on it would buy the -control a name at the cost of the dialog's. - -**Breaking (shipped as `minor`, see below), `@object-ui/components` only.** -`FullscreenEditorProps.error` is REQUIRED, not optional, and -`FullscreenEditorProps['children']` takes a fourth argument. - -FROM → TO for an out-of-repo host: - -``` - - {(draft, setDraft, disabled) =>