You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 22ae443
Browse filesBrowse the repository at this point in the historyBrowse files
Merge main into the 2g PR 2 branch; carry #26274 onto the one parser
#26274 retired the node-level visibleOn / hiddenOn / disabledOn spellings
and edited the ui copy of the parser, which this branch deletes. Its
parser half is carried onto packages/sdui-parser so it is not lost:
the three leave SDUI_BASE_PROPS, validateTree answers each with an
unknown-prop naming the canonical key, retired-visibility-dialects.ts
moves in with its MIT notice, and base-props-one-list-11044 takes the
same test edits. The pending changeset 25926 names
@objectstack/sdui-parser, where that behaviour now ships.
Claude-Session: https://claude.ai/code/session_01Xe3LjBvLrRVA6dmFUEGskm
Co-authored-by: Claude <noreply@anthropic.com>
feat(types,react,sdui-parser,app-shell)!: the node-level `visibleOn` / `hiddenOn` / `disabledOn` spellings are retired; `visibleWhen` / `hidden` / `disabled` are the one predicate each
9
+
10
+
Clause-②: no (narrowing)
11
+
Metadata-protocol: narrowed
12
+
13
+
<!-- adr-0087: not-required (no-migration-prescription) The three keys are `@object-ui/types` schema members on objectui's own node face, not an `@objectstack/spec` key, object definition, stored row or stored shape: the spec declares `visibleWhen` as the one conditional-visibility predicate at every layer (ADR-0089) and never declared a `*On` suffix on a page component, so the ADR-0087 registries in `packages/spec` have no entry to write and `objectstack migrate meta` has nothing to rewrite. Each old spelling is refused by name on both faces (the TypeScript compiler, the Zod mirror behind `objectui validate`), and the refusal names the surviving key; that is the same disposition the objectui retirements took when the package merged into this repository (maintainer ruling 28 = B on objectstack#25831). The guidance below is for the author of a page, not for a metadata upgrader. The other categories are closed on facts: the four packages publish (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and what narrows is a declared schema key on a published metadata face, not a runtime-only interface, a type annotation or a runtime export (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
14
+
15
+
`@objectstack/spec` declares exactly one conditional-visibility predicate at every layer, `visibleWhen`, and objectui carried three spellings of its own beside it: `visibleOn` beside `visibleWhen`, `hiddenOn` beside `hidden`, `disabledOn` beside `disabled`. Each pair was evaluated by the same code with the same polarity, so the second spelling added a dialect and nothing else. The three are retired, with no alias window.
16
+
17
+
-**`@object-ui/types`**: `BaseSchema.visibleOn`, `hiddenOn` and `disabledOn` are `never` on the TypeScript face and alias refusals on the Zod mirror. A document that writes one is refused at that key, and the message names the key that survives (`visibleWhen`, `hidden`, `disabled`), says the polarity is the same, and says the value is unchanged. `objectui validate` reads the same mirror, so it prints the same refusal. The `object-view` table slot's refusal of the same three keys now names the surviving node key too (write `visibleWhen` / `hidden` / `disabled` on the object-view node itself) instead of sending the author to a retired spelling. Boolean `visible` stays: the spec declares it on actions, navigation entries, tabs, alerts and settings, and the renderer keeps evaluating it.
18
+
-**`@object-ui/react`**: `SchemaRenderer`'s visibility chain has three legs, `visibleWhen` and `visible` (shown when truthy) and `hidden` (hidden when truthy), and the enablement gate has one, `disabled`. A stale retired key that reaches the renderer unvalidated is not consulted and is held out of the component's props, so it neither gates the node nor lands in the DOM.
19
+
-**`@objectstack/sdui-parser`**: the three keys are no longer base props. `validateTree` reports each as an `unknown-prop` diagnostic that names the surviving key, so a TSX tree is told the same thing a JSON document is. `checkRetiredVisibilityDialect` and `RETIRED_VISIBILITY_DIALECTS` are exported for that reading.
20
+
-**`@object-ui/app-shell`**: the command palette reads a navigation entry's `visible` only; the `visibleOn` fallback it carried read a key `NavigationItem` never declared.
21
+
22
+
**BREAKING** (an accept-set narrowing) for any page, view or component document that writes `visibleOn`, `hiddenOn` or `disabledOn` on a node. No shipped example, fixture or skill example in this repository wrote one; the ones in the documentation and the `objectui` skill are rewritten in this change.
23
+
24
+
-**Before.** The three keys parsed, were evaluated beside their canonical twin, and the docs taught both spellings.
25
+
-**Now.** Each is refused by name on `objectui validate`, on the Zod mirror, by the TypeScript compiler and by the SDUI parser, and the renderer does not read it.
26
+
-**Fix.** Rename the key and keep the value: `visibleOn` becomes `visibleWhen`, `hiddenOn` becomes `hidden`, `disabledOn` becomes `disabled`. A form field's `fields[].visibleOn` is a different, view-level slot and is unchanged.
fix(app-shell): the Setup position page shows, read-only, what each permission set the position names grants
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A position grants access only through the permission sets its definition names in `permissionSets`. The position page in Setup → Positions listed those names but showed nothing of what they grant, so an admin had to open every set to see its objects, capabilities and record scopes.
11
+
12
+
-**One preview per named set.** Under the position's definition, beside its holders, the page now shows each named set's permission preview: the object × capability grid with each object's read and write scope, field-level overrides, row-level policies, system permissions and tabs. The previews follow the order the position names the sets. Each is headed by the set's name, which links to the set's own page.
13
+
-**No merge, no editing.** Each preview shows one set as stored. The page does not compute what the sets add up to, and nothing in the previews is editable. A set is changed on its own page.
14
+
-**A set that does not load.** A named set the server does not serve shows one line, `Permission set "NAME" was not found.`, for that set only; the other previews still show. A read the server refuses says that, with the server's sentence, instead of "not found".
15
+
-**No sets.** A position that names no permission sets says so.
16
+
17
+
The permission-set page itself is unchanged: its matrix already shows each object's read and write scope.
18
+
19
+
What to do: nothing. No spec key, stored shape or API changes.
feat(spec,automation,app-shell): a read-only `record_link` screen field shows a record the run created, by its display name, as a link to its record page
8
+
9
+
Clause-②: yes (widening)
10
+
Metadata-protocol: added
11
+
12
+
A screen flow can now end on a result screen that links the records it created, instead of a toast that points at nothing.
-**`@objectstack/spec`**: `ScreenFieldConfigSchema` gains the `type` member `'record_link'` (`SCREEN_FIELD_RECORD_LINK_TYPE`). On it, `reference` (the record's object) and `defaultValue` (a value slot that evaluates to the record's id) are required, and `required`, `options`, `placeholder`, `min` and `max` are refused, each at its own key with the exported refusal text (`SCREEN_FIELD_RECORD_LINK_REFERENCE_REQUIRED`, `SCREEN_FIELD_RECORD_LINK_DEFAULT_VALUE_REQUIRED`, `screenFieldRecordLinkRefusedKey`). The name is `record_link`, not `record`, because `record` is already an object field type (a name-keyed map of sub-objects) and a screen field's `type` uses the same vocabulary. Every other `type` value parses as before.
22
+
-**`@objectstack/service-automation`**: the resume refuses any value sent under a `record_link` field's name, `null` included and visible or not, with `400 INVALID_SCREEN_INPUT` and the field code `invalid_value` ("shows a record and takes no value"). The value is never bound to a flow variable, the refusal keeps the pause, and a resume that leaves the field out still lands. A caller's param that shares the field's name never counts as answering the screen, so a result screen always pauses.
23
+
-**`@object-ui/app-shell`**: the flow screen dialog shows a `record_link` field as the record's display name (the object's `nameField`, through the lookup widget's read-only branch) inside a link to `/apps/APP/OBJECT/record/ID`. An empty id shows "—" with no link, and an object the console cannot route to shows the name without a link. `visibleWhen` works as on any field. The dialog never seeds or posts a value for the field.
fix(plugin-grid): a bulk action's confirm sentence formats its numbers and dates in the viewer's display locale
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A bulk `confirmText` such as `Set the amount to {{ amount | currency:EUR }} on {{ count }} records?` formatted its numbers and dates in `en-US` for every viewer, while the same action's single-record confirm formats them in the viewer's language. `BulkActionDialog` now reads `useDisplayLocale()`, the locale every date, number and currency renderer and the console's action runners read, and `bulkConfirmText` takes it as an optional third argument, handed to `fillTemplateHoles`'s locale channel apart from the template roots. A German viewer reads `1.234.567,50 €`; with no locale handed, the sentence formats in `en-US` as before.
feat(plugin-approvals,rest,client): a record-anchored approval lock read, `{ locked }` for every caller who can read the record
9
+
10
+
Clause-②: yes (widening)
11
+
Metadata-protocol: none
12
+
13
+
A member who could read and edit a record but was not on its approval was offered an edit the server then refused with `409 RECORD_LOCKED`. The lock's only carrier was the request row's `lock_record`, and that row is rightly not served to a non-participant unless the object turns on `approvalsVisibleToReaders`. The lock now has a carrier of its own.
14
+
15
+
-**`@objectstack/spec`.**`IApprovalService` gains the optional member `getRecordLock(object, recordId, context)`, which answers `{ locked: boolean }`, or `null` when the caller cannot read the record. No exported schema or type is added.
16
+
-**`@objectstack/plugin-approvals`.**`ApprovalService.getRecordLock` reads the record as the caller. A record the caller cannot read answers `null`, the same answer as an id that does not exist. A caller with no read on the object is refused for every id, whether it exists or not. `locked` is `true` while a pending request's node has `lockRecord` other than `false`. The lock judgement is now one function, `nodeConfigLocksRecord` (not exported from the package), and the record-lock hook, the request row's `lock_record` and the new read all call it. Request content stays behind `approvalsVisibleToReaders`: the answer carries no request id, node, approvers or `pending` flag.
17
+
-**`@objectstack/rest`.** New route `GET /api/v1/approvals/records/:object/:recordId/lock`. It answers `{ success: true, data: { locked } }`. A `null` answers `404 RECORD_NOT_FOUND` with one body, whether the record is unreadable or missing. A service without the member answers `501`. The route's closed query-parameter set is empty, so any query parameter is refused with a `400`. The route is ledgered as `sdk`.
18
+
-**`@objectstack/client`.**`client.approvals.getRecordLock(object, recordId)` calls that route.
# The repo-root SDUI manifest, regenerated from this tree and compared
4145
+
# with the committed bytes (#26137). The generator builds the registry
4146
+
# with turbo: the 36 tasks of the step above replay, and the 27
4147
+
# @object-ui packages build (3m25s cold, measured on 4 cores, on main
4148
+
# 81bba6c3f), then it enumerates the registry under plain Node (~8 s; ~6 s
4149
+
# for the whole run when nothing moved). It serialises through the
4150
+
# @objectstack/sdui-parser the step above built, so a parser edit moves
4151
+
# this verdict too, which no committed digest does. On merge_group it runs
4152
+
# on the queue's combined tree, the one run that sees two entries' effect
4153
+
# on the manifest together. Only the artefact is compared: the records the
4154
+
# generator rewrites beside it are held by `check-sdui-manifest` in
4155
+
# lint.yml. The Console build below reads the registry through its src/
4156
+
# aliases, so the dist this step builds does not reach it.
4157
+
- name: The committed SDUI manifest is the one this tree generates
4158
+
run: |
4159
+
node scripts/gen-sdui-manifest-node.mjs
4160
+
if ! git diff --quiet -- sdui.manifest.json; then
4161
+
echo "::error file=sdui.manifest.json::sdui.manifest.json is not the manifest this tree generates. Run 'node scripts/gen-sdui-manifest-node.mjs' and commit what it writes."
4162
+
git diff --stat -- sdui.manifest.json
4163
+
git diff -- sdui.manifest.json | head -n 200
4164
+
exit 1
4165
+
fi
4166
+
echo "sdui.manifest.json is byte-identical to this tree's regeneration."
0 commit comments