From 61c04f1bcff083ce175440010ac4ea9a69974d32 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:28:49 +0000 Subject: [PATCH] chore: release packages --- .changeset/action-locations-one-rule.md | 42 - ...am-reference-is-the-authorable-spelling.md | 55 -- .../actiondef-spec-owned-keys-explicit.md | 51 -- .changeset/app-shell-spec-symbol-burn-down.md | 52 -- .changeset/approval-band-quorum-progress.md | 44 -- ...-components-react-spec-symbol-burn-down.md | 57 -- .../binding-reach-lazy-candidate-coverage.md | 46 -- .changeset/bulk-action-aggregate-execution.md | 36 - ...ci-cd-pipeline-doc-matches-the-workflow.md | 36 - .changeset/core-spec-symbol-burn-down.md | 54 -- .changeset/cross-repo-issue-closer.md | 31 - .../dashboard-date-filter-preset-default.md | 56 -- .../dashboard-date-filter-unknown-value.md | 55 -- .changeset/dataset-percent-scale.md | 35 - .../datasource-preview-drop-read-replicas.md | 24 - .../decision-default-path-is-the-edge.md | 29 - .../decision-output-def-collapses-to-spec.md | 38 - .../entitlement-dialog-i18n-and-envelope.md | 24 - .changeset/fix-cross-repo-closer-require.md | 18 - ...rm-designer-speaks-the-project-language.md | 24 - .changeset/grid-search-is-not-page-scoped.md | 42 - .changeset/inverted-pin-burndown-probes.md | 45 -- ...-view-embeddable-form-datasource-bridge.md | 35 - .changeset/lookup-search-focus-trap.md | 25 - .changeset/metadata-fallback-render-loop.md | 28 - .changeset/nav-action-dispatch.md | 39 - .changeset/objectstack-family-rc1-lockstep.md | 45 -- .changeset/public-block-binding-reach.md | 37 - .../record-activity-feed-has-a-source.md | 59 -- .changeset/record-block-record-reach.md | 62 -- .changeset/reject-decision-one-dialog.md | 22 - .../required-is-presence-not-truthiness.md | 55 -- .../retired-action-keys-unauthorable.md | 27 - .../root-screenshot-dash-flag-ignore.md | 24 - .../script-node-converged-to-function.md | 39 - .../spec-rc1-wait-timeout-fields-retired.md | 61 -- .changeset/spec-symbol-ledger-batch-1.md | 48 -- ...eline-honours-its-configured-date-field.md | 30 - ...pes-page-nav-misc-spec-symbol-burn-down.md | 66 -- apps/console/CHANGELOG.md | 172 +++++ apps/console/package.json | 2 +- packages/app-shell/CHANGELOG.md | 726 ++++++++++++++++++ packages/app-shell/package.json | 2 +- packages/auth/CHANGELOG.md | 68 ++ packages/auth/package.json | 2 +- packages/cli/CHANGELOG.md | 21 + packages/cli/package.json | 2 +- packages/collaboration/CHANGELOG.md | 14 + packages/collaboration/package.json | 2 +- packages/components/CHANGELOG.md | 290 +++++++ packages/components/package.json | 2 +- packages/core/CHANGELOG.md | 488 ++++++++++++ packages/core/package.json | 2 +- packages/create-plugin/CHANGELOG.md | 2 + packages/create-plugin/package.json | 2 +- packages/data-objectstack/CHANGELOG.md | 64 ++ packages/data-objectstack/package.json | 2 +- packages/fields/CHANGELOG.md | 81 ++ packages/fields/package.json | 2 +- packages/i18n/CHANGELOG.md | 64 ++ packages/i18n/package.json | 2 +- packages/layout/CHANGELOG.md | 66 ++ packages/layout/package.json | 2 +- packages/mobile/CHANGELOG.md | 74 ++ packages/mobile/package.json | 2 +- packages/permissions/CHANGELOG.md | 14 + packages/permissions/package.json | 2 +- packages/plugin-ai/CHANGELOG.md | 29 + packages/plugin-ai/package.json | 2 +- packages/plugin-calendar/CHANGELOG.md | 35 + packages/plugin-calendar/package.json | 2 +- packages/plugin-charts/CHANGELOG.md | 31 + packages/plugin-charts/package.json | 2 +- packages/plugin-chatbot/CHANGELOG.md | 31 + packages/plugin-chatbot/package.json | 2 +- packages/plugin-dashboard/CHANGELOG.md | 116 +++ packages/plugin-dashboard/package.json | 2 +- packages/plugin-designer/CHANGELOG.md | 36 + packages/plugin-designer/package.json | 2 +- packages/plugin-detail/CHANGELOG.md | 129 ++++ packages/plugin-detail/package.json | 2 +- packages/plugin-editor/CHANGELOG.md | 29 + packages/plugin-editor/package.json | 2 +- packages/plugin-form/CHANGELOG.md | 160 ++++ packages/plugin-form/package.json | 2 +- packages/plugin-gantt/CHANGELOG.md | 34 + packages/plugin-gantt/package.json | 2 +- packages/plugin-grid/CHANGELOG.md | 208 +++++ packages/plugin-grid/package.json | 2 +- packages/plugin-kanban/CHANGELOG.md | 34 + packages/plugin-kanban/package.json | 2 +- packages/plugin-list/CHANGELOG.md | 140 ++++ packages/plugin-list/package.json | 2 +- packages/plugin-map/CHANGELOG.md | 29 + packages/plugin-map/package.json | 2 +- packages/plugin-markdown/CHANGELOG.md | 29 + packages/plugin-markdown/package.json | 2 +- packages/plugin-report/CHANGELOG.md | 117 +++ packages/plugin-report/package.json | 2 +- packages/plugin-timeline/CHANGELOG.md | 60 ++ packages/plugin-timeline/package.json | 2 +- packages/plugin-tree/CHANGELOG.md | 29 + packages/plugin-tree/package.json | 2 +- packages/plugin-view/CHANGELOG.md | 34 + packages/plugin-view/package.json | 2 +- packages/providers/CHANGELOG.md | 14 + packages/providers/package.json | 2 +- packages/react-runtime/CHANGELOG.md | 2 + packages/react-runtime/package.json | 2 +- packages/react/CHANGELOG.md | 146 ++++ packages/react/package.json | 2 +- packages/runner/CHANGELOG.md | 31 + packages/runner/package.json | 2 +- packages/sdui-parser/CHANGELOG.md | 38 + packages/sdui-parser/package.json | 2 +- packages/types/CHANGELOG.md | 360 +++++++++ packages/types/package.json | 2 +- packages/vscode-extension/CHANGELOG.md | 23 + packages/vscode-extension/package.json | 2 +- 119 files changed, 4108 insertions(+), 1636 deletions(-) delete mode 100644 .changeset/action-locations-one-rule.md delete mode 100644 .changeset/action-param-reference-is-the-authorable-spelling.md delete mode 100644 .changeset/actiondef-spec-owned-keys-explicit.md delete mode 100644 .changeset/app-shell-spec-symbol-burn-down.md delete mode 100644 .changeset/approval-band-quorum-progress.md delete mode 100644 .changeset/auth-components-react-spec-symbol-burn-down.md delete mode 100644 .changeset/binding-reach-lazy-candidate-coverage.md delete mode 100644 .changeset/bulk-action-aggregate-execution.md delete mode 100644 .changeset/ci-cd-pipeline-doc-matches-the-workflow.md delete mode 100644 .changeset/core-spec-symbol-burn-down.md delete mode 100644 .changeset/cross-repo-issue-closer.md delete mode 100644 .changeset/dashboard-date-filter-preset-default.md delete mode 100644 .changeset/dashboard-date-filter-unknown-value.md delete mode 100644 .changeset/dataset-percent-scale.md delete mode 100644 .changeset/datasource-preview-drop-read-replicas.md delete mode 100644 .changeset/decision-default-path-is-the-edge.md delete mode 100644 .changeset/decision-output-def-collapses-to-spec.md delete mode 100644 .changeset/entitlement-dialog-i18n-and-envelope.md delete mode 100644 .changeset/fix-cross-repo-closer-require.md delete mode 100644 .changeset/form-designer-speaks-the-project-language.md delete mode 100644 .changeset/grid-search-is-not-page-scoped.md delete mode 100644 .changeset/inverted-pin-burndown-probes.md delete mode 100644 .changeset/list-view-embeddable-form-datasource-bridge.md delete mode 100644 .changeset/lookup-search-focus-trap.md delete mode 100644 .changeset/metadata-fallback-render-loop.md delete mode 100644 .changeset/nav-action-dispatch.md delete mode 100644 .changeset/objectstack-family-rc1-lockstep.md delete mode 100644 .changeset/public-block-binding-reach.md delete mode 100644 .changeset/record-activity-feed-has-a-source.md delete mode 100644 .changeset/record-block-record-reach.md delete mode 100644 .changeset/reject-decision-one-dialog.md delete mode 100644 .changeset/required-is-presence-not-truthiness.md delete mode 100644 .changeset/retired-action-keys-unauthorable.md delete mode 100644 .changeset/root-screenshot-dash-flag-ignore.md delete mode 100644 .changeset/script-node-converged-to-function.md delete mode 100644 .changeset/spec-rc1-wait-timeout-fields-retired.md delete mode 100644 .changeset/spec-symbol-ledger-batch-1.md delete mode 100644 .changeset/timeline-honours-its-configured-date-field.md delete mode 100644 .changeset/types-page-nav-misc-spec-symbol-burn-down.md diff --git a/.changeset/action-locations-one-rule.md b/.changeset/action-locations-one-rule.md deleted file mode 100644 index 24465e26ba..0000000000 --- a/.changeset/action-locations-one-rule.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -"@object-ui/types": minor -"@object-ui/components": minor -"@object-ui/app-shell": minor ---- - -One placement rule for action `locations` (objectui#3142). - -**Breaking for metadata**: an action that declares no `locations` (missing key -or `[]`) no longer renders in a located surface. FROM: omitting `locations` -made an action appear on the list toolbar, the record header, and every -metadata-admin toolbar. TO: declare where it belongs — -`locations: ['record_header']` for the record header, `['list_toolbar']` for -the list toolbar, and so on. Nothing else changes; actions that already -declare a location are untouched. - -Four renderers each answered "where does an action with no `locations` go?" -differently — `action:bar` and metadata-admin showed it EVERYWHERE, -`page:header` showed it on the header, `action:group` showed it for -`undefined` but hid it for `[]` — while `ActionEngine`, `RecordDetailView`, -`DeclaredActionsBar`, the related-list bridge and the environment toolbar all -showed it NOWHERE. The same action therefore appeared or vanished depending on -which component happened to draw it. All eight now go through one exported -predicate, `actionRendersAt(action, location)` from `@object-ui/types`: an -action renders at a location only if it declares that location. - -The strict reading is the platform's own — ADR-0078 lists "an `action` with no -`locations`" as a verified inert shape, and the detail-page synthesizer already -documented "must include `locations: ['record_header']` to render". The -leniency contradicted both, and it is what let an aggregate-only bulk action -(objectui#3139) — one with no single-record placement by construction — mint a -list-toolbar button whose dispatch could only fail. - -Two placements are declared elsewhere and need no `locations`, both unchanged: -host-injected chrome in the `systemActions` / `headerSystemActions` slot (now -consistently exempt on `page:header` too, where it used to be filtered), and an -action named in a view's `bulkActions` / `bulkActionDefs`. - -Authoring side: Studio seeds `locations: ['record_header']` on a new action -instead of minting one that renders nowhere, and the action inspector says so -when no placement is ticked. The `ActionSchema.locations` JSDoc claimed a -`['record_header']` default that no renderer ever implemented — corrected. diff --git a/.changeset/action-param-reference-is-the-authorable-spelling.md b/.changeset/action-param-reference-is-the-authorable-spelling.md deleted file mode 100644 index 7a7ffdca43..0000000000 --- a/.changeset/action-param-reference-is-the-authorable-spelling.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -"@object-ui/types": minor -"@object-ui/app-shell": minor ---- - -`reference` is the one authorable action-param picker target (objectui#3174). - -**Breaking for authoring**: `ActionParam` in `@object-ui/types` no longer -declares the nine resolved-side picker keys — `referenceTo`, `displayField`, -`idField`, `descriptionField`, `titleFormat`, `lookupColumns`, `lookupFilters`, -`lookupPageSize`, `dependsOn`. FROM: `{ name: 'account_id', type: 'lookup', -referenceTo: 'account' }` type-checked. TO: `{ name: 'account_id', type: -'lookup', reference: 'account' }` — or make the param field-backed -(`{ field: 'account_id' }`) and it inherits the whole picker group from the -object field. - -The two halves of one contract disagreed about a spelling, and the type was the -half that was wrong. `resolveActionParams()` reads the spec's `reference` for an -inline `lookup`/`master_detail` target and nothing else; it EMITS -`ActionParamDef.referenceTo`, the resolved spelling. The public authoring type -declared the resolved spelling "for parity with the resolved shape", so an -author who followed it got a param whose picker target was dropped in the -resolver and a dialog that degraded to a plain record-id text input — asking a -human to paste a UUID. The dev warning that fired then told them to declare -`reference`, a key the type did not have. - -`reference` wins because the platform had already decided: `ActionParamSchema` in -`@objectstack/spec` is `.strict()`, lists `referenceTo` **by name** in its -alias map, and answers it with "use `reference`". So an authored `referenceTo` -was never storable — it was a hard parse rejection on the server while `tsc` -waved it through. Resolving it in objectui instead would have made the renderer -accept metadata the platform itself refuses, and such a param would work in a -locally-authored TS action and fail at publish; removing the declaration moves -the failure to where it can be fixed, at the authoring keystroke. - -- **`@object-ui/types`**: the nine keys are gone, and the rule they violated is - now pinned — `ActionParam` declares *exactly* the spec's authorable key set. - The drift guard names the single exception (`validation`, inert and rejected - by the same `.strict()` parse — filed as objectui#3201) so a second one cannot - appear without being a decision. -- **`@object-ui/app-shell`**: `resolveActionParams()` names any resolved-only - key it finds on an authored param in a dev-mode warning, with the - prescription (`referenceTo` → "use `reference`"; the rest → "make the param - field-backed"). It still does **not** read them. This covers the gap `tsc` - cannot gate — params authored in plain JS, loaded from JSON, or synthesised - at runtime — so the mistake is loud where it is made rather than surfacing - downstream as `paramToField()`'s "no reference target" warning naming a key - the author never wrote. - -The internal pipeline keeps its two spellings on purpose (authoring `reference` -→ `ActionParamDef.referenceTo` → the field's `reference_to`); what is pinned now -is that the public entry and the public exit agree. The end-to-end test authors -through the published `ActionParam` and follows one param to `reference_to` — -every previous test authored the resolver's own local input interface, which is -why the resolver only ever agreed with itself and the mismatch survived. diff --git a/.changeset/actiondef-spec-owned-keys-explicit.md b/.changeset/actiondef-spec-owned-keys-explicit.md deleted file mode 100644 index 5e6e09a4d8..0000000000 --- a/.changeset/actiondef-spec-owned-keys-explicit.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -"@object-ui/core": minor ---- - -Declare the 18 spec-owned action keys `ActionDef` had been absorbing silently. - -`ActionDef` ends with `[key: string]: any`, so it accepted any key of any type — -a typo (`targt`) and a retired spec key (`execute`) both type-checked, then the -runner silently bound no handler (the #2169 "Mark Done does nothing" shape). -Step 1 (objectstack#4075) made that audible with a dev-mode warning. This is -step 2: the keys the warning identified as legitimate are now real fields. - -- **18 keys promoted to explicit optional fields** — `ai`, `aria`, `bodyExtra`, - `bodyShape`, `bulkEnabled`, `component`, `icon`, `locations`, `mode`, - `objectName`, `order`, `recordIdField`, `recordIdParam`, `requiredPermissions`, - `requiresFeature`, `shortcut`, `variant`, `visible`. Every type is **derived** - from `@objectstack/spec`'s `ActionInput` (`SpecActionInput['locations']`, …), - never hand-copied: a hand-written duplicate of a spec shape is a second - contract that drifts, which is the failure this issue is about. Wrong-typed - values are now compile errors — `order: 'first'`, `variant: 'chartreuse'`, - `locations: ['nope']` — where before they were absorbed silently. -- **Derived from `z.input`, not `z.infer`.** `ActionSchema` is a `ZodPipe` whose - transform narrows `visible` from `string | { dialect, source }` to the - envelope alone. This runner consumes authored/stored rows, which are - rehydrated unparsed, so it sees the input shape; deriving from the inferred - `Action` would have rejected the raw-string predicate `ActionEngine` - explicitly supports. -- **Three `as any` casts deleted** in `ActionEngine` — `visible` and - `requiredPermissions` at the location filter, `locations` at registration. - They existed only because the fields were undeclared. -- **Four objectui-dialect keys marked `@deprecated`** with the spec spelling to - use instead — `actionType` (→ `type`), `api` and `endpoint` (→ `target`; - `executeAPI` already resolves `api || endpoint || target`), and `navigate` - (→ flat `target` / `openIn`). Only these four: the remaining dialect keys are - runner mechanics (chaining, toasts, post-execution reload/close) with no spec - counterpart, and pointing them at a spelling that does not exist would be - worse than leaving them declared. - -**Breaking edge, deliberate.** `shortcut` and `bulkEnabled` were retired by -`@objectstack/spec` 17 as `retiredKey()` tombstones (`z.never()`), so authoring -either is already a hard parse rejection. Deriving their types rather than -hand-writing them turns that runtime rejection into a **compile error**: code -that assigned `shortcut: 'ctrl+k'` to an `ActionDef` compiled before and does -not now. Such metadata was already refused by the platform — this only moves the -failure to where it can be fixed. A host may still pass either explicitly via -`ActionEngine.registerAction(action, { shortcut, bulkEnabled })`; only authored -metadata stopped carrying them. `bulkEnabled`'s replacement is the list view's -`bulkActions` / `bulkActionDefs`; `shortcut` has none. - -The index signature **stays** — removing it is step 3, and the inverted pin -asserting it is still present remains the issue's own completion check. diff --git a/.changeset/app-shell-spec-symbol-burn-down.md b/.changeset/app-shell-spec-symbol-burn-down.md deleted file mode 100644 index bd9ceab14e..0000000000 --- a/.changeset/app-shell-spec-symbol-burn-down.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -"@object-ui/app-shell": minor ---- - -Stop declaring 28 app-shell symbols under names `@objectstack/spec` owns -(objectui#3157, objectstack#4115 batch 3). - -**Breaking for importers of `@object-ui/app-shell`** — eight exported names -changed, because the spec exports the same name for a *different* thing: - -| was | now | what the spec's same-named export actually is | -|:--|:--|:--| -| `FieldInput` | `ScreenFieldInput` | the authoring shape of an object FIELD | -| `ConversationSummary` | `ConversationListItem` | the AI context-compaction record | -| `RuntimeConfig` | `AppShellRuntimeConfig` | the ENGINE runtime config | -| `PageHeaderProps` | `PageHeaderComponentProps` | the authored SDUI page-header schema | -| `FlowNode` / `FlowEdge` | `FlowDesignerNode` / `FlowDesignerEdge` | a COMPLETE authored flow node/edge | -| `PackageManifest` | `PackageManifestRow` | the full authored package manifest | -| `InstalledPackage` | `InstalledPackageRow` | the full install record | - -The object designer's `FieldGroup` also becomes `ObjectFieldGroup` — that is -the spec's own name for this exact shape, while its `FieldGroup` is the Studio -field-editor's group config. The other nineteen keep their names and are now -imported or derived from the spec instead of re-declared. - -**Three live defects the copies were hiding**, all fixed by importing the real -types: - -- `SchemaDiffEntryKind` was missing `index_mismatch` and `unmapped_index` - (framework#3728). The federation validate panel renders a label per kind from - a total map, so an index divergence — which the server already emits — arrived - as a diff row this UI could not name. The union is now the spec's, and the - compiler required the two missing labels. -- `ExplainLayer.contributors[].state` (`'active' | 'expired'`) did not exist in - the local copy of the access-explain report, so an EXPIRED permission-set or - position contribution rendered identically to a live one. -- `ExternalColumn.primaryKey` was optional locally while the server always sends - it (the spec schema defaults it), and `ExplainRecordAttribution.rules` / - `ExplainDecision.principal.positions` / `.permissionSets` were optional here - and required there — every reader carried a nullish branch that could not fire. - -The comment justifying the largest copy ("kept local so app-shell does not take -a build dependency on the framework spec package") was already false: -`@objectstack/spec` is a direct dependency of this package. - -Two symbols are derived structurally rather than re-exported, each with one -documented divergence pinned by a test: `ScreenSpec` keeps `fields` optional -(an `object-form` step legitimately sends none — #3528), and `DecisionOutputDef` -adds `required`, which the server enforces but the spec does not yet model. -Deriving the latter also narrowed its `type` from a bare `string` to the spec's -closed enum, so a typo'd picker kind now fails to compile instead of silently -degrading to a raw record-id text box (objectui#2955). diff --git a/.changeset/approval-band-quorum-progress.md b/.changeset/approval-band-quorum-progress.md deleted file mode 100644 index 73acf4d9c6..0000000000 --- a/.changeset/approval-band-quorum-progress.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -"@object-ui/app-shell": minor -"@object-ui/plugin-detail": minor -"@object-ui/react": minor -"@object-ui/i18n": minor ---- - -A record's approval band now shows the quorum / per-group tally the server already computes. - -The showcase's `showcase_committee_quorum` node declares `behavior: 'quorum'` with -`minApprovals: 2` over three approvers, and even ships a pre-rendered -`"Committee Sign-off (2 of 3)"` label; `showcase_expense_signoff` declares -`per_group` (会签) with named manager / finance groups. On the business record -the approval band rendered none of it — the lock badge, the recall button and -the approve/reject actions were all correct, but a two-of-three committee step -looked exactly like a one-approver step. An approver could not see whether their -own click finalized the node or was one of three, which is the single fact a -quorum node exists to express (objectstack#4478). - -Nothing was wrong on the wire, and nothing here papers over the server. The -framework computes `decision_progress` — `{ behavior, got, need, groups? }`, -derived from the node's own `node_config_json` snapshot, so the count a client -shows is the count the engine will enforce. **It attaches that block in -`getRequest` only**: `listRequests` deliberately skips it, because the -`sys_approval_action` tally it costs is per row and a list read may return -hundreds. The record header's `useRecordApprovals` reads -`GET /approvals/requests?object=…&recordId=…` — the list route — so the -enrichment was never in the payload it had. The hook now follows up with one -single read for the ONE pending row and folds the result onto it; a failed or -mismatched follow-up leaves the row exactly as the list sent it, so a display-only -enrichment can never take the approval panel down and no tally is ever invented. - -`InlineEditProvider` carries the block through as `approvalProgress`, and the -DetailView approval band renders it beside the existing badge: a labelled -`role="progressbar"` with one tick per required approval for `quorum` / -`unanimous`, and for `per_group` a chip per group marking which have signed -(`finance 1/1` ✓, `manager 0/1`). Group names come from the flow author's own -config, so they need no locale strings; the three new label keys are added to all -ten packs. `first_response` nodes carry no `decision_progress` and are unchanged — -one decision is the whole step there, and a "1 of 1" bar would be noise. - -Scored `minor` rather than `patch`: this is new observable rendering plus a new -public `approvalProgress` prop / `ApprovalProgress` type on `@object-ui/react`, -not a behavior correction inside an existing surface. diff --git a/.changeset/auth-components-react-spec-symbol-burn-down.md b/.changeset/auth-components-react-spec-symbol-burn-down.md deleted file mode 100644 index fb3eca0113..0000000000 --- a/.changeset/auth-components-react-spec-symbol-burn-down.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -"@object-ui/auth": major -"@object-ui/components": major -"@object-ui/react": major ---- - -Stop declaring 18 `@object-ui/auth` / `@object-ui/components` / `@object-ui/react` -symbols under names `@objectstack/spec` owns (objectui#3159, objectstack#4115 -batch 5). - -**Breaking for importers of all three packages** — six exported names changed, -because the spec exports the same name for a *different* thing: - -| package | was | now | what the spec's same-named export actually is | -|:--|:--|:--|:--| -| `auth` | `AuthSession` | `AuthClientSession` | the SERVER's session record (`{ id, userId, expiresAt: ISO string, token? }`) | -| `auth` | `AuthProviderConfig` | `AuthProviderOptions` | an OAuth/OIDC provider registration (`{ id, clientId, clientSecret, scope? }`) | -| `components` | `FilterCondition` | `FilterBuilderCondition` | the recursive ObjectQL predicate AST (`$and`/`$or`/`$not`) | -| `components` | `Field` | `FieldContainer` | an object FIELD's metadata and its builder namespace | -| `react` | `ConflictResolutionStrategy` | `ConflictResolution` | the metadata-MERGE policy (`error \| priority \| first-wins \| last-wins`) | - -The `react` rename is the odd one out: the new name is the **spec's own** name -for the union that hook always used, so it is a re-export rather than a dialect. - -Eleven more keep their names and are now **imported or derived from the spec** -instead of re-declared: `TenancyPosture`, `DelegableScope` (+`DelegableAdminScope`), -`AuthUser`, `ShareLinkPermission`, `ShareLinkAudience`, `ShareLink`, `SortItem`, -`OfflineStrategy`, `OfflineCacheConfig`, `OfflineSyncConfig`, `OfflineConfig`, -`NavigationConfig`. - -**Three of the copies were losing information, not just duplicating it.** - -- `AuthUser` never declared the spec's `positions` or `tenantId` — the - authorization inputs. Its `[key: string]: unknown` index signature meant the - omission was invisible at every call site *and* to any structural comparison - (the objectstack#4075 mechanism). It now `extends` the spec principal, so the - display-only fields (`image`, `role`, `roles`, `emailVerified`) are the delta - and the spec's keys arrive on their own. -- `useNavigationOverlay`'s copy carried the note *"inline … to avoid importing - from `@object-ui/types` (which may not be a direct dependency of - `@object-ui/react`)"*. The vocabulary belongs to `@objectstack/spec`, which - **is** a direct dependency — the same expired "kept local to avoid a - dependency" comment objectui#3169 found in `@object-ui/app-shell`. -- `useOffline` and `usePerformance` both opened with *"Types aligned with - `@objectstack/spec` v2.0.7"*. The installed spec is 17.0.0-rc.1. - -`ShareLink` derives from the spec row **minus `password_hash`** — omitted rather -than optional, because it is the credential itself and typing it in a browser -package is an invitation to render it. `password_protected` (the boolean the UI -needs in its place) is the one local addition. - -The config types derive from each schema's **input** side, not `z.infer`. -`useOffline(config: OfflineConfig = {})` defaults to the empty object, which the -output type — every `.default()`ed key required — would reject outright. - -`@objectstack/spec` moves from `devDependencies` to `dependencies` in -`@object-ui/components`: its public type surface now references the spec. diff --git a/.changeset/binding-reach-lazy-candidate-coverage.md b/.changeset/binding-reach-lazy-candidate-coverage.md deleted file mode 100644 index 5a6785d492..0000000000 --- a/.changeset/binding-reach-lazy-candidate-coverage.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -"@object-ui/console": patch ---- - -The binding-reach probe was under-reporting its own coverage by six object blocks, silently (#3149). - -`public-block-binding-reach.test.tsx` selects what to probe by filtering `getPublicConfigs()` -for a declared `objectName` input. The console registers most object blocks with -`registerLazy`, and a pending stub carries no `inputs` — `Registry.getMeta` says in as many -words that a consumer must read that as *"not yet known"*, not as *"declares no props"*. The -filter read it as the latter, so `object-chart`, `object-kanban`, `object-calendar`, -`object-gantt`, `object-timeline` and `object-map` — six Tier-A blocks, every one declaring -`objectName` as **required** — dropped out of the candidate set while the suite reported eight -green probes and no gap. - -That is objectui#2953's shape (a lazy registration falling out of the contract) recurring in a -consumer, and objectstack#4472's shape recurring inside the suite written to answer it: a gate -whose stated scope was wider than its reach. The coverage guard could not see it — `length > 0` -and `toContain('object-form')` both stayed true at 8 of 14. - -- Pending public lazy loaders are resolved through the registry's own `loadLazy` before - candidates are selected — driven off the recorded loaders, not a hand-written list of plugin - imports that would drift out of step with `register-plugins.ts` and reintroduce the same - shrinkage by another route. -- The guard is now an **exact** candidate list, the lesson `public-contract.test.ts` already - carries: the failure mode is a set getting smaller, and only an exact comparison makes both - directions a deliberate edit. Verified by simulating the regression — with resolution - disabled the assertion fails naming the missing blocks. - -All six were already wired correctly (`ObjectChart` reads the context itself; gantt/timeline/map -and kanban/calendar have context→prop wrappers), so this found no new defect of the #3144 kind. -It found two more probe artifacts, which is the same lesson a third and fourth time — a -plausible value for every input is not a plausible *configuration*: - -- **`data` supersedes the binding.** `ObjectChart`'s fetch is guarded by - `if ((schema.objectName || schema.dataset) && !boundData && !schema.data)`, and the spec - glosses `data` as static data to chart *instead of* binding via `objectName`. Filling it and - then reporting "objectName never reached" would have been the probe manufacturing its own - finding. Binding-superseding inputs are now excluded, narrowly and with the guard quoted. -- **Teardown is not the subject.** `object-map` mounts maplibre-gl, whose `map.remove()` throws - in jsdom for want of a WebGL context. Unmount is caught so the assertion speaks to data reach; - an error thrown during *render* still propagates. - -Coverage after this: 14 of 14 object-bound public blocks. The rest of #3149 — bindings other -than `objectName`, the `record:*` family under a record context, and the display primitives — -is untouched and still open. diff --git a/.changeset/bulk-action-aggregate-execution.md b/.changeset/bulk-action-aggregate-execution.md deleted file mode 100644 index d8d6936a64..0000000000 --- a/.changeset/bulk-action-aggregate-execution.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -"@object-ui/types": minor -"@object-ui/plugin-grid": minor -"@object-ui/core": minor -"@object-ui/app-shell": minor ---- - -Aggregate single-call mode for bulk actions: `execution: 'aggregate'` (objectui#3139). - -A `bulkActionDefs` entry with `operation: 'custom'` used to have exactly one -dispatch shape: one action-runner call per selected record (`_rowRecord` -attached). "Select N rows → ONE call that receives every selected id" — the -zip-of-QR-codes / merged-PDF / batch-print shape — could not be expressed, so -downstream projects fell back to per-row `window.open` storms or gave up. - -`BulkActionDef` now carries `execution?: 'perRecord' | 'aggregate'` (default -`'perRecord'`, existing views untouched). An aggregate def dispatches its -action exactly once for the whole selection with `params._selectedIds: -string[]` injected and the full records published as -`context.selectedRecords`. The authored form usually just names a declared -object action — `{ name, operation: 'custom', execution: 'aggregate' }` — -and `resolveBulkActions` attaches the declaration. Results are -all-or-nothing: a failure is attributed to every id with the real error and -per-row Retry is hidden (re-running the action is the retry; a total failure -keeps the selection). `batchSize` does not apply; `maxRecords` still gates. - -The executor rides the existing `executeBulkBatch` bulk-first decision tree — -the aggregate call is its `bulkCall`, and the per-row "fallback" only -re-throws the captured error for attribution, never fans out N dispatches -against an endpoint written for one `_selectedIds` call. - -Also: url/api target interpolation now exposes `${ctx.selection.ids}` (comma --joined) and `${ctx.selection.count}` from the grid's checkbox selection, so -a plain `list_toolbar` action can carry the selection without bulk plumbing; -the console's server-action handler recognizes `_selectedIds` and skips the -single-record multi-select guard for aggregate dispatches. diff --git a/.changeset/ci-cd-pipeline-doc-matches-the-workflow.md b/.changeset/ci-cd-pipeline-doc-matches-the-workflow.md deleted file mode 100644 index 9236e27ce4..0000000000 --- a/.changeset/ci-cd-pipeline-doc-matches-the-workflow.md +++ /dev/null @@ -1,36 +0,0 @@ ---- ---- - -docs: the CI/CD pipeline page describes the bundle-size gates that actually exist - -Release-nothing: rewrites `content/docs/guide/ci-cd-pipeline.md` and adds a test -under `scripts/__tests__/`; no package code changes. - -Three claims on that page had drifted away from `.github/workflows/` -(objectui#3197): - -- The console budget was documented as **60 KB** gzip while - `performance-budget.yml` enforces `MAX_ENTRY_GZIP_KB=350` — off by 5.8x, and - in the direction that matters: at 28.1 KB the entry chunk read as half a - budget away from breaching, when it is using 8% of it. Anyone sizing a - dependency against this page was reading a ceiling that does not exist. -- A `size-check.yml` workflow had its own section, triggers and limits table. - No such file is in `.github/workflows/`, and `git log --all` finds none ever: - the package size report it described has always been a step inside - `performance-budget.yml`. -- That step's 50 / 100 / 150 KB tiers were printed under an **Enforced limits** - heading. The step `echo`s them into the markdown report as explanatory text — - no comparison, no `exit 1`. Nothing goes red when a package exceeds them. - -The last one is the reason this is a bug rather than staleness. A documented -guardrail that is not implemented is worse than no documentation: it is trusted -in exactly the moment it fails. The tiers are now labelled advisory in the table -itself, next to the one limit that really is enforced. - -Retyping 350 would have set up the next drift, so the number is pinned instead: -`scripts/__tests__/ci-cd-pipeline-doc.test.ts` reads `MAX_ENTRY_GZIP_KB` out of -the workflow, the three tiers out of the `echo` lines, and the workflow -filenames out of the page's prose, and fails when the page and the YAML -disagree — including in the direction nobody thinks to check: if the size report -ever grows a real comparison, the test fails and sends whoever added it back to -the "advisory only" wording. diff --git a/.changeset/core-spec-symbol-burn-down.md b/.changeset/core-spec-symbol-burn-down.md deleted file mode 100644 index e91eedb8b0..0000000000 --- a/.changeset/core-spec-symbol-burn-down.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -"@object-ui/core": minor -"@object-ui/app-shell": patch -"@object-ui/plugin-form": patch -"@object-ui/plugin-grid": patch -"@object-ui/plugin-list": patch ---- - -Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns -(objectui#3158, objectstack#4115 batch 4). - -**Breaking for importers of `@object-ui/core`** — seven exported names changed, -because the spec exports the same name for a *different* thing: - -| was | now | what the spec's same-named export actually is | -|:--|:--|:--| -| `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) | -| `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` | -| `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) | -| `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity | -| `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional | -| `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` | -| `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations | - -The other six keep their names and are now **imported from the spec** instead of -re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`, -`CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`. - -**The copies were live misdescriptions, not just duplicates.** Three said so in -their own comments: - -- `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the - next coordinated release… because the installed `@objectstack/spec` predates - that export". The installed spec (17.0.0-rc.0) exports it, and the copy was - byte-identical — so it passed every value comparison and every behavioural - test for the whole interval in which its stated reason was false. -- `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary" - while being a hand-written union. It happened to be correct; nothing would - have caught the day it stopped being. -- `managedBy.ts` described itself as a "UI-side mirror of the framework's - `resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a - line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of - its override parser. - -`resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half -to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one -definition on the platform. What stays objectui's is the part the spec has no -notion of: intersecting that matrix with the server-resolved effective API -operation set (#3391), so the UI never offers a button the server would 405 — -and the name now says that instead of claiming to be the spec's function. - -Deriving `RowCrudPredicates` also **tightens** it: the local copy typed -`visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as -`Expression | ExpressionInput`. That was imprecision, not a deliberate dialect. diff --git a/.changeset/cross-repo-issue-closer.md b/.changeset/cross-repo-issue-closer.md deleted file mode 100644 index a0c959727b..0000000000 --- a/.changeset/cross-repo-issue-closer.md +++ /dev/null @@ -1,31 +0,0 @@ ---- ---- - -ci: close issues that a merged PR fixes in another repository - -Release-nothing: adds `.github/workflows/cross-repo-issue-closer.yml` and no -package code. - -Defects are routinely found in objectstack, where verification runs, and fixed -here. But GitHub's closing keywords only act within a repository, so a PR here -saying `Fixes objectstack-ai/objectstack#4475` merges and leaves that framework -issue open — with no reference to the PR on the issue's own page either. v17 -verification hit this twice in one day: #3150 fixed objectstack#4475 and #3163 -fixed objectstack#4478, and both were closed by hand. - -The job has two modes and both are visible. With a cross-repo token it closes -the foreign issue and links the PR. Without one it comments on the merged PR -naming what still needs closing by hand — this repository's secrets are -`GITHUB_TOKEN` (scoped to the repository running the workflow, which is the -whole problem), `NPM_TOKEN` and `CODECOV_TOKEN`, so until an admin provisions -`CROSS_REPO_ISSUE_TOKEN` the job cannot perform the close at all. - -That second mode is deliberate, not a fallback. A workflow that quietly does -nothing because a secret was never provisioned is the "declared but never -enforced" shape both repositories keep having to fix. A missing credential has -to announce itself. - -Matched references are restricted to the qualified `owner/repo#N` form; the -bare `#N` form already works natively and is left alone. Same-repo qualified -references are filtered out, already-closed targets are skipped, and one -unreachable target cannot swallow the rest or read as success. diff --git a/.changeset/dashboard-date-filter-preset-default.md b/.changeset/dashboard-date-filter-preset-default.md deleted file mode 100644 index bab98d04a8..0000000000 --- a/.changeset/dashboard-date-filter-preset-default.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -"@object-ui/core": minor ---- - -fix(dashboard): a date globalFilter's preset-name default becomes a range, not an equality - -Setup → System Overview rendered EVERY KPI tile as 0 while its period selector -read "All time" (objectstack#4475). Every request was `200 OK`, the widgets -rendered normally, and nothing in the UI signalled a failure — zeros read as -"nothing has happened yet" rather than as an error, which is why this survived -to an RC. - -Both symptoms are one missing normalization. `resolveDashboardFilterDefs` lifts -the built-in `dateRange` declaration's preset NAME to `{ preset }`, but passed a -`globalFilters` entry's `defaultValue` through raw. `@objectstack/spec`'s -`GlobalFilterSchema.defaultValue` is `string | number | boolean`, so a bare -preset name is the ONLY spelling an author can write — and nothing ever mapped -it. System Overview declares -`{ field: 'created_at', type: 'date', defaultValue: 'last_7_days' }`, so: - -- `buildFilterCondition` fell through to its "a bare string date means equality - on that day" branch and the widget sent - `runtimeFilter: { created_at: 'last_7_days' }`. The backend compiled - `SELECT COUNT(*) AS "user_count" FROM "sys_user" WHERE created_at = $1` - — verified against a live server, byte-for-byte the SQL in the issue. The - actual `sys_user` count is 4; that equality matches no row. -- `DateRangeFilter` derives its selected item from `value.preset` / `.from` / - `.to`, all `undefined` on a bare string, so the control fell through to its - ALL sentinel and displayed "All time" while sending that equality. The tiles - therefore looked deliberately unfiltered and merely empty. - -`normalizeDateDefault` now applies the same lift the sibling `dateRange` -declaration already receives, for `date`/`dateRange` filters whose default names -a preset this module actually knows. This is not consumer-side leniency: it is -one normalization function completing the same conversion for the sibling -declaration, and the spec admits no other spelling for an author to fix at the -producer. A genuine ISO date string still means equality on that day (the -documented behaviour), and numbers, booleans and unrecognised strings are left -exactly as declared. - -No backend change is needed: given a real range the dataset path already lowers -it correctly (`WHERE (created_at >= $1 AND created_at < $2)` → 4). The -framework's dashboard metadata needs none either — it is spec-compliant as -written, and editing it would only hide the defect. - -Levelled `minor` rather than `patch` because the change is visible in rendered -dashboards rather than internal: any dashboard declaring a date-typed -`globalFilters` default now emits a different query shape, its numbers change -(from 0 to real values), and its filter control's displayed label changes with -them. Anything asserting on the previously-emitted condition will see it move. - -Known residual, filed separately rather than widened into here: a `date` filter -whose value is neither a known preset nor a parseable ISO date still degrades -silently to an equality that matches nothing, producing the same -healthy-looking zero. Preset names are covered by this change; a misspelled -custom value is not. diff --git a/.changeset/dashboard-date-filter-unknown-value.md b/.changeset/dashboard-date-filter-unknown-value.md deleted file mode 100644 index aac4cea0b8..0000000000 --- a/.changeset/dashboard-date-filter-unknown-value.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -"@object-ui/core": minor ---- - -fix(dashboard): an unrecognised date filter value is skipped and named, not compared - -The residual the preset-name fix (objectui#3150 / objectstack#4475) left behind, -and the more deceptive half of it: a `date`/`dateRange` filter value that is -neither a known preset name nor a parseable date used to fall through to the -"a bare string date means equality on that day" branch. A misspelled default — -`defaultValue: 'last_7_dayz'` — therefore reached the widget query as -`runtimeFilter: { created_at: 'last_7_dayz' }`, which the backend faithfully -compiled to `WHERE created_at = $1`. `200 OK`, widget renders, count is 0 — -indistinguishable from "this range genuinely has no data". No 4xx, no console -warning, no UI signal. objectstack#4475 took a full RC cycle to catch for -exactly this reason: **0 looks like a legitimate answer**. - -`buildFilterCondition` now holds a date value to three spellings, and only -three: - -1. a known preset name → range bounds (unchanged, objectui#3150); -2. an ISO date (`2026-01-15`, `2026-01-15T08:30:00Z`) or a date-macro token - (`{today}`, `{7_days_ago}`) → equality on that day (the documented - behaviour, unchanged); -3. **anything else → the filter is skipped and `console.warn` names the filter, - the offending value, and the accepted spellings.** - -The `{ preset: '' }` object form gets the same voice. It already -dropped the filter — silently — because the preset lookup missed and no -`from`/`to` remained; that drop is now announced. When explicit bounds ride -along with an unknown preset the bounds are still honoured, and the warning says -which of the two won. - -Rule 3 is deliberately the same strictness `buildWidgetScopedFilter` already -applies to a *default binding on a field the object does not have* — skip and -warn, with the same rationale spelled out there: never emit a query the backend -can only empty-match. Field *names* had that guard; field *values* did not. - -The macro-token check asks `resolveDateMacros` itself whether it recognises the -string, rather than restating its token grammar in a second place. One -vocabulary, no dialect to drift — and a token that resolver does not know -(`{last_7_dayz}`) is precisely the typo this guard exists to catch. - -Levelled `minor`, matching objectui#3150, because the emitted query shape -changes: a dashboard carrying a misspelled date value stops sending a -never-matching equality and instead sends no constraint for that filter (its -numbers go from 0 to unfiltered) while the console says why. Anything asserting -on the previously-emitted equality will see it disappear. - -Note the direction of the relaxation is chosen, not incidental: skipping widens -the result set, so the number visibly changes and the warning explains it — -whereas the old behaviour narrowed it to zero, which is the one outcome an -author cannot tell from a correct answer. Author-time rejection (validating -`GlobalFilterSchema.defaultValue` at publish, in `@objectstack/spec`) is the -stricter complement and belongs on the platform side; it is filed separately. diff --git a/.changeset/dataset-percent-scale.md b/.changeset/dataset-percent-scale.md deleted file mode 100644 index ffc0fd850e..0000000000 --- a/.changeset/dataset-percent-scale.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -"@object-ui/core": patch -"@object-ui/plugin-dashboard": patch -"@object-ui/plugin-report": patch -"@object-ui/app-shell": patch ---- - -Honor the server's declared percent scale, so a ratio of exactly 1 renders as 100.0% (#3136) - -A dataset measure declared `format: '0.0%'` rendered every ratio below 1 -correctly and got the single most consequential one wrong: a rate of exactly -`1` printed as **`1.0%`**. On an SLA / pass-rate dashboard that turns -"everything met the SLA" into "1% met the SLA", on both surfaces the issue -names — the KPI card and the dataset-bound table (they share `formatMeasure`). - -The cause was never a bad multiplier; it was a missing fact. `formatMeasure` -scaled by magnitude — `percentDisplayValue` multiplies by 100 only strictly -inside `(-1, 1)` — because the column arrived with a `%` format string and -nothing saying what scale its numbers were on. That guess is undecidable at -exactly 1, which is both a full-compliance ratio ("100%") and one percentage -point ("1%"), and it resolved to the reading almost nobody means. - -The server now answers the question instead (framework: `percentScaleOf` + -`AnalyticsResult.fields[].percentScale`, the sibling of the ADR-0053 currency -chain): a `derived: { op: 'ratio' }` measure is a `fraction` by definition, and -a measure over a `percent` field inherits that field's scale. `formatMeasure` -takes the declared scale as a fourth argument and, when present, scales by it — -`fraction` ×100, `whole` verbatim — instead of inspecting the value. Every -dataset-bound call site passes the column's `percentScale`: the dashboard -metric/table/pivot cells, the report renderer's cells, totals and KPI, and the -dataset preview. - -`percentDisplayValue` is untouched and still the fallback for a column that -arrives without the annotation (an older server, or a non-dataset percent cell -in a list view), so nothing that renders correctly today changes. diff --git a/.changeset/datasource-preview-drop-read-replicas.md b/.changeset/datasource-preview-drop-read-replicas.md deleted file mode 100644 index f284d02658..0000000000 --- a/.changeset/datasource-preview-drop-read-replicas.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"@object-ui/app-shell": patch ---- - -Datasource preview stops reporting read replicas - -`DatasourcePreview` rendered a "2 read replicas" pill from -`datasource.readReplicas`. That key is retired in `@objectstack/spec` 17 -(objectstack#4468): nothing in the platform ever opened a replica connection — -no driver reads the key and no query path splits reads from writes — so the -pill confirmed a configuration that did not exist. - -It is worth being precise about what the pill did wrong, because a preview -panel echoing the draft back is normally harmless. This one did not echo, it -concluded: an author who configured replicas, saved, and saw the pill light up -got the platform telling them it had understood. It was the only surface in -either repo that acknowledged the key at all, which made it the whole of the -evidence that the feature worked. `packages/spec/liveness/README.md` has the -standing rule — an authoring or preview renderer is never a runtime consumer — -and a 2026-06 sweep that classified 13 properties on preview-renderer evidence -alone was later found wrong on 10 of them. - -Read-replica routing does not exist yet; it is tracked as a feature request -rather than reflected in the UI as though it shipped. diff --git a/.changeset/decision-default-path-is-the-edge.md b/.changeset/decision-default-path-is-the-edge.md deleted file mode 100644 index f2910ecb7f..0000000000 --- a/.changeset/decision-default-path-is-the-edge.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@object-ui/app-shell": patch ---- - -Say what the Decision inspector actually does: the default path is the edge marker, not the branch. - -Two help strings described mechanisms the engine does not have. - -The **Branches** editor said a branch whose expression is `"true"` *is* the -default/else path. It is how you **ask** for one — `FlowEdgeInspector.applyBranch()` -turns such a branch into `isDefault: true` on the out-edge it wires, and the marker -on that edge is what routes. Conflating the two is the reading that let -objectstack-ai/objectstack#4414 ship a decision whose guard did not guard, and it is -worth being exact about now that `isDefault` is finally enforced: the key had **zero -readers** in the engine until then, so this designer had been writing a marker -nothing honoured, and every Studio "default/else" edge ran unconditionally alongside -whichever branch matched. The help also now states that branches are tried in order -and that the expression is bare CEL — a braced predicate there is a build failure -since objectstack-ai/objectstack#4439. - -The legacy single **Condition** field said *"Prefer Branches above"*, which reads as -"this works, but the other is better". It does not work at all: the decision executor -never reads `config.condition`. The engine honours that key only on a Start node, as -the trigger gate, and `os validate` now reports it as `flow-inert-node-condition`. -The field stays render-only (its `__legacy__` controller never matches, so it is not -offered for new authoring) so a stored value is not invisible — but the help says it -is inert and where the predicate belongs instead. - -Text only; no behaviour change on this side. diff --git a/.changeset/decision-output-def-collapses-to-spec.md b/.changeset/decision-output-def-collapses-to-spec.md deleted file mode 100644 index 6d6de877d4..0000000000 --- a/.changeset/decision-output-def-collapses-to-spec.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -"@object-ui/app-shell": patch ---- - -Collapse app-shell's `DecisionOutputDef` to a plain re-export of the spec's -(objectstack#4562). - -The local type was `interface DecisionOutputDef extends SpecDecisionOutputDef -{ required?: boolean }` — a structural derivation carrying ONE documented -divergence, because the server enforced `required` (`decide()` rejects a blank -required output before any write) while `@objectstack/spec` did not model it. -The spec adopted `required` in cd6b9f202 and pinned it at the schema level in -objectstack#4561, and this repo now resolves a spec that has it -(`@objectstack/spec@17.0.0-rc.1`, #3178). The addition is therefore redundant -and the type becomes `export type DecisionOutputDef = SpecDecisionOutputDef`. - -No behavior change and no API change: the symbol is internal to this package -(it is not re-exported from `src/index.ts`), the resolved shape is identical -key-for-key, and `decisionOutputParams()` still reads `d.required` — now off -the spec's own field. - -The module TSDoc still asserted "the spec does not model it yet", which was -stale and actively misleading — an agent reading it would take the divergence -as ground truth and build on it, which is the objectstack#4115 failure class -this file's own tripwires exist to prevent. It now states the current truth. - -The parity pin in `__tests__/spec-symbol-parity.test.ts` is inverted -accordingly: `Exclude` -is asserted `never` rather than `'required'`, plus an exact-identity -assertion, so a future local addition to this symbol cannot slip in -undocumented. The `type`-is-the-spec's-closed-enum pin is unchanged — that -narrowing is still what stops a typo'd picker kind from silently degrading to -a raw record-id text box (objectui#2955). - -Note that this pin, like every other type-level assertion in that file, is not -yet compiled by any gate — package tsconfigs exclude `**/*.test.ts`, so -nothing type-checks it. It was verified by compiling the file explicitly. See -objectui#3181. diff --git a/.changeset/entitlement-dialog-i18n-and-envelope.md b/.changeset/entitlement-dialog-i18n-and-envelope.md deleted file mode 100644 index 59c83456b6..0000000000 --- a/.changeset/entitlement-dialog-i18n-and-envelope.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"@object-ui/app-shell": patch -"@object-ui/i18n": patch ---- - -Localize the environment entitlement dialog and read cloud's nested error envelope. - -The free-plan "Development environments are a paid feature" prompt was built from -English string literals in `entitlements.ts` — including the lowercase `your free -plan` sentence users reported (cloud#959). Both spec builders now take a translator -and resolve `environment.entitlement.*`; all ten locale packs carry the strings. -`entitlements.ts` stays dependency-free: `t` is passed in, not imported, and -defaults to the English copy with local `{{token}}` interpolation. - -The dialog now renders the Console's own copy rather than the server's prose — a -control plane upgrades independently and only localizes these messages from -cloud#959 on, so preferring the server string left the reactive path English -against every older deployment. - -Also fixes the reactive dialog not firing at all: cloud#948 moved coded errors into -a nested envelope (`{ success, error: { code, … } }`), and -`entitlementDialogFromError` read `code` off the top level — returning `null` for -every entitlement 403, so the upgrade dialog degraded to a generic red error toast. -Both shapes are read now. diff --git a/.changeset/fix-cross-repo-closer-require.md b/.changeset/fix-cross-repo-closer-require.md deleted file mode 100644 index 9f3925e482..0000000000 --- a/.changeset/fix-cross-repo-closer-require.md +++ /dev/null @@ -1,18 +0,0 @@ ---- ---- - -fix(ci): hand the cross-repo token to github-script instead of requiring @actions/github - -Release-nothing: touches `.github/workflows/cross-repo-issue-closer.yml` only. - -`require('@actions/github')` is not resolvable from a github-script `script:` -block — the action bundles its dependencies, so the call fails at runtime with -`MODULE_NOT_FOUND`. The token is now handed to the action itself -(`github-token:`), which makes the injected `github` client the cross-repo one, -with `secrets.GITHUB_TOKEN` as the fallback so the report path can still -comment on the pull request when no cross-repo credential is configured. - -Found on this workflow's first run that got past parsing. The run also -confirmed the credential logging works — `CROSS_REPO_ISSUE_TOKEN: configured` -followed by `Cross-repo targets: objectstack-ai/objectstack#4475` — so the -job now fails at the last step rather than the first. diff --git a/.changeset/form-designer-speaks-the-project-language.md b/.changeset/form-designer-speaks-the-project-language.md deleted file mode 100644 index b6889109bd..0000000000 --- a/.changeset/form-designer-speaks-the-project-language.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"@object-ui/app-shell": minor ---- - -fix(studio): the form-layout canvas resolves the object's field and section translations (#3134) - -`ObjectFormDesigner` bills itself as a preview of the end-user form, but it read -labels straight off the object draft — `entry.def.label` for fields, `group.label` -for section headers. Every other surface for the same object (`ObjectForm`, -`RecordDetailView`, the data grid) resolves those through the project's object -translations first, so a fully translated object rendered `Opportunity Name` / -`Basic Information` on the layout canvas while the very same fields read -`商机名称` / `基本信息` one click away. - -The designer now goes through `useSafeFieldLabel()` — `fieldLabel()` for field -cards (including the drag overlay) and `sectionLabel()` for section headers — -which is the same resolver the runtime form uses, with the authored metadata -label as fallback when no translation exists. The lookup root is the object's -API name; `StudioDesignSurface` now passes it explicitly (`objectName`) so a -draft body that has not been re-named still resolves, falling back to -`draft.name`. - -Observable rendering change (translated labels now appear where English source -labels did), hence `minor`. diff --git a/.changeset/grid-search-is-not-page-scoped.md b/.changeset/grid-search-is-not-page-scoped.md deleted file mode 100644 index f79941d5c6..0000000000 --- a/.changeset/grid-search-is-not-page-scoped.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -"@object-ui/types": patch -"@object-ui/components": patch -"@object-ui/plugin-grid": patch ---- - -A standalone grid's search box searches the list, not the page you can see (objectui#3118). - -Under server-side pagination a standalone `ObjectGrid` rendered `data-table`'s -built-in search box, and that box filtered the rows the table was holding — -which is one page. The user read "2 results for X in this list" while 3075 rows -never participated, with the pager beside it still reading `1 / 63`. Every piece -was individually correct: `searchable` defaults to true, `manualPagination` is -true, and the two are declared next to each other in the same object literal. - -This is objectui#3106 one axis over — sort there, filter here — and it takes the -same shape. `DataTable` gains `manualSearch` + a controlled `search` + -`onSearchChange`. In that mode it filters nothing, reports the typed term, and -renders `search` as the box's value, holding **no** term of its own: a private -copy beside a controlled prop is the shape the defect had. `ObjectGrid` turns -that term into a `$search` on the refetch — the server picks the matching fields -from the object's metadata (ADR-0061), the same channel the ListView toolbar has -always used — and returns to page 1, since a new term makes the old page index a -different set of rows (usually no rows at all). `$searchFields` rides along only -when the view declared `searchableFields`, which can narrow the server-resolved -set and never widen it. - -Two things worth naming: - -- Both paths are never live at once. The server's answer is the answer; a client - pass left running underneath would silently re-narrow it to whichever returned - rows happen to contain the term as *rendered text*, overruling the server's - own notion of which fields are searchable. -- Under `manualSearch` a table with no `onSearchChange` renders **no** search - box. The sort axis could degrade to inert headers; here there is no honest - local behaviour to fall back to, because the rows to search are not in the - browser. - -Client-paginated grids are untouched: inline, bound and grouped grids hold every -row they display, so their box keeps filtering in memory, where the count it -produces is true. The ListView path was never affected — it passes -`showSearch: false` and searches from its own toolbar. diff --git a/.changeset/inverted-pin-burndown-probes.md b/.changeset/inverted-pin-burndown-probes.md deleted file mode 100644 index 5e6344628c..0000000000 --- a/.changeset/inverted-pin-burndown-probes.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -"@object-ui/types": patch ---- - -Fix the admission probes behind objectstack#4171's three inverted pins, and -derive the `NavigationItem` keys that genuinely became derivable (objectui#3177). - -Spec 17.0.0-rc.1 typed `NavigationItem`, `FormField` and -`ConditionalValidation.then`/`.otherwise`, so the `IsAny` / `IsUnknown` pins -guarding them fired. Firing was supposed to mean "the burn-down is due". A -per-symbol triage found it did not: **`any` was never the only blocker for any -of the three**, so "no longer `any`" was never the right admission question. -Nothing was bound; the probes now ask the condition that actually governs each -symbol, and each still asserts today's state — so they pass now and stop -compiling the day their own blocker lifts. - -- `NavigationItem` — the spec models navigation as a nine-variant discriminated - union; objectui keeps one flat shape, and the spec has no counterpart at - either tier for `visible: boolean` (which `menuItemToNavigationItem` - manufactures when it inverts legacy `MenuItem.hidden`), `pinned` (backs - `useNavPins`), the legacy `defaultOpen` spelling, or a separator carrying a - `label`. Four probes, one per blocker. -- `FormField` — two concepts on two layers, not two dialects of one: the - required keys are disjoint (objectui `name` = the form data path; spec - `field` = an object-field reference, with no `name` at either tier), and the - shared `field` key is a string on one side and the resolved metadata object - on the other. Binding would also collapse the objectui#3090 disambiguation - that exports `SpecFormField` separately, and revert framework#4074's - `dependsOn` widening. -- `ConditionalValidation` — the branches went from `unknown` to - `BaseValidationRuleShape`, which is `{ type: string; …; [key: string]: - unknown }`. Better than `unknown`, still not derivable: `type` is not a - literal union so a branch cannot narrow by discriminant, and the index - signature waves through any member — a typo'd `type: 'formatt'` included. The - spec says so itself and names the remaining work as objectstack#4075. The - probe now pins "literal discriminant / no index signature", so it goes green - exactly when that lands. - -What DID become derivable is derived. `NavigationItemType` now comes off the -spec's own nav-item discriminant instead of a hand-written nine-member copy — -the objectstack#4115 failure class, and it also makes a future spec variant a -compile error at exhaustive consumers rather than a silent `default:`. Same for -`recordMode`, `filters`, `badge`, `target`, `params` and `actionDef`, each taken -from the spec branch that owns it, extending the existing `badgeVariant` -precedent. No member changes today, so no consumer is affected. diff --git a/.changeset/list-view-embeddable-form-datasource-bridge.md b/.changeset/list-view-embeddable-form-datasource-bridge.md deleted file mode 100644 index 0c73ad1ac6..0000000000 --- a/.changeset/list-view-embeddable-form-datasource-bridge.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -"@object-ui/plugin-list": patch -"@object-ui/plugin-form": patch ---- - -`list-view` and `embeddable-form` get a data source on the registry path — their required `objectName` was binding to nothing (#3144). - -`SchemaRenderer` puts the data source on `SchemaRendererContext` and **never** injects it into -component props. A component that reads `props.dataSource` therefore needs its registration to -bridge the two. `object-form`, `object-kanban` and `object-calendar` each register a small -renderer that does exactly that. These two did not: - -- `list-view` (and its `view:list` alias) registered the bare `ListView`, which reads - `props.dataSource` — so its `getObjectSchema` effect returned immediately, nothing was ever - fetched, and it rendered the `empty-state` "Nothing here". -- `embeddable-form`'s renderer was `({ schema }) => `, dropping - the context entirely — so the read-only source it derives for its inner `ObjectForm` was never - built, and its submit path (`if (dataSource) await dataSource.create(...)`) had nothing to call. - -Both declare `objectName` **required** in their registry `inputs`. A binding the protocol obliges -an author to supply, that nothing on that path can consume, is objectstack#4413's shape one layer -up — and the reason it went unnoticed is that the console never takes this path: it reaches -ListView through `ObjectView`'s `renderListView` render-prop, which passes a data source itself. -Broken on the registry/SDUI path, which is the path `sdui.manifest.json` describes and a -`kind:'react'` page walks. - -Found by `apps/console/src/__tests__/public-block-binding-reach.test.tsx` (objectstack#4472), not -by hand — that suite mounts every public block declaring an `objectName` under a recording -`dataSource` and asserts the binding arrives. Its ledger carried these two as named debt; with the -bridge in place the ledger's both-directions assertion **failed until the entries were deleted**, -which is the mechanism working as designed. Only `record:related_list` remains, and legitimately -(it needs a parent record id from `RecordContext` before it may fetch). - -An explicit `dataSource` prop still wins, so hosts passing their own are unaffected, and -`ListViewRenderer` forwards refs so `ListViewHandle` still works through the registry. diff --git a/.changeset/lookup-search-focus-trap.md b/.changeset/lookup-search-focus-trap.md deleted file mode 100644 index 8e42b6f79a..0000000000 --- a/.changeset/lookup-search-focus-trap.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@object-ui/console": patch -"@object-ui/components": patch ---- - -Lookup search inside a create/edit modal is typeable again (objectui#3183). - -In every production console build, the search input of a lookup field's -quick-select popover — and the nested Record Picker dialog — could not take -focus while the form modal was open: every click/focus was synchronously -yanked back to the field trigger, so a lookup could not be searched while -creating a record. - -Root cause is a race in stock `@radix-ui/react-focus-scope@1.1.16`: the -focus-scopes stack effect's cleanup schedules `focusScopesStack.remove(scope)` -in a `setTimeout(0)`. When the effect re-runs for a still-mounted scope (a -`container` ref flicker), the re-run re-`add`s the scope and the stale timeout -then evicts it — the dialog's trap listeners stay active but its scope is no -longer in the stack, so an opening popover pauses nothing and the trap yanks -focus out of the popover forever. - -Fixed via `patches/@radix-ui__react-focus-scope.patch`: an effect re-run for a -live scope cancels the pending eviction; a real unmount still runs the full -delayed cleanup (autofocus-on-unmount + stack removal). Regression-tested in -`packages/components` with a deterministic reproduction of the race. diff --git a/.changeset/metadata-fallback-render-loop.md b/.changeset/metadata-fallback-render-loop.md deleted file mode 100644 index 8b496fff18..0000000000 --- a/.changeset/metadata-fallback-render-loop.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -"@object-ui/react": patch ---- - -`useMetadataItem` no longer spins forever outside a `` — the "graceful fallback" was the thing that made those consumers impossible to mount. - -`useMetadata()` built its no-provider fallback **inline on every call**, so outside a provider -every render produced a new `getItem`. `useMetadataItem` lists `getItem` in its effect deps and, -on the no-name path, called `setState({ item: null, loading: false, error: null })` with a fresh -object each run. New identity → effect re-runs → new state object → re-render → new identity: -an unbreakable loop, synchronous enough to hang inside `render()` rather than fail. - -So the fallback documented as the graceful path for consumers mounted outside a provider — -"common in unit tests that only need to assert on rendering" — was precisely what made them -unmountable. `record:alert` and `record:quick_actions` both call `useMetadataItem` -unconditionally; each pinned a core and grew unbounded (8.6 GB before the first kill) on a -`render()` that never returned. - -Two changes, at the cause and one layer in: - -- The fallback is a frozen module-level singleton, so its identity is stable across renders. -- The clear-state path bails out when the state is already cleared, instead of installing an - equal-but-new object. That covers the same loop arriving by another route — any caller whose - context value is rebuilt per render, which this interface explicitly invites ("hand-rolled - context values in tests keep working"). - -Found by `apps/console/src/__tests__/record-block-record-reach.test.tsx` (objectui#3149), which -could not mount either block until this was fixed. diff --git a/.changeset/nav-action-dispatch.md b/.changeset/nav-action-dispatch.md deleted file mode 100644 index 53e6cfb534..0000000000 --- a/.changeset/nav-action-dispatch.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -"@object-ui/layout": minor -"@object-ui/app-shell": minor ---- - -Navigation `action` items actually run now (framework#4509). - -A `type: 'action'` nav item rendered, gated like any other item, and did -**nothing** when clicked. `NavigationRenderer` dispatches such a click to an -`onAction` prop it expects the host shell to supply — it deliberately never -reads `item.actionDef` itself — and no shipped sidebar supplied that prop. So -`actionDef.actionName` reached no dispatcher: an author could put an action in -the menu, watch it render with its icon and label, and never find out that -clicking it was a no-op. The framework's liveness ledger recorded this as the -single gap in the AppSchema navigation surface. - -**New `useNavActionDispatch`** (`@object-ui/app-shell`) resolves the nav item's -`actionName` against `action` metadata at click time — the same source -`DeclaredActionsBar` reads for a record toolbar — and dispatches the resolved -definition through `useAction()`. `UnifiedSidebar` now passes it. No new -provider is involved: the sidebar already renders inside `ConsoleShell`'s -`GlobalActionRuntimeProvider`, so nav actions get the fully-wired console runner -including the confirm, param-collection, result and navigate dialogs. A declared -`params` array becomes the runner's param-dialog input, and the nav item's own -`actionDef.params` is passed as the value bag, so a menu entry can pre-fill the -action it launches. - -Nav actions are inherently **global**: `ActionNavItemSchema` is strict with -exactly `{ actionName, params? }` and carries no `objectName`, so resolution is -by name alone and no record context rides along. - -**Behaviour change:** a shell that passes no `onAction` no longer renders -`action` items at all, instead of rendering them dead. This mirrors the existing -capability guards — an item the host cannot serve is hidden — and it makes the -omission diagnosable: a missing prop now shows up as "my action item is gone", -which leads to the prop, rather than "clicking does nothing", which for three -releases led nowhere. Every failure at dispatch time (an unnamed item, an -unresolvable action, a throwing action) warns and toasts instead of returning -silently. diff --git a/.changeset/objectstack-family-rc1-lockstep.md b/.changeset/objectstack-family-rc1-lockstep.md deleted file mode 100644 index 9d8970b36c..0000000000 --- a/.changeset/objectstack-family-rc1-lockstep.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -"@object-ui/app-shell": minor -"@object-ui/core": minor -"@object-ui/data-objectstack": minor -"@object-ui/console": minor ---- - -Bring the whole `@objectstack` family to `17.0.0-rc.1`, so the dependency graph resolves a -single copy of `@objectstack/spec`. - -#3178 bumped **only** `@objectstack/spec` to `17.0.0-rc.1`. The rest of the family — -`client`, `core`, `formula`, `lint` (and `sdui-parser`, reached through `lint`) — stayed on -`17.0.0-rc.0`, and each of them depends on spec at an **exact** version rather than a -caret: - -``` -@objectstack/client@17.0.0-rc.0 -> spec "17.0.0-rc.0" -@objectstack/core@17.0.0-rc.0 -> spec "17.0.0-rc.0" -@objectstack/formula@17.0.0-rc.0 -> spec "17.0.0-rc.0" -@objectstack/lint@17.0.0-rc.0 -> spec "17.0.0-rc.0" -``` - -So `main` carried **two** spec copies: objectui's own code read `17.0.0-rc.1` while every -`@objectstack/*` package read `17.0.0-rc.0` from its own nested `node_modules`. That breaks -the single-contract invariant this repo's guards are built on, and it breaks them -*silently* — the affected checks depend on identity, not on version strings: - -- `spec-subschema-parity.test.ts` distinguishes a genuine re-export from a fork by - **reference identity** of the zod schema object. Two spec copies make every schema a - distinct object, so a real re-export starts reading as a fork (or a fork slips through, - depending on which copy each side resolved). -- `scripts/check-spec-symbol-derivation.mjs` and `spec-symbol-parity.test.ts` use - `createRequire` to resolve spec's `.d.ts` and run it through the TS checker. With two - copies installed, *which* declaration file the checker sees is a function of resolution - order rather than of intent. - -The declared ranges were already `^17.0.0-rc.0`, which technically admits rc.1 — the pin -lived in the lockfile. Raising the remaining ranges to `^17.0.0-rc.1` makes the floor -explicit and forbids a future install from silently sliding back onto a family member that -drags rc.0 along with it. The rc.1 family members pin spec at `17.0.0-rc.1` exactly, so the -graph now converges on one copy by construction, not by luck. - -No product behaviour changes here. `check:spec-symbols` reconciliation was already -completed by #3178 and stays green under the unified graph; this changeset is `minor` -per the repo's fixed-group version policy. diff --git a/.changeset/public-block-binding-reach.md b/.changeset/public-block-binding-reach.md deleted file mode 100644 index aa711dc925..0000000000 --- a/.changeset/public-block-binding-reach.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -"@object-ui/sdui-parser": patch ---- - -A declared `objectName` must reach the data layer — the evidence the framework's spec↔registry check cannot gather (objectstack#4472). - -The framework diffs `sdui.manifest.json` against the spec's zod schemas and, while that -check was named `check:react-conformance`, it was read — by its own file header — as -confirming these components "ACTUALLY implement" the spec's props. It never could. Both -sides of that diff are **declarations**, and this repo produces one of them: -`manifestFromConfigs` copies `config.inputs` verbatim and cannot observe whether the -renderer behind a block reads any of them. So a prop both sides declare and nothing -consumes reads there as agreement — which is how objectstack#4413's four `record:*` blocks -published an `objectName`/`recordId` no renderer read, rendered blank, and stayed green. - -Evidence about the render path has to be taken from the render path, so it lives here now. -`apps/console/src/__tests__/public-block-binding-reach.test.tsx` mounts every public block -that declares an `objectName` input through `SchemaRenderer` with nothing but that binding, -under a provider whose `dataSource` is a Proxy recording every call, and asserts some call -carried the object name. Deliberately narrow — "is this binding wired", not "is every -declared input consumed", which is not decidable from outside without heuristics. Every -non-reaching block carries a written reason in a ledger asserted to equal the observed set -in **both** directions, so a block that starts binding forces its entry deleted and a block -that stops binding fails; the suite was verified to go red both ways. - -First run: five of eight bound blocks reach the data layer, three do not. -`record:related_list` legitimately declines to fetch without the parent record id from -`RecordContext` (already documented in @objectstack/spec's objectstack#4413 ledger). -`list-view` and `embeddable-form` do not, and that is a real defect of the same shape — -neither registration bridges the schema-renderer context onto the component's `dataSource` -prop the way `object-form` / `object-kanban` / `object-calendar` do, and `SchemaRenderer` -never injects it, so on the registry/SDUI path both render an empty shell while declaring -`objectName` **required**. Filed as objectui#3144 rather than fixed here: giving them a -data source changes what they render everywhere they are mounted bare. - -`manifestFromConfigs` and `scripts/dump-public-manifest.mjs` now say in their own docs that -what they emit is what a registration *declared*, never what a renderer reads. diff --git a/.changeset/record-activity-feed-has-a-source.md b/.changeset/record-activity-feed-has-a-source.md deleted file mode 100644 index b94ff1dffe..0000000000 --- a/.changeset/record-activity-feed-has-a-source.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -"@object-ui/plugin-detail": minor ---- - -`record:activity` fetches a feed instead of rendering a permanently empty one (objectui#3165). - -The block published eleven inputs — `types`, `filterMode`, `showFilterToggle`, -`limit`, `showCompleted`, `unifiedTimeline`, `showCommentInput`, -`enableMentions`, `enableReactions`, `enableThreading`, -`showSubscriptionToggle` — every one of them a filter or an affordance over a -feed that could not have content on any path. `RecordActivityRenderer` called -`useRecordContext()`, discarded the result and rendered -`` with the empty array hard-coded; the -timeline takes `items` as a prop and never fetched; no host supplied any -(`buildDefaultPageSchema` emits `{ type: 'record:activity' }` with no props at -all). Declared, published to `sdui.manifest.json`, inert at runtime — -objectstack#4413's shape, three blocks over. - -**The feed now has three sources, in precedence order.** `items` on the node -(the convention `record:history` uses for `entries`); a mounted -`DiscussionContext`, which the console's record page fills with the merged -`sys_comment` + `sys_activity` feed and the write handlers; otherwise a -**self-fetch** of `sys_activity` scoped to the bound record -(`{ object_name, record_id }`, newest first, `limit` rows per page, "Load more" -re-reading a wider window). The third path is what makes the block -drop-anywhere — hand-authored inside a `page:tabs`, with no host feeding it — -and it mirrors the read `record:history` already had. Rows map to feed items -exactly as the console's record page maps them, so both surfaces agree about -what a row is. - -**The read-side inputs now filter.** `types` is an allow-list over feed item -types (unrecognised entries ignored; an all-typo list is treated as unset -rather than emptying the feed). `limit` is a page size and caps the scoped -read. `showCompleted` (spec default `false`) hides completed activities. -`unifiedTimeline: false` un-mixes field changes from the comment stream — the -panel becomes a discussion feed and field changes stay in `record:history`. -`filterMode` seeds which slice the dropdown opens on and falls back to `all` -on an unrecognised value instead of leaving a `` matching nothing. + + **The write-side switches are wired to the host's handlers.** + `showCommentInput`, `enableThreading`, `enableReactions` and `enableMentions` + read `onAddComment` / `onAddReply` / `onToggleReaction` / + `mentionSuggestions` off `DiscussionContext` — the same standing + `record:discussion` has. With no host mounted the feed stays read-only and no + composer is rendered, rather than showing one that silently drops what you + type. + + **`showSubscriptionToggle` is recorded as a known gap, not quietly left + looking configurable.** The bell needs a `RecordSubscription` value and + somewhere to persist it, and the platform has no record-subscription object to + read or write one from. Its input description now says `NOT IMPLEMENTED` (that + text ships to `sdui.manifest.json`, so an author meets it before writing the + prop), the docs repeat it, and a test pins it inert so the note has to be + deleted the day a backend for it lands. + + `apps/console`'s record-reach probe (objectui#3149 layer 3a) asserted the old + behaviour from its `NO_RECORD_REACH` ledger in both directions; that entry is + deleted, and the probe now reports `record:activity` as responding to the bound + record. + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/fields@18.0.0 + - @object-ui/permissions@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/plugin-detail/package.json b/packages/plugin-detail/package.json index 488171aa45..db57368b56 100644 --- a/packages/plugin-detail/package.json +++ b/packages/plugin-detail/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-detail", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "DetailView plugin for Object UI - comprehensive detail page with sections, tabs, and related lists", diff --git a/packages/plugin-editor/CHANGELOG.md b/packages/plugin-editor/CHANGELOG.md index 8459fe2557..1563582ec9 100644 --- a/packages/plugin-editor/CHANGELOG.md +++ b/packages/plugin-editor/CHANGELOG.md @@ -1,5 +1,34 @@ # @object-ui/plugin-editor +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-editor/package.json b/packages/plugin-editor/package.json index 6e20c3ed83..afa813ea87 100644 --- a/packages/plugin-editor/package.json +++ b/packages/plugin-editor/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-editor", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Rich text editor plugin for Object UI, powered by Monaco Editor", diff --git a/packages/plugin-form/CHANGELOG.md b/packages/plugin-form/CHANGELOG.md index 1756fcd4a9..27c64f5127 100644 --- a/packages/plugin-form/CHANGELOG.md +++ b/packages/plugin-form/CHANGELOG.md @@ -1,5 +1,165 @@ # @object-ui/plugin-form +## 18.0.0 + +### Patch Changes + +- 335041c: Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns + (objectui#3158, objectstack#4115 batch 4). + + **Breaking for importers of `@object-ui/core`** — seven exported names changed, + because the spec exports the same name for a _different_ thing: + + | was | now | what the spec's same-named export actually is | + | :----------------------- | :-------------------------------- | :--------------------------------------------------------------------------- | + | `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) | + | `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` | + | `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) | + | `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity | + | `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional | + | `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` | + | `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations | + + The other six keep their names and are now **imported from the spec** instead of + re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`, + `CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`. + + **The copies were live misdescriptions, not just duplicates.** Three said so in + their own comments: + + - `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the + next coordinated release… because the installed `@objectstack/spec` predates + that export". The installed spec (17.0.0-rc.0) exports it, and the copy was + byte-identical — so it passed every value comparison and every behavioural + test for the whole interval in which its stated reason was false. + - `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary" + while being a hand-written union. It happened to be correct; nothing would + have caught the day it stopped being. + - `managedBy.ts` described itself as a "UI-side mirror of the framework's + `resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a + line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of + its override parser. + + `resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half + to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one + definition on the platform. What stays objectui's is the part the spec has no + notion of: intersecting that matrix with the server-resolved effective API + operation set (#3391), so the UI never offers a button the server would 405 — + and the name now says that instead of claiming to be the spec's function. + + Deriving `RowCrudPredicates` also **tightens** it: the local copy typed + `visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as + `Expression | ExpressionInput`. That was imprecision, not a deliberate dialect. + +- 5eaa861: `list-view` and `embeddable-form` get a data source on the registry path — their required `objectName` was binding to nothing (#3144). + + `SchemaRenderer` puts the data source on `SchemaRendererContext` and **never** injects it into + component props. A component that reads `props.dataSource` therefore needs its registration to + bridge the two. `object-form`, `object-kanban` and `object-calendar` each register a small + renderer that does exactly that. These two did not: + + - `list-view` (and its `view:list` alias) registered the bare `ListView`, which reads + `props.dataSource` — so its `getObjectSchema` effect returned immediately, nothing was ever + fetched, and it rendered the `empty-state` "Nothing here". + - `embeddable-form`'s renderer was `({ schema }) => `, dropping + the context entirely — so the read-only source it derives for its inner `ObjectForm` was never + built, and its submit path (`if (dataSource) await dataSource.create(...)`) had nothing to call. + + Both declare `objectName` **required** in their registry `inputs`. A binding the protocol obliges + an author to supply, that nothing on that path can consume, is objectstack#4413's shape one layer + up — and the reason it went unnoticed is that the console never takes this path: it reaches + ListView through `ObjectView`'s `renderListView` render-prop, which passes a data source itself. + Broken on the registry/SDUI path, which is the path `sdui.manifest.json` describes and a + `kind:'react'` page walks. + + Found by `apps/console/src/__tests__/public-block-binding-reach.test.tsx` (objectstack#4472), not + by hand — that suite mounts every public block declaring an `objectName` under a recording + `dataSource` and asserts the binding arrives. Its ledger carried these two as named debt; with the + bridge in place the ledger's both-directions assertion **failed until the entries were deleted**, + which is the mechanism working as designed. Only `record:related_list` remains, and legitimately + (it needs a parent record id from `RecordContext` before it may fetch). + + An explicit `dataSource` prop still wins, so hosts passing their own are unaffected, and + `ListViewRenderer` forwards refs so `ListViewHandle` still works through the registry. + +- a8ad6c0: A required boolean must be savable in its UNCHECKED state — `false` and `0` are values. + + Reported against an AI-built task tracker whose 任务 object has a required + `是否完成` boolean: the create form showed the switch OFF, answered "是否完成不能 + 为空", and saved instantly once the switch was turned ON. The app could only ever + create ALREADY-DONE tasks — the one state the control shows by default was the + one value it refused to save (cloud#972). + + Two defects stacked, and either alone is enough to break it: + + **The `required` verdict read truthiness, not presence.** `@objectstack/spec` + FieldSchema.required (ADR-0113) is "an insert must provide a NON-NULL value", + and objectql's record validator implements exactly that. react-hook-form's + built-in rule instead fails whenever `isBoolean(value) && !value` — its + accept-the-terms checkbox heritage — silently redefining every required boolean + as "must be TRUE", including a select whose chosen option value is `false`. It + also disagreed the other way, letting a whitespace-only string through for the + server to reject with a 400. The form renderer no longer hands RHF its own + `required`: the check is now a `validate` entry keyed `required` (so the error + still surfaces as `type: 'required'`, which the conditional-required cleanup + keys on) backed by a new shared `isMissingForRequired` in `@object-ui/core`, a + deliberate mirror of objectql `record-validator.isMissing` — `undefined`, + `null`, blank-after-trim string, empty array. Deleting the inherited rule also + stops a `required` that rode in on `validation` from outliving a `requiredWhen` + that resolved to FALSE. + + **A boolean field held `undefined` while displaying "off".** A two-state control + has no third state, but a field with no entry in `defaultValues` rendered an OFF + switch backed by nothing: the create payload omitted the column (it lands null, + which reads as unchecked but isn't) and the presence check above would still + refuse it. The form renderer now folds `false` into `defaultValues` for every + boolean-widget field the caller left unset — in `defaultValues` itself, not + per-Controller, because that object is also the dirty-check baseline and what + the defaults-reset window replays. Every surface gets it, including the + modal/drawer create dialogs that start from a bare `{}`. An authored default + (or a loaded record, `null` included) still wins. + + `WizardForm`'s cross-step gate had its own copy of the empty-value predicate; it + now imports the shared one so it cannot drift from the per-field verdict. And + the field-demo renderer read `schema.defaultValue || schema.value`, throwing + away an authored default of `false` / `0` / `''` — same falsy-as-empty class, + now `??`. + + Verified end to end on a local stack against the exact metadata shape + `apply_blueprint` materializes (`{ type: 'boolean', required: true }`, no + default): a 是否完成 = 否 task with 工时 = 0 now creates and persists as + `{ hours: 0, is_done: false }`, turning the switch on still stores `true`, and a + blank required text is still refused. + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/fields@18.0.0 + - @object-ui/permissions@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/plugin-form/package.json b/packages/plugin-form/package.json index ce6066e396..96c1c7093d 100644 --- a/packages/plugin-form/package.json +++ b/packages/plugin-form/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-form", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Form plugin for Object UI", diff --git a/packages/plugin-gantt/CHANGELOG.md b/packages/plugin-gantt/CHANGELOG.md index 2e2ed36c3f..502daa7d41 100644 --- a/packages/plugin-gantt/CHANGELOG.md +++ b/packages/plugin-gantt/CHANGELOG.md @@ -1,5 +1,39 @@ # @object-ui/plugin-gantt +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [dd06bcd] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/plugin-detail@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/fields@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-gantt/package.json b/packages/plugin-gantt/package.json index edc71f72d5..e5ef9fb560 100644 --- a/packages/plugin-gantt/package.json +++ b/packages/plugin-gantt/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-gantt", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Gantt chart plugin for Object UI", diff --git a/packages/plugin-grid/CHANGELOG.md b/packages/plugin-grid/CHANGELOG.md index f3c75888c3..ec2f3a0c05 100644 --- a/packages/plugin-grid/CHANGELOG.md +++ b/packages/plugin-grid/CHANGELOG.md @@ -1,5 +1,213 @@ # @object-ui/plugin-grid +## 18.0.0 + +### Minor Changes + +- 4bf612c: Aggregate single-call mode for bulk actions: `execution: 'aggregate'` (objectui#3139). + + A `bulkActionDefs` entry with `operation: 'custom'` used to have exactly one + dispatch shape: one action-runner call per selected record (`_rowRecord` + attached). "Select N rows → ONE call that receives every selected id" — the + zip-of-QR-codes / merged-PDF / batch-print shape — could not be expressed, so + downstream projects fell back to per-row `window.open` storms or gave up. + + `BulkActionDef` now carries `execution?: 'perRecord' | 'aggregate'` (default + `'perRecord'`, existing views untouched). An aggregate def dispatches its + action exactly once for the whole selection with `params._selectedIds: +string[]` injected and the full records published as + `context.selectedRecords`. The authored form usually just names a declared + object action — `{ name, operation: 'custom', execution: 'aggregate' }` — + and `resolveBulkActions` attaches the declaration. Results are + all-or-nothing: a failure is attributed to every id with the real error and + per-row Retry is hidden (re-running the action is the retry; a total failure + keeps the selection). `batchSize` does not apply; `maxRecords` still gates. + + The executor rides the existing `executeBulkBatch` bulk-first decision tree — + the aggregate call is its `bulkCall`, and the per-row "fallback" only + re-throws the captured error for attribution, never fans out N dispatches + against an endpoint written for one `_selectedIds` call. + + Also: url/api target interpolation now exposes `${ctx.selection.ids}` (comma + -joined) and `${ctx.selection.count}` from the grid's checkbox selection, so + a plain `list_toolbar` action can carry the selection without bulk plumbing; + the console's server-action handler recognizes `_selectedIds` and skips the + single-record multi-select guard for aggregate dispatches. + +- 726b89c: `@object-ui/types` stops declaring sixteen symbols under names `@objectstack/spec` owns (objectui#3156, objectstack#4115). + + Seven are now **derived** from the spec, nine are **renamed** to the local + dialect they always were. Both halves remove the same hazard: a local + declaration under a spec export's name reads as the spec's own definition to + the next reader, so a copy that is merely _correct today_ is a planted premise + tomorrow. + + **Derived** — the spec now supplies the keys, by reference: + + | symbol | derivation | + | :----------------------- | :-------------------------------------------------------------------------------- | + | `ActionParam` | `z.input`, `type` widened to the local legacy spellings | + | `CreateExportJobRequest` | `Omit` (`object` is the method argument) | + | `CreateExportJobResult` | re-export from `@objectstack/spec/contracts` | + | `ImportRowResult` | re-export from `@objectstack/spec/api` | + | `NavigationArea` | spec keys, with `navigation` / `visible` pinned locally | + | `NavigationAreaSchema` | `specFieldsExcept(NavigationAreaSchema.shape, …)` | + | `Theme` | re-export of the spec's `ThemeInput` (the authoring shape) | + | `ExportJobFormat` | re-export of the spec's `ExportFormat` | + + Four of these close real gaps rather than tidy names. `ActionParam` never + declared `reference` — the key `resolveActionParams()` actually reads for an + inline lookup target — nor `defaultFromRow`, which the metadata designer's own + inspector writes; it also narrowed `visible` to a bare string although the + resolver has always accepted the `{ dialect, source }` envelope too. + `CreateExportJobResult.createdAt` and `ImportRowResult.action` were optional + here and required by the server, leaving every consumer a branch that could + never run. And `NavigationArea`'s `id` now carries the spec's own length rule + instead of accepting any string. + + **Renamed** — same word, different concept: + + | was | now | why | + | :----------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------- | + | `FileMetadata` | `UploadedFileMetadata` | field-VALUE payload (`url`, `original_name`), not the storage file record | + | `GestureType` | `TouchGestureType` | direction-fused (`swipe-left`), not the spec's type+direction pair | + | `GestureConfig` | `TouchGestureConfig` | gesture→`action` binding, not per-gesture tuning | + | `OfflineConfig` | `PWAOfflineConfig` | service-worker route caching, not the offline data/sync model | + | `PageRegion` | `PageNodeRegion` | region of the renderer page NODE, holding `SchemaNode`s | + | `PageRegionSchema` | `PageNodeRegionSchema` | zod twin of the above | + | `ResponsiveConfig` | `MobileResponsiveConfig` | mobile box config, not the spec's SDUI grid contract | + | `WidgetManifest` | `RuntimeWidgetManifest` | SDUI component manifest, not the field-widget plugin manifest | + | `WidgetSource` | `RuntimeWidgetSource` | `module`/`inline`/`registry` loader union — and its `inline` carries a resolved component where the spec's carries source code | + + **Migration**: the old names are gone, not deprecated — an alias would preserve + exactly the ambiguity being removed. Import the new name; nothing about the + shapes changed. `@object-ui/types` already re-exports the spec's own + `SpecResponsiveConfig`, and `@object-ui/react`'s `useOffline` config remains the + spec-shaped `OfflineConfig`, so both concepts stay reachable under + distinguishable names. + + Each rename carries a bidirectional tripwire + (`packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts`): it fails if + the spec ever claims the new name, and also if the spec retires the old one — + at which point the natural name can be taken back rather than the workaround + outliving its reason. + +### Patch Changes + +- 335041c: Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns + (objectui#3158, objectstack#4115 batch 4). + + **Breaking for importers of `@object-ui/core`** — seven exported names changed, + because the spec exports the same name for a _different_ thing: + + | was | now | what the spec's same-named export actually is | + | :----------------------- | :-------------------------------- | :--------------------------------------------------------------------------- | + | `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) | + | `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` | + | `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) | + | `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity | + | `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional | + | `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` | + | `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations | + + The other six keep their names and are now **imported from the spec** instead of + re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`, + `CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`. + + **The copies were live misdescriptions, not just duplicates.** Three said so in + their own comments: + + - `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the + next coordinated release… because the installed `@objectstack/spec` predates + that export". The installed spec (17.0.0-rc.0) exports it, and the copy was + byte-identical — so it passed every value comparison and every behavioural + test for the whole interval in which its stated reason was false. + - `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary" + while being a hand-written union. It happened to be correct; nothing would + have caught the day it stopped being. + - `managedBy.ts` described itself as a "UI-side mirror of the framework's + `resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a + line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of + its override parser. + + `resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half + to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one + definition on the platform. What stays objectui's is the part the spec has no + notion of: intersecting that matrix with the server-resolved effective API + operation set (#3391), so the UI never offers a button the server would 405 — + and the name now says that instead of claiming to be the spec's function. + + Deriving `RowCrudPredicates` also **tightens** it: the local copy typed + `visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as + `Expression | ExpressionInput`. That was imprecision, not a deliberate dialect. + +- cb82705: A standalone grid's search box searches the list, not the page you can see (objectui#3118). + + Under server-side pagination a standalone `ObjectGrid` rendered `data-table`'s + built-in search box, and that box filtered the rows the table was holding — + which is one page. The user read "2 results for X in this list" while 3075 rows + never participated, with the pager beside it still reading `1 / 63`. Every piece + was individually correct: `searchable` defaults to true, `manualPagination` is + true, and the two are declared next to each other in the same object literal. + + This is objectui#3106 one axis over — sort there, filter here — and it takes the + same shape. `DataTable` gains `manualSearch` + a controlled `search` + + `onSearchChange`. In that mode it filters nothing, reports the typed term, and + renders `search` as the box's value, holding **no** term of its own: a private + copy beside a controlled prop is the shape the defect had. `ObjectGrid` turns + that term into a `$search` on the refetch — the server picks the matching fields + from the object's metadata (ADR-0061), the same channel the ListView toolbar has + always used — and returns to page 1, since a new term makes the old page index a + different set of rows (usually no rows at all). `$searchFields` rides along only + when the view declared `searchableFields`, which can narrow the server-resolved + set and never widen it. + + Two things worth naming: + + - Both paths are never live at once. The server's answer is the answer; a client + pass left running underneath would silently re-narrow it to whichever returned + rows happen to contain the term as _rendered text_, overruling the server's + own notion of which fields are searchable. + - Under `manualSearch` a table with no `onSearchChange` renders **no** search + box. The sort axis could degrade to inert headers; here there is no honest + local behaviour to fall back to, because the rows to search are not in the + browser. + + Client-paginated grids are untouched: inline, bound and grouped grids hold every + row they display, so their box keeps filtering in memory, where the count it + produces is true. The ListView path was never affected — it passes + `showSearch: false` and searches from its own toolbar. + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/fields@18.0.0 + - @object-ui/mobile@18.0.0 + - @object-ui/permissions@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/plugin-grid/package.json b/packages/plugin-grid/package.json index ff991c849a..5fb7f7a974 100644 --- a/packages/plugin-grid/package.json +++ b/packages/plugin-grid/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-grid", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Grid plugin for Object UI", diff --git a/packages/plugin-kanban/CHANGELOG.md b/packages/plugin-kanban/CHANGELOG.md index 4173421012..c9cee5ccd8 100644 --- a/packages/plugin-kanban/CHANGELOG.md +++ b/packages/plugin-kanban/CHANGELOG.md @@ -1,5 +1,39 @@ # @object-ui/plugin-kanban +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [dd06bcd] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/plugin-detail@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/fields@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-kanban/package.json b/packages/plugin-kanban/package.json index 790dc1d53c..4b795036d6 100644 --- a/packages/plugin-kanban/package.json +++ b/packages/plugin-kanban/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-kanban", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Kanban board plugin for Object UI, powered by dnd-kit", diff --git a/packages/plugin-list/CHANGELOG.md b/packages/plugin-list/CHANGELOG.md index 1a14bb1ed1..74435c55fb 100644 --- a/packages/plugin-list/CHANGELOG.md +++ b/packages/plugin-list/CHANGELOG.md @@ -1,5 +1,145 @@ # @object-ui/plugin-list +## 18.0.0 + +### Minor Changes + +- 5cb75b3: fix(timeline,list): the timeline honours `timeline.dateField`, not just `timeline.startDateField` (#3129) + + `dateField` is the pre-#2231 alias for `startDateField`. `@object-ui/types` + declares it on the nested config (`ListViewTimelineConfig`), and both + `ObjectView` read-sites (app-shell and plugin-view) resolve it — but the two + read-sites that actually drive the axis did not: + + - `ObjectTimeline` consulted the alias only on the FLAT prop (`schema.dateField`), + never on the nested `schema.timeline`. + - `ListView` resolved it out of `options.timeline` but not out of the + spec-canonical `schema.timeline` — including in the capability gate, so such a + view could fail to offer the Timeline option at all. + + So a view authored as `timeline: { dateField: 'start_date' }` — the spec nesting + with the legacy key — fell through to the caller's default (`created_at` / + `due_date`). That field is normally absent from the `$select` projection, so + every record came back without it and the timeline rendered all of them under + **No date** — while the configured date was sitting in the row untouched. That + also explains why widening the view's projection changed nothing: the projection + already carried the right field; the renderer was reading a different one. + + Both read-sites now resolve the alias in the same precedence position they + already use for `options.timeline.dateField`. The spec key still wins wherever + both appear. Observable rendering change (records move out of "No date" into + real date buckets), hence `minor`. + +### Patch Changes + +- 335041c: Stop declaring 13 `@object-ui/core` symbols under names `@objectstack/spec` owns + (objectui#3158, objectstack#4115 batch 4). + + **Breaking for importers of `@object-ui/core`** — seven exported names changed, + because the spec exports the same name for a _different_ thing: + + | was | now | what the spec's same-named export actually is | + | :----------------------- | :-------------------------------- | :--------------------------------------------------------------------------- | + | `ChartSeries` | `ChartSeriesBinding` | the authored dataset-binding descriptor (a measure `name`, no `data`) | + | `ActionHandler` | `ActionRunnerHandler` | the SERVER-side objectql handler, `(ctx) => unknown` | + | `PluginDefinition` | `RegistryPluginDefinition` | the platform PACKAGE manifest (`id`/`slug`/`staticPath`/install hooks) | + | `ValidationError` | `SchemaNodeValidationError` | plugin-manifest validation, keyed by `field`, no severity | + | `ValidationResult` | `SchemaNodeValidationResult` | ditto, with both arrays optional | + | `defineView` | `defineSystemView` | the VIEW-DOCUMENT factory: parses a `ViewSchema`, returns a validated `View` | + | `resolveCrudAffordances` | `resolveEffectiveCrudAffordances` | the object-level affordance matrix, with no notion of server API operations | + + The other six keep their names and are now **imported from the spec** instead of + re-declared: `StyleMap`, `ResponsiveStyles` (ADR-0065), `RowHeight`, + `CONTEXT_TOKENS`, `CrudAffordances`, `RowCrudPredicates`. + + **The copies were live misdescriptions, not just duplicates.** Three said so in + their own comments: + + - `CONTEXT_TOKENS` carried a note that the duplication was "temporary until the + next coordinated release… because the installed `@objectstack/spec` predates + that export". The installed spec (17.0.0-rc.0) exports it, and the copy was + byte-identical — so it passed every value comparison and every behavioural + test for the whole interval in which its stated reason was false. + - `RowHeight` advertised itself as "the spec's `RowHeightSchema` vocabulary" + while being a hand-written union. It happened to be correct; nothing would + have caught the day it stopped being. + - `managedBy.ts` described itself as a "UI-side mirror of the framework's + `resolveCrudAffordances()`" and carried its own `DEFAULTS` table — a + line-for-line copy of the spec's `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of + its override parser. + + `resolveEffectiveCrudAffordances` now **delegates** the bucket/`userActions` half + to the spec's `resolveCrudAffordances()`, so the bucket table has exactly one + definition on the platform. What stays objectui's is the part the spec has no + notion of: intersecting that matrix with the server-resolved effective API + operation set (#3391), so the UI never offers a button the server would 405 — + and the name now says that instead of claiming to be the spec's function. + + Deriving `RowCrudPredicates` also **tightens** it: the local copy typed + `visibleWhen`/`disabledWhen` as `unknown`, where the spec types them as + `Expression | ExpressionInput`. That was imprecision, not a deliberate dialect. + +- 5eaa861: `list-view` and `embeddable-form` get a data source on the registry path — their required `objectName` was binding to nothing (#3144). + + `SchemaRenderer` puts the data source on `SchemaRendererContext` and **never** injects it into + component props. A component that reads `props.dataSource` therefore needs its registration to + bridge the two. `object-form`, `object-kanban` and `object-calendar` each register a small + renderer that does exactly that. These two did not: + + - `list-view` (and its `view:list` alias) registered the bare `ListView`, which reads + `props.dataSource` — so its `getObjectSchema` effect returned immediately, nothing was ever + fetched, and it rendered the `empty-state` "Nothing here". + - `embeddable-form`'s renderer was `({ schema }) => `, dropping + the context entirely — so the read-only source it derives for its inner `ObjectForm` was never + built, and its submit path (`if (dataSource) await dataSource.create(...)`) had nothing to call. + + Both declare `objectName` **required** in their registry `inputs`. A binding the protocol obliges + an author to supply, that nothing on that path can consume, is objectstack#4413's shape one layer + up — and the reason it went unnoticed is that the console never takes this path: it reaches + ListView through `ObjectView`'s `renderListView` render-prop, which passes a data source itself. + Broken on the registry/SDUI path, which is the path `sdui.manifest.json` describes and a + `kind:'react'` page walks. + + Found by `apps/console/src/__tests__/public-block-binding-reach.test.tsx` (objectstack#4472), not + by hand — that suite mounts every public block declaring an `objectName` under a recording + `dataSource` and asserts the binding arrives. Its ledger carried these two as named debt; with the + bridge in place the ledger's both-directions assertion **failed until the entries were deleted**, + which is the mechanism working as designed. Only `record:related_list` remains, and legitimately + (it needs a parent record id from `RecordContext` before it may fetch). + + An explicit `dataSource` prop still wins, so hosts passing their own are unaffected, and + `ListViewRenderer` forwards refs so `ListViewHandle` still works through the registry. + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/fields@18.0.0 + - @object-ui/mobile@18.0.0 + - @object-ui/permissions@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/plugin-list/package.json b/packages/plugin-list/package.json index 08c5e518d9..a8cfd12140 100644 --- a/packages/plugin-list/package.json +++ b/packages/plugin-list/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-list", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "ListView plugin for Object UI - unified view component with view type switching", diff --git a/packages/plugin-map/CHANGELOG.md b/packages/plugin-map/CHANGELOG.md index a706e6f5d6..d9e5f9983b 100644 --- a/packages/plugin-map/CHANGELOG.md +++ b/packages/plugin-map/CHANGELOG.md @@ -1,5 +1,34 @@ # @object-ui/plugin-map +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-map/package.json b/packages/plugin-map/package.json index b8b89e38c9..9ab0d8a426 100644 --- a/packages/plugin-map/package.json +++ b/packages/plugin-map/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-map", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Map visualization plugin for Object UI", diff --git a/packages/plugin-markdown/CHANGELOG.md b/packages/plugin-markdown/CHANGELOG.md index f3908d69db..0d2a0f462f 100644 --- a/packages/plugin-markdown/CHANGELOG.md +++ b/packages/plugin-markdown/CHANGELOG.md @@ -1,5 +1,34 @@ # @object-ui/plugin-markdown +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-markdown/package.json b/packages/plugin-markdown/package.json index aa970b225a..9cbe1e7618 100644 --- a/packages/plugin-markdown/package.json +++ b/packages/plugin-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-markdown", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Markdown rendering plugin for Object UI, powered by react-markdown", diff --git a/packages/plugin-report/CHANGELOG.md b/packages/plugin-report/CHANGELOG.md index 2342b552cf..5066ab43c4 100644 --- a/packages/plugin-report/CHANGELOG.md +++ b/packages/plugin-report/CHANGELOG.md @@ -1,5 +1,122 @@ # @object-ui/plugin-report +## 18.0.0 + +### Patch Changes + +- d9668a7: Honor the server's declared percent scale, so a ratio of exactly 1 renders as 100.0% (#3136) + + A dataset measure declared `format: '0.0%'` rendered every ratio below 1 + correctly and got the single most consequential one wrong: a rate of exactly + `1` printed as **`1.0%`**. On an SLA / pass-rate dashboard that turns + "everything met the SLA" into "1% met the SLA", on both surfaces the issue + names — the KPI card and the dataset-bound table (they share `formatMeasure`). + + The cause was never a bad multiplier; it was a missing fact. `formatMeasure` + scaled by magnitude — `percentDisplayValue` multiplies by 100 only strictly + inside `(-1, 1)` — because the column arrived with a `%` format string and + nothing saying what scale its numbers were on. That guess is undecidable at + exactly 1, which is both a full-compliance ratio ("100%") and one percentage + point ("1%"), and it resolved to the reading almost nobody means. + + The server now answers the question instead (framework: `percentScaleOf` + + `AnalyticsResult.fields[].percentScale`, the sibling of the ADR-0053 currency + chain): a `derived: { op: 'ratio' }` measure is a `fraction` by definition, and + a measure over a `percent` field inherits that field's scale. `formatMeasure` + takes the declared scale as a fourth argument and, when present, scales by it — + `fraction` ×100, `whole` verbatim — instead of inspecting the value. Every + dataset-bound call site passes the column's `percentScale`: the dashboard + metric/table/pivot cells, the report renderer's cells, totals and KPI, and the + dataset preview. + + `percentDisplayValue` is untouched and still the fallback for a column that + arrives without the annotation (an older server, or a non-dataset percent cell + in a list view), so nothing that renders correctly today changes. + +- 022e4c3: Upgrade to `@objectstack/spec@17.0.0-rc.1`, stop offering the retired `wait` timeout fields (#3101), and route the newly-adopted `combo` chart type. + + **Breaking for authoring, and the reason to do it now**: the `wait` panel no longer offers + `waitEventConfig.timeoutMs` or `.onTimeout`. Both are `retiredKey()` tombstones as of spec + 17.0.0-rc.1 (framework#4158), which means a value written there is **rejected at load** — + so until this lands, Studio can produce flow metadata the author's own runtime refuses. + That hazard opened the moment rc.1 published, independent of when this repo bumps. + + `wait` never had a timeout: `onTimeout` had zero readers, so neither `'fail'` nor + `'continue'` ever happened, and `timeoutMs`'s only reader used it as the timer **duration** + when `timerDuration` was absent. Use **Duration** — it accepts a bare number as + milliseconds, making the old `timeoutMs: 60000` and `timerDuration: '60000'` the same wait. + Stored flows are converted by framework's D2 conversion; the designer simply stops offering + the entry. The two `zh` label overrides go with the fields. + + #3101 asked for this to ride along with the bump rather than land alone, and that is + load-bearing: the sibling-block assertion is **bidirectional**, so deleting the fields + against a spec that still declares them fails in the other direction. + + **`combo` is now a spec chart type** — the sole addition to `ChartTypeSchema` in rc.1 (19 + members → 20). It had been a renderer-local family the chart renderer derived from the + series, so nothing classified it on the two surfaces that route a _spec_ chart type: a + spec-valid `combo` fell through to the red "Unknown component type" panel on a dashboard + and to the out-of-spec notice on a report. Both now route it + (`widgetDispatch.SERIES_CHART_TYPES`, `planReportChart`). The renderer-local derivation + stays — it is what makes an authored `type: 'combo'` render rather than merely validate. + + **Retired spec exports this repo bound to**, all removed upstream in spec 17.0.0: + + - `JoinStrategy` / `WindowFunction` (framework#4286 tombstoned `query.joins` and + `query.windowFunctions`: no engine or driver ever read either on the query path). They + were derived off the spec enums under objectstack#4115's "come off the spec enum, not a + restatement" rule; with no enum left, `data-protocol.ts` now restates the members locally + — verbatim from the last spec that published them — as the objectui query-AST vocabulary + they have become. The AST itself is unchanged. + - `PerformanceConfig`, retired with `dashboard.performance` (framework#3896). Nothing bound + to it — `@object-ui/react`'s `usePerformance` declares its own interface and is untouched. + The dashboard form is derived from the spec's own `dashboardForm`, so the field + disappears from the inspector for free; its test now pins the absence. + + **Three inverted pins fired, and are recorded rather than resolved.** objectstack#4171's + tripwires asserted that `NavigationItem`, `FormField` and `ConditionalValidation`'s branches + still erased to `any`/`unknown` upstream — the premise that justified objectui keeping local + declarations. rc.1 types them properly, so the assertions are inverted to state the new + fact. The burn-down each one asks for — deriving those types from the spec — touches + widely-used public types and is deliberately **not** bundled into a version bump; it is + tracked in #3177. `JoinNode`'s pin is gone outright: the symbol no longer exists. + + **What the bump arms.** The reconciliation ledger's `subflow` and `decision` panels + feature-detect their spec exports and had never actually run — rc.0 predates the exports + (framework#4278). They now execute and pass. The `script` panel's full bidirectional check + stays deliberately skipped: rc.1 predates framework#4343, so the retired dispatch branches + are still contract keys there, and only the "offers nothing the executor ignores" direction + is meaningful. It arms itself on the next rc. + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/plugin-grid@18.0.0 + - @object-ui/fields@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/plugin-report/package.json b/packages/plugin-report/package.json index bb42b82778..59e25dc742 100644 --- a/packages/plugin-report/package.json +++ b/packages/plugin-report/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-report", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "main": "dist/index.umd.cjs", "module": "dist/index.js", diff --git a/packages/plugin-timeline/CHANGELOG.md b/packages/plugin-timeline/CHANGELOG.md index 8c48056353..868de6887c 100644 --- a/packages/plugin-timeline/CHANGELOG.md +++ b/packages/plugin-timeline/CHANGELOG.md @@ -1,5 +1,65 @@ # @object-ui/plugin-timeline +## 18.0.0 + +### Minor Changes + +- 5cb75b3: fix(timeline,list): the timeline honours `timeline.dateField`, not just `timeline.startDateField` (#3129) + + `dateField` is the pre-#2231 alias for `startDateField`. `@object-ui/types` + declares it on the nested config (`ListViewTimelineConfig`), and both + `ObjectView` read-sites (app-shell and plugin-view) resolve it — but the two + read-sites that actually drive the axis did not: + + - `ObjectTimeline` consulted the alias only on the FLAT prop (`schema.dateField`), + never on the nested `schema.timeline`. + - `ListView` resolved it out of `options.timeline` but not out of the + spec-canonical `schema.timeline` — including in the capability gate, so such a + view could fail to offer the Timeline option at all. + + So a view authored as `timeline: { dateField: 'start_date' }` — the spec nesting + with the legacy key — fell through to the caller's default (`created_at` / + `due_date`). That field is normally absent from the `$select` projection, so + every record came back without it and the timeline rendered all of them under + **No date** — while the configured date was sitting in the row untouched. That + also explains why widening the view's projection changed nothing: the projection + already carried the right field; the renderer was reading a different one. + + Both read-sites now resolve the alias in the same precedence position they + already use for `options.timeline.dateField`. The spec key still wins wherever + both appear. Observable rendering change (records move out of "No date" into + real date buckets), hence `minor`. + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/mobile@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-timeline/package.json b/packages/plugin-timeline/package.json index 1245c5059f..6db4cd8139 100644 --- a/packages/plugin-timeline/package.json +++ b/packages/plugin-timeline/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-timeline", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Timeline component plugin for Object UI", diff --git a/packages/plugin-tree/CHANGELOG.md b/packages/plugin-tree/CHANGELOG.md index 30fce49d5a..70e67bd3ba 100644 --- a/packages/plugin-tree/CHANGELOG.md +++ b/packages/plugin-tree/CHANGELOG.md @@ -1,5 +1,34 @@ # @object-ui/plugin-tree +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/plugin-tree/package.json b/packages/plugin-tree/package.json index bbb8bdcc23..1fc6dd0ff8 100644 --- a/packages/plugin-tree/package.json +++ b/packages/plugin-tree/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-tree", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Tree / tree-grid visualization plugin for Object UI", diff --git a/packages/plugin-view/CHANGELOG.md b/packages/plugin-view/CHANGELOG.md index b8b7f9fafc..30f2d37aab 100644 --- a/packages/plugin-view/CHANGELOG.md +++ b/packages/plugin-view/CHANGELOG.md @@ -1,5 +1,39 @@ # @object-ui/plugin-view +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [5eaa861] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/plugin-grid@18.0.0 + - @object-ui/plugin-form@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/plugin-view/package.json b/packages/plugin-view/package.json index d8e0e60b09..cb104da6bb 100644 --- a/packages/plugin-view/package.json +++ b/packages/plugin-view/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/plugin-view", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Object View plugin for Object UI", diff --git a/packages/providers/CHANGELOG.md b/packages/providers/CHANGELOG.md index aafa458e86..4bd978b838 100644 --- a/packages/providers/CHANGELOG.md +++ b/packages/providers/CHANGELOG.md @@ -1,5 +1,19 @@ # @object-ui/providers — Changelog +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [4bf612c] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/providers/package.json b/packages/providers/package.json index b92db78c77..21305b79a6 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/providers", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "Reusable context providers for ObjectUI applications", diff --git a/packages/react-runtime/CHANGELOG.md b/packages/react-runtime/CHANGELOG.md index 9c943ae548..1dc8d19778 100644 --- a/packages/react-runtime/CHANGELOG.md +++ b/packages/react-runtime/CHANGELOG.md @@ -1,5 +1,7 @@ # @object-ui/react-runtime +## 18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/react-runtime/package.json b/packages/react-runtime/package.json index 7174518a65..84768494d5 100644 --- a/packages/react-runtime/package.json +++ b/packages/react-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/react-runtime", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "sideEffects": false, "license": "MIT", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 965d17d0ae..8ae0f78fa7 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,151 @@ # @object-ui/react +## 18.0.0 + +### Major Changes + +- 09d30a4: Stop declaring 18 `@object-ui/auth` / `@object-ui/components` / `@object-ui/react` + symbols under names `@objectstack/spec` owns (objectui#3159, objectstack#4115 + batch 5). + + **Breaking for importers of all three packages** — six exported names changed, + because the spec exports the same name for a _different_ thing: + + | package | was | now | what the spec's same-named export actually is | + | :----------- | :--------------------------- | :----------------------- | :----------------------------------------------------------------------------- | + | `auth` | `AuthSession` | `AuthClientSession` | the SERVER's session record (`{ id, userId, expiresAt: ISO string, token? }`) | + | `auth` | `AuthProviderConfig` | `AuthProviderOptions` | an OAuth/OIDC provider registration (`{ id, clientId, clientSecret, scope? }`) | + | `components` | `FilterCondition` | `FilterBuilderCondition` | the recursive ObjectQL predicate AST (`$and`/`$or`/`$not`) | + | `components` | `Field` | `FieldContainer` | an object FIELD's metadata and its builder namespace | + | `react` | `ConflictResolutionStrategy` | `ConflictResolution` | the metadata-MERGE policy (`error \| priority \| first-wins \| last-wins`) | + + The `react` rename is the odd one out: the new name is the **spec's own** name + for the union that hook always used, so it is a re-export rather than a dialect. + + Eleven more keep their names and are now **imported or derived from the spec** + instead of re-declared: `TenancyPosture`, `DelegableScope` (+`DelegableAdminScope`), + `AuthUser`, `ShareLinkPermission`, `ShareLinkAudience`, `ShareLink`, `SortItem`, + `OfflineStrategy`, `OfflineCacheConfig`, `OfflineSyncConfig`, `OfflineConfig`, + `NavigationConfig`. + + **Three of the copies were losing information, not just duplicating it.** + + - `AuthUser` never declared the spec's `positions` or `tenantId` — the + authorization inputs. Its `[key: string]: unknown` index signature meant the + omission was invisible at every call site _and_ to any structural comparison + (the objectstack#4075 mechanism). It now `extends` the spec principal, so the + display-only fields (`image`, `role`, `roles`, `emailVerified`) are the delta + and the spec's keys arrive on their own. + - `useNavigationOverlay`'s copy carried the note _"inline … to avoid importing + from `@object-ui/types` (which may not be a direct dependency of + `@object-ui/react`)"_. The vocabulary belongs to `@objectstack/spec`, which + **is** a direct dependency — the same expired "kept local to avoid a + dependency" comment objectui#3169 found in `@object-ui/app-shell`. + - `useOffline` and `usePerformance` both opened with _"Types aligned with + `@objectstack/spec` v2.0.7"_. The installed spec is 17.0.0-rc.1. + + `ShareLink` derives from the spec row **minus `password_hash`** — omitted rather + than optional, because it is the credential itself and typing it in a browser + package is an invitation to render it. `password_protected` (the boolean the UI + needs in its place) is the one local addition. + + The config types derive from each schema's **input** side, not `z.infer`. + `useOffline(config: OfflineConfig = {})` defaults to the empty object, which the + output type — every `.default()`ed key required — would reject outright. + + `@objectstack/spec` moves from `devDependencies` to `dependencies` in + `@object-ui/components`: its public type surface now references the spec. + +### Minor Changes + +- a889e31: A record's approval band now shows the quorum / per-group tally the server already computes. + + The showcase's `showcase_committee_quorum` node declares `behavior: 'quorum'` with + `minApprovals: 2` over three approvers, and even ships a pre-rendered + `"Committee Sign-off (2 of 3)"` label; `showcase_expense_signoff` declares + `per_group` (会签) with named manager / finance groups. On the business record + the approval band rendered none of it — the lock badge, the recall button and + the approve/reject actions were all correct, but a two-of-three committee step + looked exactly like a one-approver step. An approver could not see whether their + own click finalized the node or was one of three, which is the single fact a + quorum node exists to express (objectstack#4478). + + Nothing was wrong on the wire, and nothing here papers over the server. The + framework computes `decision_progress` — `{ behavior, got, need, groups? }`, + derived from the node's own `node_config_json` snapshot, so the count a client + shows is the count the engine will enforce. **It attaches that block in + `getRequest` only**: `listRequests` deliberately skips it, because the + `sys_approval_action` tally it costs is per row and a list read may return + hundreds. The record header's `useRecordApprovals` reads + `GET /approvals/requests?object=…&recordId=…` — the list route — so the + enrichment was never in the payload it had. The hook now follows up with one + single read for the ONE pending row and folds the result onto it; a failed or + mismatched follow-up leaves the row exactly as the list sent it, so a display-only + enrichment can never take the approval panel down and no tally is ever invented. + + `InlineEditProvider` carries the block through as `approvalProgress`, and the + DetailView approval band renders it beside the existing badge: a labelled + `role="progressbar"` with one tick per required approval for `quorum` / + `unanimous`, and for `per_group` a chip per group marking which have signed + (`finance 1/1` ✓, `manager 0/1`). Group names come from the flow author's own + config, so they need no locale strings; the three new label keys are added to all + ten packs. `first_response` nodes carry no `decision_progress` and are unchanged — + one decision is the whole step there, and a "1 of 1" bar would be noise. + + Scored `minor` rather than `patch`: this is new observable rendering plus a new + public `approvalProgress` prop / `ApprovalProgress` type on `@object-ui/react`, + not a behavior correction inside an existing surface. + +### Patch Changes + +- ea96284: `useMetadataItem` no longer spins forever outside a `` — the "graceful fallback" was the thing that made those consumers impossible to mount. + + `useMetadata()` built its no-provider fallback **inline on every call**, so outside a provider + every render produced a new `getItem`. `useMetadataItem` lists `getItem` in its effect deps and, + on the no-name path, called `setState({ item: null, loading: false, error: null })` with a fresh + object each run. New identity → effect re-runs → new state object → re-render → new identity: + an unbreakable loop, synchronous enough to hang inside `render()` rather than fail. + + So the fallback documented as the graceful path for consumers mounted outside a provider — + "common in unit tests that only need to assert on rendering" — was precisely what made them + unmountable. `record:alert` and `record:quick_actions` both call `useMetadataItem` + unconditionally; each pinned a core and grew unbounded (8.6 GB before the first kill) on a + `render()` that never returned. + + Two changes, at the cause and one layer in: + + - The fallback is a frozen module-level singleton, so its identity is stable across renders. + - The clear-state path bails out when the state is already cleared, instead of installing an + equal-but-new object. That covers the same loop arriving by another route — any caller whose + context value is rebuilt per render, which this interface explicitly invites ("hand-rolled + context values in tests keep working"). + + Found by `apps/console/src/__tests__/record-block-record-reach.test.tsx` (objectui#3149), which + could not mount either block until this was fixed. + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [4b470b9] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/i18n@18.0.0 + - @object-ui/data-objectstack@18.0.0 + ## 17.1.0 ### Minor Changes diff --git a/packages/react/package.json b/packages/react/package.json index cc77d5d2d8..b36c85014f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/react", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "license": "MIT", "description": "React bindings and SchemaRenderer component for Object UI", diff --git a/packages/runner/CHANGELOG.md b/packages/runner/CHANGELOG.md index 227bd8ecd0..6dd250cd61 100644 --- a/packages/runner/CHANGELOG.md +++ b/packages/runner/CHANGELOG.md @@ -1,5 +1,36 @@ # @object-ui/runner +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [a889e31] +- Updated dependencies [09d30a4] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [f6e8d78] +- Updated dependencies [ea96284] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/components@18.0.0 + - @object-ui/core@18.0.0 + - @object-ui/react@18.0.0 + - @object-ui/plugin-charts@18.0.0 + - @object-ui/plugin-kanban@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/runner/package.json b/packages/runner/package.json index b80e998275..7dc2a46669 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -1,7 +1,7 @@ { "name": "@object-ui/runner", "private": false, - "version": "17.1.0", + "version": "18.0.0", "description": "Universal Object UI Application Runner", "type": "module", "homepage": "https://www.objectui.org/docs/utilities/runner", diff --git a/packages/sdui-parser/CHANGELOG.md b/packages/sdui-parser/CHANGELOG.md index 02895022c7..40bb83b7ba 100644 --- a/packages/sdui-parser/CHANGELOG.md +++ b/packages/sdui-parser/CHANGELOG.md @@ -1,5 +1,43 @@ # @object-ui/sdui-parser +## 18.0.0 + +### Patch Changes + +- cc70b8f: A declared `objectName` must reach the data layer — the evidence the framework's spec↔registry check cannot gather (objectstack#4472). + + The framework diffs `sdui.manifest.json` against the spec's zod schemas and, while that + check was named `check:react-conformance`, it was read — by its own file header — as + confirming these components "ACTUALLY implement" the spec's props. It never could. Both + sides of that diff are **declarations**, and this repo produces one of them: + `manifestFromConfigs` copies `config.inputs` verbatim and cannot observe whether the + renderer behind a block reads any of them. So a prop both sides declare and nothing + consumes reads there as agreement — which is how objectstack#4413's four `record:*` blocks + published an `objectName`/`recordId` no renderer read, rendered blank, and stayed green. + + Evidence about the render path has to be taken from the render path, so it lives here now. + `apps/console/src/__tests__/public-block-binding-reach.test.tsx` mounts every public block + that declares an `objectName` input through `SchemaRenderer` with nothing but that binding, + under a provider whose `dataSource` is a Proxy recording every call, and asserts some call + carried the object name. Deliberately narrow — "is this binding wired", not "is every + declared input consumed", which is not decidable from outside without heuristics. Every + non-reaching block carries a written reason in a ledger asserted to equal the observed set + in **both** directions, so a block that starts binding forces its entry deleted and a block + that stops binding fails; the suite was verified to go red both ways. + + First run: five of eight bound blocks reach the data layer, three do not. + `record:related_list` legitimately declines to fetch without the parent record id from + `RecordContext` (already documented in @objectstack/spec's objectstack#4413 ledger). + `list-view` and `embeddable-form` do not, and that is a real defect of the same shape — + neither registration bridges the schema-renderer context onto the component's `dataSource` + prop the way `object-form` / `object-kanban` / `object-calendar` do, and `SchemaRenderer` + never injects it, so on the registry/SDUI path both render an empty shell while declaring + `objectName` **required**. Filed as objectui#3144 rather than fixed here: giving them a + data source changes what they render everywhere they are mounted bare. + + `manifestFromConfigs` and `scripts/dump-public-manifest.mjs` now say in their own docs that + what they emit is what a registration _declared_, never what a renderer reads. + ## 17.1.0 ### Minor Changes diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index 59bb5b82c6..d10a832a44 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/sdui-parser", - "version": "17.1.0", + "version": "18.0.0", "type": "module", "sideEffects": false, "license": "MIT", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 4d971b1fad..912746dee7 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,365 @@ # @object-ui/types +## 18.0.0 + +### Minor Changes + +- 4ae0ac4: One placement rule for action `locations` (objectui#3142). + + **Breaking for metadata**: an action that declares no `locations` (missing key + or `[]`) no longer renders in a located surface. FROM: omitting `locations` + made an action appear on the list toolbar, the record header, and every + metadata-admin toolbar. TO: declare where it belongs — + `locations: ['record_header']` for the record header, `['list_toolbar']` for + the list toolbar, and so on. Nothing else changes; actions that already + declare a location are untouched. + + Four renderers each answered "where does an action with no `locations` go?" + differently — `action:bar` and metadata-admin showed it EVERYWHERE, + `page:header` showed it on the header, `action:group` showed it for + `undefined` but hid it for `[]` — while `ActionEngine`, `RecordDetailView`, + `DeclaredActionsBar`, the related-list bridge and the environment toolbar all + showed it NOWHERE. The same action therefore appeared or vanished depending on + which component happened to draw it. All eight now go through one exported + predicate, `actionRendersAt(action, location)` from `@object-ui/types`: an + action renders at a location only if it declares that location. + + The strict reading is the platform's own — ADR-0078 lists "an `action` with no + `locations`" as a verified inert shape, and the detail-page synthesizer already + documented "must include `locations: ['record_header']` to render". The + leniency contradicted both, and it is what let an aggregate-only bulk action + (objectui#3139) — one with no single-record placement by construction — mint a + list-toolbar button whose dispatch could only fail. + + Two placements are declared elsewhere and need no `locations`, both unchanged: + host-injected chrome in the `systemActions` / `headerSystemActions` slot (now + consistently exempt on `page:header` too, where it used to be filtered), and an + action named in a view's `bulkActions` / `bulkActionDefs`. + + Authoring side: Studio seeds `locations: ['record_header']` on a new action + instead of minting one that renders nowhere, and the action inspector says so + when no placement is ticked. The `ActionSchema.locations` JSDoc claimed a + `['record_header']` default that no renderer ever implemented — corrected. + +- 696e3c1: `reference` is the one authorable action-param picker target (objectui#3174). + + **Breaking for authoring**: `ActionParam` in `@object-ui/types` no longer + declares the nine resolved-side picker keys — `referenceTo`, `displayField`, + `idField`, `descriptionField`, `titleFormat`, `lookupColumns`, `lookupFilters`, + `lookupPageSize`, `dependsOn`. FROM: `{ name: 'account_id', type: 'lookup', +referenceTo: 'account' }` type-checked. TO: `{ name: 'account_id', type: +'lookup', reference: 'account' }` — or make the param field-backed + (`{ field: 'account_id' }`) and it inherits the whole picker group from the + object field. + + The two halves of one contract disagreed about a spelling, and the type was the + half that was wrong. `resolveActionParams()` reads the spec's `reference` for an + inline `lookup`/`master_detail` target and nothing else; it EMITS + `ActionParamDef.referenceTo`, the resolved spelling. The public authoring type + declared the resolved spelling "for parity with the resolved shape", so an + author who followed it got a param whose picker target was dropped in the + resolver and a dialog that degraded to a plain record-id text input — asking a + human to paste a UUID. The dev warning that fired then told them to declare + `reference`, a key the type did not have. + + `reference` wins because the platform had already decided: `ActionParamSchema` in + `@objectstack/spec` is `.strict()`, lists `referenceTo` **by name** in its + alias map, and answers it with "use `reference`". So an authored `referenceTo` + was never storable — it was a hard parse rejection on the server while `tsc` + waved it through. Resolving it in objectui instead would have made the renderer + accept metadata the platform itself refuses, and such a param would work in a + locally-authored TS action and fail at publish; removing the declaration moves + the failure to where it can be fixed, at the authoring keystroke. + + - **`@object-ui/types`**: the nine keys are gone, and the rule they violated is + now pinned — `ActionParam` declares _exactly_ the spec's authorable key set. + The drift guard names the single exception (`validation`, inert and rejected + by the same `.strict()` parse — filed as objectui#3201) so a second one cannot + appear without being a decision. + - **`@object-ui/app-shell`**: `resolveActionParams()` names any resolved-only + key it finds on an authored param in a dev-mode warning, with the + prescription (`referenceTo` → "use `reference`"; the rest → "make the param + field-backed"). It still does **not** read them. This covers the gap `tsc` + cannot gate — params authored in plain JS, loaded from JSON, or synthesised + at runtime — so the mistake is loud where it is made rather than surfacing + downstream as `paramToField()`'s "no reference target" warning naming a key + the author never wrote. + + The internal pipeline keeps its two spellings on purpose (authoring `reference` + → `ActionParamDef.referenceTo` → the field's `reference_to`); what is pinned now + is that the public entry and the public exit agree. The end-to-end test authors + through the published `ActionParam` and follows one param to `reference_to` — + every previous test authored the resolver's own local input interface, which is + why the resolver only ever agreed with itself and the mismatch survived. + +- 4bf612c: Aggregate single-call mode for bulk actions: `execution: 'aggregate'` (objectui#3139). + + A `bulkActionDefs` entry with `operation: 'custom'` used to have exactly one + dispatch shape: one action-runner call per selected record (`_rowRecord` + attached). "Select N rows → ONE call that receives every selected id" — the + zip-of-QR-codes / merged-PDF / batch-print shape — could not be expressed, so + downstream projects fell back to per-row `window.open` storms or gave up. + + `BulkActionDef` now carries `execution?: 'perRecord' | 'aggregate'` (default + `'perRecord'`, existing views untouched). An aggregate def dispatches its + action exactly once for the whole selection with `params._selectedIds: +string[]` injected and the full records published as + `context.selectedRecords`. The authored form usually just names a declared + object action — `{ name, operation: 'custom', execution: 'aggregate' }` — + and `resolveBulkActions` attaches the declaration. Results are + all-or-nothing: a failure is attributed to every id with the real error and + per-row Retry is hidden (re-running the action is the retry; a total failure + keeps the selection). `batchSize` does not apply; `maxRecords` still gates. + + The executor rides the existing `executeBulkBatch` bulk-first decision tree — + the aggregate call is its `bulkCall`, and the per-row "fallback" only + re-throws the captured error for attribution, never fans out N dispatches + against an endpoint written for one `_selectedIds` call. + + Also: url/api target interpolation now exposes `${ctx.selection.ids}` (comma + -joined) and `${ctx.selection.count}` from the grid's checkbox selection, so + a plain `list_toolbar` action can carry the selection without bulk plumbing; + the console's server-action handler recognizes `_selectedIds` and skips the + single-record multi-select guard for aggregate dispatches. + +- 022e4c3: Upgrade to `@objectstack/spec@17.0.0-rc.1`, stop offering the retired `wait` timeout fields (#3101), and route the newly-adopted `combo` chart type. + + **Breaking for authoring, and the reason to do it now**: the `wait` panel no longer offers + `waitEventConfig.timeoutMs` or `.onTimeout`. Both are `retiredKey()` tombstones as of spec + 17.0.0-rc.1 (framework#4158), which means a value written there is **rejected at load** — + so until this lands, Studio can produce flow metadata the author's own runtime refuses. + That hazard opened the moment rc.1 published, independent of when this repo bumps. + + `wait` never had a timeout: `onTimeout` had zero readers, so neither `'fail'` nor + `'continue'` ever happened, and `timeoutMs`'s only reader used it as the timer **duration** + when `timerDuration` was absent. Use **Duration** — it accepts a bare number as + milliseconds, making the old `timeoutMs: 60000` and `timerDuration: '60000'` the same wait. + Stored flows are converted by framework's D2 conversion; the designer simply stops offering + the entry. The two `zh` label overrides go with the fields. + + #3101 asked for this to ride along with the bump rather than land alone, and that is + load-bearing: the sibling-block assertion is **bidirectional**, so deleting the fields + against a spec that still declares them fails in the other direction. + + **`combo` is now a spec chart type** — the sole addition to `ChartTypeSchema` in rc.1 (19 + members → 20). It had been a renderer-local family the chart renderer derived from the + series, so nothing classified it on the two surfaces that route a _spec_ chart type: a + spec-valid `combo` fell through to the red "Unknown component type" panel on a dashboard + and to the out-of-spec notice on a report. Both now route it + (`widgetDispatch.SERIES_CHART_TYPES`, `planReportChart`). The renderer-local derivation + stays — it is what makes an authored `type: 'combo'` render rather than merely validate. + + **Retired spec exports this repo bound to**, all removed upstream in spec 17.0.0: + + - `JoinStrategy` / `WindowFunction` (framework#4286 tombstoned `query.joins` and + `query.windowFunctions`: no engine or driver ever read either on the query path). They + were derived off the spec enums under objectstack#4115's "come off the spec enum, not a + restatement" rule; with no enum left, `data-protocol.ts` now restates the members locally + — verbatim from the last spec that published them — as the objectui query-AST vocabulary + they have become. The AST itself is unchanged. + - `PerformanceConfig`, retired with `dashboard.performance` (framework#3896). Nothing bound + to it — `@object-ui/react`'s `usePerformance` declares its own interface and is untouched. + The dashboard form is derived from the spec's own `dashboardForm`, so the field + disappears from the inspector for free; its test now pins the absence. + + **Three inverted pins fired, and are recorded rather than resolved.** objectstack#4171's + tripwires asserted that `NavigationItem`, `FormField` and `ConditionalValidation`'s branches + still erased to `any`/`unknown` upstream — the premise that justified objectui keeping local + declarations. rc.1 types them properly, so the assertions are inverted to state the new + fact. The burn-down each one asks for — deriving those types from the spec — touches + widely-used public types and is deliberately **not** bundled into a version bump; it is + tracked in #3177. `JoinNode`'s pin is gone outright: the symbol no longer exists. + + **What the bump arms.** The reconciliation ledger's `subflow` and `decision` panels + feature-detect their spec exports and had never actually run — rc.0 predates the exports + (framework#4278). They now execute and pass. The `script` panel's full bidirectional check + stays deliberately skipped: rc.1 predates framework#4343, so the retired dispatch branches + are still contract keys there, and only the "offers nothing the executor ignores" direction + is meaningful. It arms itself on the next rc. + +- 009e25d: Report / chart / query symbols stop wearing `@objectstack/spec`'s names + (objectui#3155, objectstack#4115). + + **Breaking for TypeScript imports** — six exported names change. Each was a + different concept than the spec export it collided with, so an author reading + the objectui declaration as "the spec's" was reading a false claim: + + | was | now | why they were never the same thing | + | :------------------ | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `ChartSeries` | `ChartDataSeries` | ours is a display name plus literal `data: number[]`; the spec's is a dataset-bound series descriptor (`type`/`stack`/`yAxis`/`variant`) with no data at all | + | `ChartSeriesSchema` | `ChartDataSeriesSchema` | zod twin of the above | + | `QueryAST` | `SqlQueryAST` | ours is a compiled SQL syntax tree (`select`/`from`/`join`/`group_by`); the spec's is the ObjectQL request descriptor (`object`/`fields`/`where`/`expand`) | + | `QuerySchema` | `DriverQueryConfig` | ours is the high-level config `QueryASTBuilder` compiles; the spec exports that name as a zod schema value | + | `DriverInterface` | `SqlDriverInterface` | ours is objectui's SQL-oriented client abstraction (`query(sql, params)`); the spec's is the platform runtime driver contract | + | `DatasourceSchema` | `DatasourceRegistration` | ours is the in-memory record `DatasourceManager` holds — its `driver` is a live instance; the spec's is the authored metadata document, where `driver` is a name | + + Three more are now DERIVED from the spec instead of hand-restated, which fixes + live silent-stripping defects, since a `z.object()` drops unknown keys: + + - **`DashboardWidgetSchema`** declared 10 of the spec's 22 keys, so + `objectui validate` deleted the other 12 without a word — `chartConfig`, + `colorVariant`, `filter`, `responsive`, `aria`, + `actionUrl`/`actionType`/`actionIcon`, `compareTo`, `suppressWarnings` and the + `requiresObject` / `requiresService` capability gates the dashboard renderer + honours at runtime. The TS interface had declared most of them all along, so a + widget could type-check and still lose half its configuration on validation. + Pinned divergences kept: `id` stays optional, `type` stays widened for the + objectui-only `list` / `custom` families, and the legacy `component` envelope + stays. + - **`GlobalFilterSchema`** took `scope` as a free-form string (any typo + validated); it now uses the spec's `widget | dashboard` vocabulary. The three + objectui widenings that back a real runtime normalizer are kept and pinned: + the bare-string `options` shorthand, the normalized `{ preset }` date default, + and an optional `optionsFrom.labelField`. + - **`AppContextSelectorSchema`** was a full restatement; spec keys and their + defaults now flow in by reference, with `label` widened for objectui's i18n + label envelope — which `AppContextSelectors` already renders. + + `ListViewSchema`'s zod node now names the spec in its own initializer rather + than one hop away through a local const, so its long-standing derivation is + visible where it is declared. + + Drift guard: `packages/types/src/__tests__/report-chart-query-spec-parity.test.ts`. + +- 726b89c: `@object-ui/types` stops declaring sixteen symbols under names `@objectstack/spec` owns (objectui#3156, objectstack#4115). + + Seven are now **derived** from the spec, nine are **renamed** to the local + dialect they always were. Both halves remove the same hazard: a local + declaration under a spec export's name reads as the spec's own definition to + the next reader, so a copy that is merely _correct today_ is a planted premise + tomorrow. + + **Derived** — the spec now supplies the keys, by reference: + + | symbol | derivation | + | :----------------------- | :-------------------------------------------------------------------------------- | + | `ActionParam` | `z.input`, `type` widened to the local legacy spellings | + | `CreateExportJobRequest` | `Omit` (`object` is the method argument) | + | `CreateExportJobResult` | re-export from `@objectstack/spec/contracts` | + | `ImportRowResult` | re-export from `@objectstack/spec/api` | + | `NavigationArea` | spec keys, with `navigation` / `visible` pinned locally | + | `NavigationAreaSchema` | `specFieldsExcept(NavigationAreaSchema.shape, …)` | + | `Theme` | re-export of the spec's `ThemeInput` (the authoring shape) | + | `ExportJobFormat` | re-export of the spec's `ExportFormat` | + + Four of these close real gaps rather than tidy names. `ActionParam` never + declared `reference` — the key `resolveActionParams()` actually reads for an + inline lookup target — nor `defaultFromRow`, which the metadata designer's own + inspector writes; it also narrowed `visible` to a bare string although the + resolver has always accepted the `{ dialect, source }` envelope too. + `CreateExportJobResult.createdAt` and `ImportRowResult.action` were optional + here and required by the server, leaving every consumer a branch that could + never run. And `NavigationArea`'s `id` now carries the spec's own length rule + instead of accepting any string. + + **Renamed** — same word, different concept: + + | was | now | why | + | :----------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------- | + | `FileMetadata` | `UploadedFileMetadata` | field-VALUE payload (`url`, `original_name`), not the storage file record | + | `GestureType` | `TouchGestureType` | direction-fused (`swipe-left`), not the spec's type+direction pair | + | `GestureConfig` | `TouchGestureConfig` | gesture→`action` binding, not per-gesture tuning | + | `OfflineConfig` | `PWAOfflineConfig` | service-worker route caching, not the offline data/sync model | + | `PageRegion` | `PageNodeRegion` | region of the renderer page NODE, holding `SchemaNode`s | + | `PageRegionSchema` | `PageNodeRegionSchema` | zod twin of the above | + | `ResponsiveConfig` | `MobileResponsiveConfig` | mobile box config, not the spec's SDUI grid contract | + | `WidgetManifest` | `RuntimeWidgetManifest` | SDUI component manifest, not the field-widget plugin manifest | + | `WidgetSource` | `RuntimeWidgetSource` | `module`/`inline`/`registry` loader union — and its `inline` carries a resolved component where the spec's carries source code | + + **Migration**: the old names are gone, not deprecated — an alias would preserve + exactly the ambiguity being removed. Import the new name; nothing about the + shapes changed. `@object-ui/types` already re-exports the spec's own + `SpecResponsiveConfig`, and `@object-ui/react`'s `useOffline` config remains the + spec-shaped `OfflineConfig`, so both concepts stay reachable under + distinguishable names. + + Each rename carries a bidirectional tripwire + (`packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts`): it fails if + the spec ever claims the new name, and also if the spec retires the old one — + at which point the natural name can be taken back rather than the workaround + outliving its reason. + +### Patch Changes + +- cb82705: A standalone grid's search box searches the list, not the page you can see (objectui#3118). + + Under server-side pagination a standalone `ObjectGrid` rendered `data-table`'s + built-in search box, and that box filtered the rows the table was holding — + which is one page. The user read "2 results for X in this list" while 3075 rows + never participated, with the pager beside it still reading `1 / 63`. Every piece + was individually correct: `searchable` defaults to true, `manualPagination` is + true, and the two are declared next to each other in the same object literal. + + This is objectui#3106 one axis over — sort there, filter here — and it takes the + same shape. `DataTable` gains `manualSearch` + a controlled `search` + + `onSearchChange`. In that mode it filters nothing, reports the typed term, and + renders `search` as the box's value, holding **no** term of its own: a private + copy beside a controlled prop is the shape the defect had. `ObjectGrid` turns + that term into a `$search` on the refetch — the server picks the matching fields + from the object's metadata (ADR-0061), the same channel the ListView toolbar has + always used — and returns to page 1, since a new term makes the old page index a + different set of rows (usually no rows at all). `$searchFields` rides along only + when the view declared `searchableFields`, which can narrow the server-resolved + set and never widen it. + + Two things worth naming: + + - Both paths are never live at once. The server's answer is the answer; a client + pass left running underneath would silently re-narrow it to whichever returned + rows happen to contain the term as _rendered text_, overruling the server's + own notion of which fields are searchable. + - Under `manualSearch` a table with no `onSearchChange` renders **no** search + box. The sort axis could degrade to inert headers; here there is no honest + local behaviour to fall back to, because the rows to search are not in the + browser. + + Client-paginated grids are untouched: inline, bound and grouped grids hold every + row they display, so their box keeps filtering in memory, where the count it + produces is true. The ListView path was never affected — it passes + `showSearch: false` and searches from its own toolbar. + +- f572849: Fix the admission probes behind objectstack#4171's three inverted pins, and + derive the `NavigationItem` keys that genuinely became derivable (objectui#3177). + + Spec 17.0.0-rc.1 typed `NavigationItem`, `FormField` and + `ConditionalValidation.then`/`.otherwise`, so the `IsAny` / `IsUnknown` pins + guarding them fired. Firing was supposed to mean "the burn-down is due". A + per-symbol triage found it did not: **`any` was never the only blocker for any + of the three**, so "no longer `any`" was never the right admission question. + Nothing was bound; the probes now ask the condition that actually governs each + symbol, and each still asserts today's state — so they pass now and stop + compiling the day their own blocker lifts. + + - `NavigationItem` — the spec models navigation as a nine-variant discriminated + union; objectui keeps one flat shape, and the spec has no counterpart at + either tier for `visible: boolean` (which `menuItemToNavigationItem` + manufactures when it inverts legacy `MenuItem.hidden`), `pinned` (backs + `useNavPins`), the legacy `defaultOpen` spelling, or a separator carrying a + `label`. Four probes, one per blocker. + - `FormField` — two concepts on two layers, not two dialects of one: the + required keys are disjoint (objectui `name` = the form data path; spec + `field` = an object-field reference, with no `name` at either tier), and the + shared `field` key is a string on one side and the resolved metadata object + on the other. Binding would also collapse the objectui#3090 disambiguation + that exports `SpecFormField` separately, and revert framework#4074's + `dependsOn` widening. + - `ConditionalValidation` — the branches went from `unknown` to + `BaseValidationRuleShape`, which is `{ type: string; …; [key: string]: +unknown }`. Better than `unknown`, still not derivable: `type` is not a + literal union so a branch cannot narrow by discriminant, and the index + signature waves through any member — a typo'd `type: 'formatt'` included. The + spec says so itself and names the remaining work as objectstack#4075. The + probe now pins "literal discriminant / no index signature", so it goes green + exactly when that lands. + + What DID become derivable is derived. `NavigationItemType` now comes off the + spec's own nav-item discriminant instead of a hand-written nine-member copy — + the objectstack#4115 failure class, and it also makes a future spec variant a + compile error at exhaustive consumers rather than a silent `default:`. Same for + `recordMode`, `filters`, `badge`, `target`, `params` and `actionDef`, each taken + from the spec branch that owns it, extending the existing `badgeVariant` + precedent. No member changes today, so no consumer is affected. + ## 17.1.0 ### Minor Changes diff --git a/packages/types/package.json b/packages/types/package.json index adc65f7492..4b59fd24c1 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@object-ui/types", - "version": "17.1.0", + "version": "18.0.0", "description": "Pure TypeScript type definitions for Object UI - The Protocol Layer", "type": "module", "sideEffects": false, diff --git a/packages/vscode-extension/CHANGELOG.md b/packages/vscode-extension/CHANGELOG.md index 5112dda265..ab7dd7a063 100644 --- a/packages/vscode-extension/CHANGELOG.md +++ b/packages/vscode-extension/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 18.0.0 + +### Patch Changes + +- Updated dependencies [4ae0ac4] +- Updated dependencies [696e3c1] +- Updated dependencies [bca45cc] +- Updated dependencies [4bf612c] +- Updated dependencies [335041c] +- Updated dependencies [b414983] +- Updated dependencies [256f8cc] +- Updated dependencies [d9668a7] +- Updated dependencies [cb82705] +- Updated dependencies [f572849] +- Updated dependencies [d3584c6] +- Updated dependencies [a8ad6c0] +- Updated dependencies [850033c] +- Updated dependencies [022e4c3] +- Updated dependencies [009e25d] +- Updated dependencies [726b89c] + - @object-ui/types@18.0.0 + - @object-ui/core@18.0.0 + ## 17.1.0 ### Patch Changes diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 77e9717340..eb62a7b860 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -2,7 +2,7 @@ "name": "object-ui", "displayName": "Object UI", "description": "VSCode extension for Object UI - Schema-driven UI development with IntelliSense, validation, and live preview", - "version": "17.1.0", + "version": "18.0.0", "publisher": "objectui", "private": true, "icon": "icon.svg",