Skip to content

Commit 138dacf

Browse files
docs(spec): relatedList describe states each value's role, not the record page's tabs (#29799)
domain:spec#3 · os-support-ai · session_01WGufzgh6TyUUCFMAfapZqb <!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBBBNTDAYRKaoWkKn1quxAC4)_ Fixes #28888 Ruling-ref: 6568787922 Clause-②: no Metadata-protocol: none Skills-docs: none — this PR moves no skill text. One skill sentence still names the old chrome for the same placement, `skills/objectstack-data/rules/relationships.md:345-348` ("in the Details tab, no tab of its own", "a side card"); it is out of this card's file surface (spec only) and is reported to the seat for its own card. ## 维护者速读 - 改了什么:`relatedList` 字段的说明文字(`.describe()` 与上方 TSDoc)不再提任何标签页或栏位:`false` = 父记录页不显示该子对象;`true` 或不写 = 记录页上的一个辅助面板;`'primary'` = 记录详情主体里的一张通栏表格。版式只引 ADR-0085,不复述。 - 为什么改:裁决 6568787922(字母 B)。旧文字描述的共用「Related」标签和「Details」标签在现在的记录页上已不存在,AI 作者照着读会把用户引向不存在的标签。 - 风险与回滚:只改文字,不动键、默认值、可接受值;`@objectstack/spec` patch。生成的参考页(field / object / migration 三页的同一行)随之重生成。回滚即还原本 PR。 - 先看:`packages/spec/src/data/field.zod.ts`(describe 与 TSDoc)、`packages/spec/src/data/record-page-describes.test.ts`(原来钉着旧措辞的用例改钉新措辞,并加一条「不提 tab / column / Related / Details / side card」)。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run src/data/record-page-describes.test.ts` 全绿;换回 `origin/main` 的 `field.zod.ts` 再跑,2 条红。 - 需要你做的:无,席位落地。 ## What changes `packages/spec/src/data/field.zod.ts`, the `relatedList` `.describe()` and the TSDoc block above it. Every other clause keeps its meaning: the master_detail + inlineEdit `'primary'` default, `relatedLists` ordering, and the derived-list sort sentences are unchanged. **Before** (the value clauses): > false = suppress; true/absent = shown as a supporting list, a side card in the record's related column (the shared "Related" tab on the tabbed page); 'primary' = part of the record's main content, drawn in the record's body (the details column, after its first field section) as a full-width table (inside the Details tab on the tabbed page, with no tab of its own). Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it a side card. 'primary' places a list and does not order it: the parent object's relatedLists orders the body tables and the side cards (ADR-0085). **After:** > false = the child object is not shown on the parent record page; true/absent = shown as an auxiliary panel on the record page; 'primary' = part of the record's main content, shown as a full-width table in the record's detail body. Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it an auxiliary panel. 'primary' places a list and does not order it: the parent object's relatedLists orders the full-width tables and the auxiliary panels. The page layout is defined by ADR-0085. The TSDoc drops "related column", "shared \"Related\" tab", "details column", "Details tab ... no tab of its own" and "side card" the same way, and adds one line saying the layout is ADR-0085's to define. It keeps the sentence that a field group's `relatedList` can place a primary list among the field sections, since that is a real placement key, not page chrome. Generated: `content/docs/references/data/field.mdx`, `data/object.mdx`, `system/migration.mdx` (the same `relatedList` row; `check:generated --fix`, then `check:generated` clean). ## Why Ruling 6568787922, letter B: the describe states the field's meaning, not the page's chrome, so the next layout card under #26676 does not make it wrong again. ## Verification - Acceptance: `git grep -n 'Related tab\|Details tab' -- packages/spec/src/data/field.zod.ts` is empty at head. - `record-page-describes.test.ts`: the `[#27312]` case that pinned "the details column, after its first field section" and "inside the Details tab on the tabbed page, with no tab of its own" now pins the three new value clauses and the ADR-0085 sentence; a new case refuses `tab(s)`, `column`, `"Related"`, `Details` and `side card` in the describe. Red first: with `origin/main`'s `field.zod.ts`, `Tests 2 failed | 9 passed (11)`; restored, green. - `pnpm exec vitest run src/data/record-page-describes.test.ts src/data/field.test.ts` under os-verify-lock: `Tests 303 passed (303)`, VERDICT command-exit 0. - Derived gates: `dispatch-gates --commands` derived 102 families at `9a801adf`; 95 exit 0, 0 red, 7 NOT MEASURED (exit 3, prerequisite not met: `check-plugin-teardown-shape --self-test` needs its pinned fixture commit in this shallow clone; `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift`, `check:dual-build-cjs-loads`, `check:lean-entry-closure` need a full build). `dispatch-gates --ran`: 102 accounted, 0 unrun. CI covers the 7. ## Acceptance notes Grep of `packages/spec/src/**` for the record page's "Related" / "Details" tab and its columns, other than this describe: - `src/data/record-page-describes.test.ts` (the old pin, changed here). - `src/data/object.zod.ts:1251,1273` and `src/data/object.form.ts:113`: `fieldGroups[i].relatedList` says "details column", "body table" and "side card". That is a different key (a field group's placement), so per the order it is reported, not fixed. - `src/migrations/entries/semantic/18.field-group-related-list-side-card-refused.ts`: migration history text, left as it is. ## Human Minimum None; the seat lands it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzgh6TyUUCFMAfapZqb Co-authored-by: Claude <noreply@anthropic.com>
1 parent be6b15c commit 138dacf

6 files changed

Lines changed: 43 additions & 28 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the `relatedList` field description says what each value does on the parent record page (`false` hidden, `true`/absent an auxiliary panel, `'primary'` a full-width table in the detail body) and points at ADR-0085 for the layout, instead of naming the retired "Related" and "Details" tabs and the page's columns

‎content/docs/references/data/field.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const result = CurrencyConfigSchema.parse(data);
8585
| **inlineTitle** | `string` | optional | Title for the inline master-detail grid |
8686
| **inlineColumns** | `{ name: string; label?: string; type?: Enum<'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'time' \| 'select' \| 'lookup' \| 'file'>; width?: number; … }[]` | optional | Explicit columns for the inline grid (derived from the child object when omitted). Each entry is a strict, name-keyed column (`{ name, label?, type?, … }` — objectui GridColumn); identity-only entries (`{ name }`) hydrate everything else from the child object's fields. Unknown keys and the retired `field` spelling are refused at parse. |
8787
| **inlineAmountField** | `string` | optional | Numeric child field summed for the inline grid total |
88-
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = suppress; true/absent = shown as a supporting list, a side card in the record's related column (the shared "Related" tab on the tabbed page); 'primary' = part of the record's main content, drawn in the record's body (the details column, after its first field section) as a full-width table (inside the Details tab on the tabbed page, with no tab of its own). Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it a side card. 'primary' places a list and does not order it: the parent object's relatedLists orders the body tables and the side cards (ADR-0085). A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
88+
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = the child object is not shown on the parent record page; true/absent = shown as an auxiliary panel on the record page; 'primary' = part of the record's main content, shown as a full-width table in the record's detail body. Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it an auxiliary panel. 'primary' places a list and does not order it: the parent object's relatedLists orders the full-width tables and the auxiliary panels. The page layout is defined by ADR-0085. A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
8989
| **relatedListTitle** | `string` | optional | Title for the detail-page related list |
9090
| **relatedListColumns** | `string[]` | optional | Explicit columns for the detail-page related list, as child field names (e.g. ['name', 'status']); derived from the child object (highlightFields → field walk) when omitted. Strings only — labels, cell types and formatting always derive from the child object's field definitions; column objects are refused at parse. |
9191
| **relatedListFilter** | `any` | optional | Declarative default filter for the detail-page related list: AND-composed with the parent-relationship condition `{ [referenceField]: parentId }` — an authored constraint, never a user-editable suggestion. The related-list tab badge count honors the same composed filter, so counts match the visible rows. Canonical Query-DSL FilterCondition (the same dialect as a query `where`), e.g. `{ status: { $ne: 'deleted' } }` to hide soft-deleted children. |

‎content/docs/references/data/object.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ const result = ApiMethod.parse(data);
254254
| **inlineTitle** | `string` | optional | Title for the inline master-detail grid |
255255
| **inlineColumns** | `{ name: string; label?: string; type?: Enum<'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'time' \| 'select' \| 'lookup' \| 'file'>; width?: number; … }[]` | optional | Explicit columns for the inline grid (derived from the child object when omitted). Each entry is a strict, name-keyed column (`{ name, label?, type?, … }` — objectui GridColumn); identity-only entries (`{ name }`) hydrate everything else from the child object's fields. Unknown keys and the retired `field` spelling are refused at parse. |
256256
| **inlineAmountField** | `string` | optional | Numeric child field summed for the inline grid total |
257-
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = suppress; true/absent = shown as a supporting list, a side card in the record's related column (the shared "Related" tab on the tabbed page); 'primary' = part of the record's main content, drawn in the record's body (the details column, after its first field section) as a full-width table (inside the Details tab on the tabbed page, with no tab of its own). Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it a side card. 'primary' places a list and does not order it: the parent object's relatedLists orders the body tables and the side cards (ADR-0085). A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
257+
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = the child object is not shown on the parent record page; true/absent = shown as an auxiliary panel on the record page; 'primary' = part of the record's main content, shown as a full-width table in the record's detail body. Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it an auxiliary panel. 'primary' places a list and does not order it: the parent object's relatedLists orders the full-width tables and the auxiliary panels. The page layout is defined by ADR-0085. A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
258258
| **relatedListTitle** | `string` | optional | Title for the detail-page related list |
259259
| **relatedListColumns** | `string[]` | optional | Explicit columns for the detail-page related list, as child field names (e.g. ['name', 'status']); derived from the child object (highlightFields → field walk) when omitted. Strings only — labels, cell types and formatting always derive from the child object's field definitions; column objects are refused at parse. |
260260
| **relatedListFilter** | `any` | optional | Declarative default filter for the detail-page related list: AND-composed with the parent-relationship condition `{ [referenceField]: parentId }` — an authored constraint, never a user-editable suggestion. The related-list tab badge count honors the same composed filter, so counts match the visible rows. Canonical Query-DSL FilterCondition (the same dialect as a query `where`), e.g. `{ status: { $ne: 'deleted' } }` to hide soft-deleted children. |
@@ -625,7 +625,7 @@ const result = ApiMethod.parse(data);
625625
| **inlineTitle** | `string` | optional | Title for the inline master-detail grid |
626626
| **inlineColumns** | `{ name: string; label?: string; type?: Enum<'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'time' \| 'select' \| 'lookup' \| 'file'>; width?: number; … }[]` | optional | Explicit columns for the inline grid (derived from the child object when omitted). Each entry is a strict, name-keyed column (`{ name, label?, type?, … }` — objectui GridColumn); identity-only entries (`{ name }`) hydrate everything else from the child object's fields. Unknown keys and the retired `field` spelling are refused at parse. |
627627
| **inlineAmountField** | `string` | optional | Numeric child field summed for the inline grid total |
628-
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = suppress; true/absent = shown as a supporting list, a side card in the record's related column (the shared "Related" tab on the tabbed page); 'primary' = part of the record's main content, drawn in the record's body (the details column, after its first field section) as a full-width table (inside the Details tab on the tabbed page, with no tab of its own). Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it a side card. 'primary' places a list and does not order it: the parent object's relatedLists orders the body tables and the side cards (ADR-0085). A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
628+
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = the child object is not shown on the parent record page; true/absent = shown as an auxiliary panel on the record page; 'primary' = part of the record's main content, shown as a full-width table in the record's detail body. Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it an auxiliary panel. 'primary' places a list and does not order it: the parent object's relatedLists orders the full-width tables and the auxiliary panels. The page layout is defined by ADR-0085. A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
629629
| **relatedListTitle** | `string` | optional | Title for the detail-page related list |
630630
| **relatedListColumns** | `string[]` | optional | Explicit columns for the detail-page related list, as child field names (e.g. ['name', 'status']); derived from the child object (highlightFields → field walk) when omitted. Strings only — labels, cell types and formatting always derive from the child object's field definitions; column objects are refused at parse. |
631631
| **relatedListFilter** | `any` | optional | Declarative default filter for the detail-page related list: AND-composed with the parent-relationship condition `{ [referenceField]: parentId }` — an authored constraint, never a user-editable suggestion. The related-list tab badge count honors the same composed filter, so counts match the visible rows. Canonical Query-DSL FilterCondition (the same dialect as a query `where`), e.g. `{ status: { $ne: 'deleted' } }` to hide soft-deleted children. |

‎content/docs/references/system/migration.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Add a new field to an existing object
8888
| **inlineTitle** | `string` | optional | Title for the inline master-detail grid |
8989
| **inlineColumns** | `{ name: string; label?: string; type?: Enum<'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'time' \| 'select' \| 'lookup' \| 'file'>; width?: number; … }[]` | optional | Explicit columns for the inline grid (derived from the child object when omitted). Each entry is a strict, name-keyed column (`{ name, label?, type?, … }` — objectui GridColumn); identity-only entries (`{ name }`) hydrate everything else from the child object's fields. Unknown keys and the retired `field` spelling are refused at parse. |
9090
| **inlineAmountField** | `string` | optional | Numeric child field summed for the inline grid total |
91-
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = suppress; true/absent = shown as a supporting list, a side card in the record's related column (the shared "Related" tab on the tabbed page); 'primary' = part of the record's main content, drawn in the record's body (the details column, after its first field section) as a full-width table (inside the Details tab on the tabbed page, with no tab of its own). Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it a side card. 'primary' places a list and does not order it: the parent object's relatedLists orders the body tables and the side cards (ADR-0085). A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
91+
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = the child object is not shown on the parent record page; true/absent = shown as an auxiliary panel on the record page; 'primary' = part of the record's main content, shown as a full-width table in the record's detail body. Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it an auxiliary panel. 'primary' places a list and does not order it: the parent object's relatedLists orders the full-width tables and the auxiliary panels. The page layout is defined by ADR-0085. A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
9292
| **relatedListTitle** | `string` | optional | Title for the detail-page related list |
9393
| **relatedListColumns** | `string[]` | optional | Explicit columns for the detail-page related list, as child field names (e.g. ['name', 'status']); derived from the child object (highlightFields → field walk) when omitted. Strings only — labels, cell types and formatting always derive from the child object's field definitions; column objects are refused at parse. |
9494
| **relatedListFilter** | `any` | optional | Declarative default filter for the detail-page related list: AND-composed with the parent-relationship condition `{ [referenceField]: parentId }` — an authored constraint, never a user-editable suggestion. The related-list tab badge count honors the same composed filter, so counts match the visible rows. Canonical Query-DSL FilterCondition (the same dialect as a query `where`), e.g. `{ status: { $ne: 'deleted' } }` to hide soft-deleted children. |
@@ -522,7 +522,7 @@ Add a new field to an existing object
522522
| **inlineTitle** | `string` | optional | Title for the inline master-detail grid |
523523
| **inlineColumns** | `{ name: string; label?: string; type?: Enum<'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'time' \| 'select' \| 'lookup' \| 'file'>; width?: number; … }[]` | optional | Explicit columns for the inline grid (derived from the child object when omitted). Each entry is a strict, name-keyed column (`{ name, label?, type?, … }` — objectui GridColumn); identity-only entries (`{ name }`) hydrate everything else from the child object's fields. Unknown keys and the retired `field` spelling are refused at parse. |
524524
| **inlineAmountField** | `string` | optional | Numeric child field summed for the inline grid total |
525-
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = suppress; true/absent = shown as a supporting list, a side card in the record's related column (the shared "Related" tab on the tabbed page); 'primary' = part of the record's main content, drawn in the record's body (the details column, after its first field section) as a full-width table (inside the Details tab on the tabbed page, with no tab of its own). Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it a side card. 'primary' places a list and does not order it: the parent object's relatedLists orders the body tables and the side cards (ADR-0085). A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
525+
| **relatedList** | `boolean \| 'primary'` | optional | Show this child collection as a related list on the parent's detail page (read-side mirror of inlineEdit). false = the child object is not shown on the parent record page; true/absent = shown as an auxiliary panel on the record page; 'primary' = part of the record's main content, shown as a full-width table in the record's detail body. Absent on a master_detail field that sets inlineEdit, the list is 'primary' by default; write true to keep it an auxiliary panel. 'primary' places a list and does not order it: the parent object's relatedLists orders the full-width tables and the auxiliary panels. The page layout is defined by ADR-0085. A derived related list (relatedList: 'primary') inherits its row order from the child object's DEFAULT list view sort — the isDefault view, or the first declared list item when none is marked default; wire spelling is sort=`<field>` / sort=-`<field>`, never $orderby. A child object with no list-view sort emits no ordering parameter, and rows fall back to record-id order. |
526526
| **relatedListTitle** | `string` | optional | Title for the detail-page related list |
527527
| **relatedListColumns** | `string[]` | optional | Explicit columns for the detail-page related list, as child field names (e.g. ['name', 'status']); derived from the child object (highlightFields → field walk) when omitted. Strings only — labels, cell types and formatting always derive from the child object's field definitions; column objects are refused at parse. |
528528
| **relatedListFilter** | `any` | optional | Declarative default filter for the detail-page related list: AND-composed with the parent-relationship condition `{ [referenceField]: parentId }` — an authored constraint, never a user-editable suggestion. The related-list tab badge count honors the same composed filter, so counts match the visible rows. Canonical Query-DSL FilterCondition (the same dialect as a query `where`), e.g. `{ status: { $ne: 'deleted' } }` to hide soft-deleted children. |

0 commit comments

Comments
 (0)