Skip to content

Commit 4cc3ff9

Browse files
feat(spec): record:related_list declares its parent record with parentDataSource, beside the child-row dataSource (#23134) (#23205)
Fixes #23134 Clause-②: yes (widening) `record:related_list` declares its PARENT record with a binding key of its own, `parentDataSource`, beside the child-row `dataSource`. This is the spec half of ruling B on objectstack-ai/objectui#12296 (ruling comment 6137226306). The objectui reader is the related-list leg of objectstack-ai/objectui#12294 and is not in this PR. ## What changed - `RecordRelatedListProps` (the `ComponentPropsMap['record:related_list']` row, `packages/spec/src/ui/component.zod.ts`) gains `parentDataSource: ElementDataSourceSchema.optional()`. That is the element data-source object binding `{ object, view?, filter?, sort?, limit? }`, referenced from `page.zod.ts` and not copied. - The describe states the three facts the card names. First, this key is the parent record, read by the single-record rule: the first row by `sort`; when no row answers, a loud absence, never an empty list. Second, when the key is absent, the record page's record context remains the parent. Third, `dataSource` on this block keeps its published child-row meaning and is never read as the parent. The describe opens with `[PLANNED — not yet rendered]`. - `PageComponentSchema` and `ElementDataSourceSchema` are untouched. `page.zod.ts` is not in the diff, and the node-level `dataSource` is byte-for-byte unchanged. - Generated: `authorable-surface/ui.json` gains one row (`ui/RecordRelatedListProps:parentDataSource`). `content/docs/references/ui/component.mdx` gains the key's row and its nested shape. `authorable-surface.base.json` is not touched. - Liveness: one PLANNED RECORD sentence is appended to the `liveness/page.json` `_note`, with no row (see measurement 5). - A `minor` changeset for `@objectstack/spec`. - No `os validate` finding is added, as the ruling directs. ## Measurements (dispatch zone 2) All objectui reads below were taken at objectui `main` `f0532d8a`, fetched 2026-10-13. Spec reads were taken at this repo's base `e540e7ffb` and re-checked after merging `origin/main` `d4aaeaf0e`. 1. **Where the key lives: the props door, as the PM assumed. The card's `page.zod.ts` path does not hold.** The node-level `dataSource` is `PageComponentSchema.dataSource` (`page.zod.ts:696`), one shape for every component type, so a node key there could be authored on all of them. The per-type door is `ComponentPropsMap`, whose `record:related_list` row is `RecordRelatedListProps` in `component.zod.ts` (`:10602` at base). objectui's `SchemaRenderer` hoists `properties` onto the node, so a props key reaches the renderer on `schema`. The key therefore lives on `RecordRelatedListProps`. objectui reaches the child `dataSource` through `RecordRelatedListRenderer`, which is `elementDataSourceBlock(...)` wrapping `ElementDataSourceGate` with the `RECORD_RELATED_LIST_DATA_SOURCE` mapping (`plugin-detail/src/renderers/record-related-list.tsx:689-705`). The gate maps the binding's `object` onto `objectName`. 2. **The name: `parentDataSource`.** Spellings read: - the three sibling record blocks read their one binding as node-level `dataSource`: `recordBindingBlock` reads `isElementDataSourceConfig(schema.dataSource)` (`plugin-detail/src/renderers/recordBinding.tsx:124`, `:147`), and wraps `record-details.tsx:1016`, `record-highlights.tsx:199` and `record-quick-actions.tsx:460`; - objectui's strict face declares that binding as `dataSource: RECORD_BLOCK_DATA_SOURCE` on those three arms, where `RECORD_BLOCK_DATA_SOURCE` is `stripImportedDefaults(SpecElementDataSourceSchema)` (`types/src/zod/public-blocks.zod.ts:663`, `:688`, `:711`, `:963`); - the related list's own parent today comes from `useRecordContext()` (`record-related-list.tsx:196`, `:257-261`); - spec: `PageComponentSchema.dataSource` is the only `dataSource` key. The one other block with a parent half, `record:line_items`, spells that half `parentObject` / `parentId` (`component.zod.ts:2615-2619` at base). The proposed name takes the `dataSource` stem from the binding family and the `parent` prefix from `record:line_items`. Rejected alternatives: - `parentRecord`: reads as a record value (a row or an id), not a binding, and leaves the `dataSource` family. - `recordDataSource`: both bindings on this block resolve to records, so `record` does not say which one. - `parentSource`: `source` already means other things in the spec, such as the page variable writer `source`, `optionsSource`, and the `source` alias of `object` on the data-source shape itself. - `parentBinding`: names the concept, not the family's spelling. No spec key ends in `Binding`. - `parent`: too terse, and it collides in reading with the tree and gantt `parentField`. - `parentObject` + `parentId` (the `record:line_items` shape): a different shape (a name plus an id in metadata), not the `dataSource` shape the ruling fixes. The PM and the contract reviewer judge the name. 3. **The shape: reused by reference, with no new field.** The key's inner def is `ElementDataSourceSchema`'s own def. The new test pins `_zod.def` identity against `ElementDataSourceSchema` and against the object arm of `PageComponentSchema.dataSource`. The single-record rule is stated on that shape without a new field: the first row by `sort`, with `limit: 1` as the author's way to say one row is meant. The `api` arm of `dataSource` is not admitted, because the card fixes the shape as the object binding's (object, view, filter, sort, limit) and the siblings' strict face takes that arm alone. No fork from the shape side. 4. **The fork reading: no renderer reads `dataSource` on this block as the parent.** I searched every objectui source file that names `record:related_list` at `f0532d8a`: - the renderer: `dataSource` is the child binding through the gate, and the parent is `useRecordContext()`; - the tab-count probe in `components/src/renderers/layout/containers.tsx:614-626`: it reads the parent from `ctx.data.id`; - the CLI's `record-block-binding-hint.ts`: it excludes `record:related_list` from the one-record set and gives the reason "its binding is the binding of its CHILD rows"; - hotcrm at `0f84c9c`: authors related lists on record pages with no `dataSource`. One objectstack reader needs naming, so it is stated here rather than left silent. `os validate`'s `page-field-unknown` rule checks the related list's parent-side `relationshipValueField` against the component's walked object (`packages/lint/src/validate-page-field-bindings.ts:617`). For a node with a `dataSource`, that object is `dataSource.object` (`packages/lint/src/page-walk.ts:136-137`). The rule's own comment says the parent is the page object, so this is a defect against its stated reading, not a parent meaning given to `dataSource`. Repro (scratch probe against the lint source, page object `account`, child `contact`): - child via `properties.objectName`: one finding, `columns[0]` checked against `contact`, and nothing on `relationshipValueField` (correct); - child via `dataSource: { object: 'contact' }` (the published child shape): no `columns` finding, and a false `relationshipValueField` "not a field on object contact". I judged this not to be the ruling's fork and continued. It is reported as a finding for the seat to file (Acceptance notes below), and the PM is asked to confirm that judgement. 5. **Liveness: no ledger row. A `_note` record and the describe marker carry it.** The ledger walk never reaches a component-level key: `page/regions` is a recorded undrilled container, as the existing `page.json` `_note` already says for the `ElementApiDataSourceSchema` PLANNED record. So the key is recorded the way that precedent is: the `[PLANNED — not yet rendered]` describe marker (which `check-liveness.mts` `MARKER_RE.planned` reads), a PLANNED RECORD sentence in the `liveness/page.json` `_note` naming the carrier, and the authorable-surface row. `authorHint` has no row to attach to. ## Pins New file: `packages/spec/src/ui/component-record-related-list-parent-23134.test.ts`, 17 cases. - **A:** a full binding parses and keeps what the shape keeps. A bare `{ object }` parses. There is no default. The key's def is the data-source def, identical to the object arm of the node's `dataSource`. - **B:** what the shape refuses is refused at `parentDataSource`, judged by codes and paths: no `object`, `objectName` (unrecognized), a record-form `filter`, the `api` arm, and a non-object value. - **C:** no other `ComponentPropsMap` row declares the key. Every other row refuses it by name (`unrecognized_keys`), except the two retired types, which refuse every bag (derived, not listed). The related list itself does not name it unknown. - **D (CONTROL, the child binding):** the node-level child `dataSource` parses unchanged with and without a parent binding beside it, and the parent binding survives in `properties`. - **E:** the describe opens with the planned marker. ## Verification - Ablation (one-time, through `scripts/ablation-replace.mjs`, which restores itself, plus an outer EXIT/INT/TERM trap). The key was renamed `parentDataSource` to `zzAblatedParent` on disk: anchor x1 to x0, blob `5fc72026` to `c9701e67`. The new file went red: **12 failed / 5 passed of 17**. The 5 that stayed green are the controls that should: the base document, the no-default case, the population check, the other rows refusing the key, and the child binding parsing. The file was restored with blob equal to HEAD `5fc72026` and `git diff HEAD` empty. No build or dist leg was needed: the test imports `./component.zod` from `src`. - Type-level reverse check against the REBUILT `dist/ui/index.d.ts` (a scratch tsc project outside the repo). The green leg exits 0: the key is accepted, and both `@ts-expect-error` lines are consumed, one for `object: 1` and one for the `api` arm. The mutated leg exits 2 with two errors: `parentDataSourceX` gets TS2561, and the `api` arm without its expect-error gets TS2353 on `provider`. - `os validate` door, probed against the lint source with the rebuilt spec: - `parentDataSource` on `record:details` is reported as `component-props-unknown-key` (warning); - a valid binding on `record:related_list` yields no finding; - a malformed one yields `component-props-invalid` / `component-props-unknown-key` at its own path. - Final head `d4b3e7264` (after merging `origin/main` `d4aaeaf0e` through `scripts/pm/os-regen-merge.sh`, then rebuilding spec), all through the shared verify lock: `pnpm --filter @objectstack/spec typecheck` exit 0 (`check:test-typecheck: OK`, no new debt), and `pnpm --filter @objectstack/spec test` **662 files passed, 20125 tests passed, 1 todo**. `pnpm --filter @objectstack/spec test:repo` passed at the pre-merge head `0e5ae9203` (57 files, 997 tests). It was not re-run after the merge: the merge brought spec changes disjoint from this diff, and CI runs it (declared narrowing). `check:generated` reports all 14 artifacts up to date on the merged tree. - ESLint, narrowed and proven, at `d4b3e7264`. The changed files in eslint's `**/*.{ts,…}` globs are the two `.ts` files; the other four (`.md`, `.mdx`, `.json`) match no lint glob. `--format json` reports 2 files, 0 errors, 0 warnings. Invariance: `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project`), so this diff cannot move a verdict on an untouched file. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on `d4b3e7264` derives 110 commands. All 110 were run with exit codes captured before any pipe. **109 exit 0.** Six first exited 3 (PREREQUISITE NOT MET); after a targeted `turbo run build` of lint, formula, client-react and objectql (35/35 cached) they re-ran at 0. **`pnpm check:platform-checklist` exits 1, and it is not this diff:** its 8 problems are absent symbols in `packages/metadata-protocol/src/protocol.ts` and `packages/services/service-storage/src/attachment-access-hooks.ts`, and the gate's inputs are byte-identical to `origin/main` `d4aaeaf0e` (`git diff origin/main HEAD -- docs/qa packages/metadata-protocol packages/services scripts` is empty; the symbol `anonymousFormIntakeOrgScopeRefusal` has zero hits on `origin/main`). That is a main-red. `--ran` reconcile: 110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN, all 110 coded. ## Acceptance notes - **Finding, class (a), for the seat to file:** the `page-field-unknown` rule resolves the related list's parent-side `relationshipValueField` against `dataSource.object` when the child is bound through `dataSource`. It also skips the child-side `columns` / `sort` / `filter` / `relationshipField`, because it reads only `properties.objectName`. Reach: `os validate` gives a false warning on the published child-binding shape, and a missed check. Seam: `spec:PageComponentSchema.dataSource (record:related_list child binding) → runtime:packages/lint/src/validate-page-field-bindings.ts:617 (checkRefs(split.parent, objectName)) via packages/lint/src/page-walk.ts:136-137`. When that is fixed, the parent-side refs have a declared object to resolve against: `parentDataSource.object` when present, otherwise the page object. That is a resolution fix to an existing rule, not a new finding. Dedupe words: `page-field-unknown related_list dataSource relationshipValueField`, `relatedListFieldRefs parent object`, `walkPageComponents dataSource object related list`. - For the objectui leg (carrier: objectstack-ai/objectui#12294, the related-list leg): the `page:tabs` count probe reads the parent from `ctx.data.id` (`containers.tsx:614-626`). A related list bound by `parentDataSource` on an `app` page gets no auto-derived tab count unless that leg feeds it. Noted, not filed. - `RecordRelatedListProps` still requires `objectName`, even when the child `dataSource` supplies it (the objectui renderer type says so in its own docblock). That predates this card and is unchanged here. --- _Generated by [Claude Code](https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 920bc12 commit 4cc3ff9

6 files changed

Lines changed: 316 additions & 2 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): `record:related_list` declares its parent record with a binding of its own, `parentDataSource`, beside the child-row `dataSource` (#23134)
6+
7+
Clause-②: yes (widening)
8+
9+
- **What is new.** `ComponentPropsMap['record:related_list']` (`RecordRelatedListProps`) accepts `parentDataSource`: the PARENT record the list hangs off, bound by a query. Its shape is the element data-source object binding `{ object, view?, filter?, sort?, limit? }` (`ElementDataSourceSchema`, referenced, not copied), the same binding `dataSource` takes on every page component. A binding the data-source shape refuses (no `object`, an unknown key, the MongoDB-style `filter` record, the `api` arm) is refused here too, at `parentDataSource`.
10+
- **The single-record rule.** The parent is the FIRST row of the bound set by the binding's `sort`; declare `limit: 1` to say one row is meant. When no row answers, the block renders a loud absence, never an empty list. `relationshipValueField` is read on that row. This is the rule `record:details`, `record:highlights` and `record:quick_actions` already take for their own `dataSource`.
11+
- **The fallback.** Without `parentDataSource`, the record page's record context remains the parent, as before. Every related list that parsed before parses unchanged.
12+
- **`dataSource` keeps its meaning.** The node-level `dataSource` on `record:related_list` is still the CHILD rows the list draws (it fills `objectName`, `columns`, `filter`, `sort` and `limit`) and is never read as the parent.
13+
- **Planned, not yet rendered.** The key's describe opens with `[PLANNED — not yet rendered]`: no objectui renderer reads it yet. objectui reads it once a release carrying this key is installable there; until then a related list on an `app` page still finds no parent.
14+
- **Refused elsewhere.** Every other `ComponentPropsMap` row refuses `parentDataSource` as an unknown key, and `os validate` reports it as `component-props-unknown-key`.
15+
16+
```ts
17+
{
18+
type: 'record:related_list',
19+
properties: {
20+
objectName: 'contact',
21+
relationshipField: 'account_id',
22+
parentDataSource: {
23+
object: 'account',
24+
filter: [{ field: 'is_primary', operator: 'equals', value: true }],
25+
limit: 1,
26+
},
27+
},
28+
}
29+
```

‎content/docs/references/ui/component.mdx‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,6 +1938,7 @@ Sort field and direction pair
19381938
| **objectName** | `string` | ✅ | Related object name (e.g., "task", "opportunity") |
19391939
| **relationshipField** | `string` | ✅ | Field on related object that points to this record (e.g., "account_id") |
19401940
| **relationshipValueField** | `string` | optional (default: `"id"`) | Parent-record field whose value relationshipField stores (default 'id'; e.g. 'name' for name-keyed junctions). |
1941+
| **parentDataSource** | `{ object: string; view?: string; filter?: object[]; sort?: object[]; … }` | optional | [PLANNED — not yet rendered] The PARENT record this related list hangs off, bound by a query of its own: an object binding `{ object, view?, filter?, sort?, limit? }`, the shape `dataSource` takes. Read by the single-record rule: the FIRST row of the bound set by its `sort` is the parent (declare `limit: 1` to say one row is meant), and when no row answers, the block renders a loud absence, never an empty list, which would read as "this record has no related records". `relationshipValueField` is read on that row. When this key is absent, the record page's record context remains the parent. The node-level `dataSource` on this block keeps its published meaning, the CHILD rows the list draws (it fills `objectName`, `columns`, `filter`, `sort` and `limit`), and is never read as the parent. |
19411942
| **columns** | `string[] \| { field: string; label?: string \| Record<string, string>; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | optional | Fields to display in the related list — either plain field-name strings, or the same per-column entries a saved list view declares (`ListColumn`: `field`, plus `label`, `width`, `align`, `hidden`, `sortable`, `summary`, …). A view-supplied list may arrive in the `ListColumn` spelling: objectui composes a saved view's `columns` onto this block verbatim, and this key declares the SAME union as `listViews[].columns`. One spelling per list — the two arms are exclusive, so an array mixing strings and column objects is refused. Optional: when omitted, columns derive from the related object's highlightFields / default list columns (a related list is just another surface that lists that object). Override chain: child highlightFields → field-level relatedListColumns (field-name strings only) → this inline list. |
19421943
| **sort** | `string \| { field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Sort order for related records |
19431944
| **limit** | `integer` | optional (default: `5`) | Number of records to display initially |
@@ -1951,6 +1952,16 @@ Sort field and direction pair
19511952
| **requiredPermissions** | `string[]` | optional | [ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open. |
19521953
| **aria** | `{ ariaLabel?: string \| Record<string, string>; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes |
19531954

1955+
### Nested Shape: `RecordRelatedListProps.parentDataSource`
1956+
1957+
| Property | Type | Required | Description |
1958+
| :--- | :--- | :--- | :--- |
1959+
| **object** | `string` | ✅ | Object to query |
1960+
| **view** | `string` | optional | Named view to apply |
1961+
| **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Additional filter criteria — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in ComponentPropsMap shares; AND-combined with the filter of the named view. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array` |
1962+
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Sort order |
1963+
| **limit** | `integer` | optional | Max records to display |
1964+
19541965
### Nested Shape: `RecordRelatedListProps.columns[number]`
19551966

19561967
| Property | Type | Required | Description |

‎packages/spec/authorable-surface/ui.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,7 @@
12311231
"ui/RecordRelatedListProps:filter",
12321232
"ui/RecordRelatedListProps:limit",
12331233
"ui/RecordRelatedListProps:objectName",
1234+
"ui/RecordRelatedListProps:parentDataSource",
12341235
"ui/RecordRelatedListProps:redactFields",
12351236
"ui/RecordRelatedListProps:relationshipField",
12361237
"ui/RecordRelatedListProps:relationshipValueField",

‎packages/spec/liveness/page.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"type": "page",
3-
"_note": "PageSchema (UI). Renderers live in objectui; objectui paths are cited with the `objectui:` realm marker (in `evidence` or `note` prose) and counted, not resolved against this repo (#5623). Seeded from the Studio page-design dogfood (framework#2254/#2261/#2265). Containers (variables/regions/interfaceConfig/slots/aria) classified at top level — one drill level, no divergent sub-statuses. The removed roadmap page types (record_review/blank) and their config fields (recordReview/blankLayout) were hard-removed — no longer authorable. RETIREMENT RECORD (#11027, 2026-08-24): `regions[].components[].responsive` is a retiredKey tombstone (ADR-0049 D2) — measured dead across both repos (zero callers of either objectui consumer implementation); the shared `ResponsiveConfigSchema` left with it. The walk drills one level only, so no component-level key has a row here (`page/regions` stays RECORDED in undrilled-containers.baseline.json — #4956's page-side instance, still open for the other component keys); the checkable record of THIS retirement is the authorable-surface `ui/PageComponent:responsive [RETIRED]` row, RETIRED_KEYS_BY_MAJOR[18], and the `page-component-responsive-removed` conversion. PLANNED RECORD (#23016, 2026-10-12, maintainer ruling in the card body): `regions[].components[].dataSource` gained a second shape, the API binding `ElementApiDataSourceSchema` (`{ provider: 'api', read: { url, method? }, path? }`, `read.url` a same-origin API path refused at parse otherwise, no credential key) — PLANNED, not live: measured at objectui @7578323a544e, the page-element data-source hook admits only a binding with a string `object` (objectui: isElementDataSourceConfig in packages/core/src/data-scope/element-data-source.ts), so no block reads an API binding yet. Carrier: objectstack-ai/objectui#12197 (the generic stat / usage-bar / checklist blocks, re-scoped by the same ruling). No row carries it because the walk never reaches a component-level key (`page/regions` and `page/slots` are recorded undrilled containers) and `childShape()` does not descend the `z.union` the key now is; the checkable record is the authorable-surface `ui/ElementApiDataSource:*` rows and the schema's own `[PLANNED — not yet rendered]` describe. When objectui#12197 lands, that describe marker leaves and this sentence is rewritten.",
3+
"_note": "PageSchema (UI). Renderers live in objectui; objectui paths are cited with the `objectui:` realm marker (in `evidence` or `note` prose) and counted, not resolved against this repo (#5623). Seeded from the Studio page-design dogfood (framework#2254/#2261/#2265). Containers (variables/regions/interfaceConfig/slots/aria) classified at top level — one drill level, no divergent sub-statuses. The removed roadmap page types (record_review/blank) and their config fields (recordReview/blankLayout) were hard-removed — no longer authorable. RETIREMENT RECORD (#11027, 2026-08-24): `regions[].components[].responsive` is a retiredKey tombstone (ADR-0049 D2) — measured dead across both repos (zero callers of either objectui consumer implementation); the shared `ResponsiveConfigSchema` left with it. The walk drills one level only, so no component-level key has a row here (`page/regions` stays RECORDED in undrilled-containers.baseline.json — #4956's page-side instance, still open for the other component keys); the checkable record of THIS retirement is the authorable-surface `ui/PageComponent:responsive [RETIRED]` row, RETIRED_KEYS_BY_MAJOR[18], and the `page-component-responsive-removed` conversion. PLANNED RECORD (#23016, 2026-10-12, maintainer ruling in the card body): `regions[].components[].dataSource` gained a second shape, the API binding `ElementApiDataSourceSchema` (`{ provider: 'api', read: { url, method? }, path? }`, `read.url` a same-origin API path refused at parse otherwise, no credential key) — PLANNED, not live: measured at objectui @7578323a544e, the page-element data-source hook admits only a binding with a string `object` (objectui: isElementDataSourceConfig in packages/core/src/data-scope/element-data-source.ts), so no block reads an API binding yet. Carrier: objectstack-ai/objectui#12197 (the generic stat / usage-bar / checklist blocks, re-scoped by the same ruling). No row carries it because the walk never reaches a component-level key (`page/regions` and `page/slots` are recorded undrilled containers) and `childShape()` does not descend the `z.union` the key now is; the checkable record is the authorable-surface `ui/ElementApiDataSource:*` rows and the schema's own `[PLANNED — not yet rendered]` describe. When objectui#12197 lands, that describe marker leaves and this sentence is rewritten. PLANNED RECORD (#23134, 2026-10-13, ruling B on objectstack-ai/objectui#12296, ruling comment 6137226306): `regions[].components[].properties.parentDataSource` on `record:related_list` (`RecordRelatedListProps` in packages/spec/src/ui/component.zod.ts) is the block's PARENT-record binding, the element object binding `ElementDataSourceSchema` by reference, read by the single-record rule (the first row by `sort`; no row is a loud absence, never an empty list); the node-level `dataSource` on the block keeps its child-row meaning (#6953). PLANNED, not live: measured at objectui @f0532d8a, the block reads its parent from the record context only (objectui: packages/plugin-detail/src/renderers/record-related-list.tsx, whose RecordRelatedListBody reads useRecordContext()) and nothing reads the key. Carrier: the related-list leg of objectstack-ai/objectui#12294, dispatched once a spec release carrying the key is installable. No row carries it for the reason above (component-level keys sit under the recorded undrilled `page/regions` container); the checkable record is the authorable-surface `ui/RecordRelatedListProps:parentDataSource` row and the key's own `[PLANNED — not yet rendered]` describe. When that leg lands, the marker leaves and this sentence is rewritten.",
44
"props": {
55
"source": {
66
"status": "live",

0 commit comments

Comments
 (0)