Skip to content

Commit ad3a640

Browse files
docs(spec): related-list descriptions say which unknown names are refused; primary list and trackHistory describe the v3 record page (#27352)
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP <!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBJt5Dq32Sbwm4wPh1kLM6ME)_ Fixes #27311 Fixes #27312 Clause-②: no Metadata-protocol: none **Seat ruling on #27311: option A (by design).** The boundary in `packages/lint/src/validate-related-list-refs.ts`'s header is deliberate: under a package closure a child outside it may be a downstream package's object that points in, which the closure cannot see. Refusing there would refuse legitimate stacks, and option B needs a new declaration surface no card asks for. So the contract text changes to say what is enforced. No change to the lint rule, `defineStack` or the accept set. **Landing order.** #27312's text describes the record page v3 that PR #27310 builds. #27310 has merged (`eef83734`), and this branch carries it through a merge of `main` at `cf218eed1`. ## 维护者速读 - 改了什么:只改说明文字。`relatedLists` 和 field group 的 `relatedList` 说清楚哪些未知名称会被拒;`relatedList: 'primary'` 和 `enable.trackHistory` 改成描述 v3 记录页(主表在详情列里,字段变更进时间线,不再有独立页签)。 - 为什么改:旧文字承诺了运行时并不做的拒绝(包内发布会存下未知子对象名),也还在描述 v2 页面的「独立页签」和「History 页签」。 - 风险与回滚:零运行时风险,没有键、类型、默认值或导出变化;回滚即还原文字。按契约复核补了两处精确度:平台前缀名(sys_ / cloud_ / ai_)在 os validate / os build 不判、留给运行时;trackHistory 说明自建页面上 record:history 和 record:activity 两种块都会显示字段变更,且都不读这个开关。 - 先看:`packages/spec/src/data/object.zod.ts`(relatedLists 的 describe 与 docblock),`packages/spec/src/data/field.zod.ts`(relatedList 的 describe),`content/docs/ui/pages.mdx`(合成记录页一段)。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run src/data/record-page-describes.test.ts`,应见 10 个测试全绿。 - 需要你做的:无,#27310 已合并,席位落地。 ## What changes Description text only. No key, type, default, accepted value or export moves; `check:api-surface` and `check:authorable-surface` are green with no artifact change. ### #27311: `ObjectSchema.relatedLists` and `fieldGroups[i].relatedList` Measured on `origin/main` 6d7634a: `defineStack` (`collectRelatedListNameErrors`, `stack.zod.ts`) refuses a name whose child is declared with no inbound FK, and a name whose child is nowhere (sibling-package and platform objects stay unjudged). The runtime door runs `validateRelatedListRefs`, which refuses a stack child with no inbound FK on every write, and a child missing from the collection only under `wholeCollection` (`reference-integrity-suite.ts`: a gated write with no `packageScope`). A write into a package stores the entry. Before (`relatedLists` describe): > An entry that matches no inbound lookup / master_detail field of this object is refused. After: > An entry whose child is in the stack but has no inbound lookup / master_detail field to this object is refused everywhere. An entry whose child exists nowhere is refused by os validate / os build (which leave a sys_ / cloud_ / ai_ name to the runtime) and by a metadata write that names no package; a write into a package stores it, since a child outside the package may be a downstream package's object that points here, and the record page places nothing for it. Before (`fieldGroups[i].relatedList` describe): > A group with relatedList holds no fields: a field that names it as its group is refused, and so is a name that matches no inbound lookup / master_detail field. After: > A group with relatedList holds no fields: a field that names it as its group is refused. The name is judged as a relatedLists entry is: refused when its child is in the stack with no inbound lookup / master_detail field, or exists nowhere (by os validate / os build, which leave a sys_ / cloud_ / ai_ name to the runtime, and by a metadata write that names no package); a write into a package stores it, and the record page places nothing for it. Both docblocks say the same, and the `relatedLists` one points at the lint rule's header for the reasoning. The platform-prefix clause (r1) is measured at `stack.zod.ts` `collectRelatedListNameErrors`: `if (!fieldsByObject.has(child) && (artifactScope.has(child) || isPlatformObjectName(child))) return;`, where `isPlatformObjectName` is `hasPlatformObjectPrefix` (`PLATFORM_OBJECT_PREFIXES = ['sys_', 'cloud_', 'ai_']`, `platform-object-names.ts`). The runtime door under `wholeCollection` judges such a name against the registry (`validate-related-list-refs.ts`: `if (!fieldsByObject.has(child) && !options?.wholeCollection) return;`). ### #27312: `relatedList: 'primary'` and `enable.trackHistory` Measured on #27310's head `349cd5625c`: - `ui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts` `buildDefaultTabs`: a list with `isBody ?? isPrimary` is a `record:related_list` inside the **Details** tab, placed by `bodyTablePlacement` right after the FIRST field section; every other list is a side card in the one **Related** tab. No per-list tab is emitted. The column tiers (`recordPageColumns.ts`) draw the same Details content in the details column. - Field changes: `RecordDetailView.tsx` reads `sys_audit_log` only when `enable.trackHistory === true` (`historyEnabled`), and only for the synthesized page; `auditEntriesToFeedItems` turns them into `field_change` items of the one timeline stream (`recordTimelineStream.ts`, filters All / Activity / Discussion / Changes, `streamChanges: 'Changes'` in `en.ts`). No History tab is emitted. On an assigned page `RecordFieldChangesContext` is `null`, and a `record:history` block self-fetches the record's `sys_activity` rows without reading `trackHistory`. Before (`relatedList` describe, the arms that move): > true/absent = shown as a supporting list, a side card beside the record (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 the field sections) as a full-width table (its own tab on the tabbed page). After: > 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). Before (`enable.trackHistory` describe): > Show the record History tab (audit-trail UI). Pair with per-field trackHistory to pick which field diffs are summarized; audit capture itself is always on for compliance After: > Show the record's field changes (audit-trail UI): on the default record page they join the record's one timeline stream, under its Changes filter. On an authored page, a record:history or record:activity block shows the record's field changes from its own activity, and neither reads this flag. Pair with per-field trackHistory to pick which field diffs are summarized; audit capture itself is always on for compliance The order asked for "and the History block on an authored page". Measured on `main`, neither authored block reads `trackHistory`, and neither is the only one that shows changes: `record-history.tsx` self-fetches `sys_activity` and keeps the field-mutating types (`HISTORY_TYPES`), and `record-activity.tsx` self-fetches the same rows and maps `created` / `updated` / `deleted` / `assigned` / `shared` to `field_change` items (`ACTIVITY_TYPE_TO_FEED_TYPE`, `recordActivityFeed.ts`), shown under the Changes filter unless the author writes `unifiedTimeline: false`. On an assigned page `RecordFieldChangesContext` is `null`, so the flag's `sys_audit_log` diffs reach neither block. The text (r2) names both blocks, says neither reads this flag, and claims no exclusivity; r1's "only where it places a record:history block" and its "only as activity lines" docblock parenthetical were wrong and are gone. ### Other sentences the same promises reached - `content/docs/ui/pages.mdx`, "The synthesized default": the tab strip is now Details / Timeline / Related (plus Approvals), a primary list is a table inside Details, attachments close the Related tab, and the timeline is one stream with the Changes filter. The three-column sentence drops "then Activity and History". - `ui/packages/plugin-detail/README.md` (r1), the wide-layout table: the 1280 px timeline column said "the discussion composer on top, then Activity and History"; on `main` after #27310 it is one stream (`buildDefaultTimeline` emits one `record:activity`), so it now reads "one stream: the composer on top, then the discussion, the activity and, with `enable.trackHistory`, the field changes, filtered by All / Activity / Discussion / Changes". The changeset gains `'@object-ui/plugin-detail': patch` because its `files[]` ships `README.md`. - `packages/spec/liveness/object.json`, row `trackHistory`: the evidence names the timeline stream instead of the History tab (status unchanged, `live`). - `packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`: one `why` string said a `'primary'` switch "promotes the list to its own tab"; it now says "places the list in the record's body". - Regenerated with `pnpm --filter @objectstack/spec check:generated --fix`: `content/docs/references/{api/metadata,data/field,data/object,system/migration}.mdx`. No translation bundle carries these describes (the Studio form's own `helpText` rows are separate text; see Acceptance notes). ## Tests - New `packages/spec/src/data/record-page-describes.test.ts` (10 tests) pins each rewritten describe's load-bearing phrase in the JSON-schema projection, plus the absence of "its own tab" / "History tab" and of the old unbounded refusal sentence. - Reverse verification: with `object.zod.ts` and `field.zod.ts` restored from `origin/main` 6d7634a, the file goes red, 10 of 10 (`Test Files 1 failed`, `Tests 10 failed`); restored from `HEAD`, `git diff HEAD` empty. - r2 moves the `trackHistory` pin to the new sentence and adds two negative pins (`only where it places`, `activity lines`). Reverse leg: with `object.zod.ts` restored from the r1 head `0fae4859d`, exactly that test goes red (`Tests 1 failed | 9 passed`); restored from `HEAD`, `git diff HEAD` empty. - r1 extends three pins (the platform-prefix clause in both related-list describes, and a `trackHistory` phrase that r2 replaced). Reverse leg: with `object.zod.ts` restored from the r0 head `e4e06533f`, exactly those 3 go red (`Tests 3 failed | 7 passed`); restored from `HEAD`, the tree is clean. - `@objectstack/spec` on head `456377baa`, under the verify lock: `check:generated --fix` (it regenerated `content/docs/references/data/object.mdx`, then all 14 artifacts up to date), then `test` (Test Files 750 passed, Tests 22306 passed, 1 todo) and `typecheck`: VERDICT command-exit 0. Only `packages/spec` source and one README move, and the public surface is byte-unchanged, so no importer owes a run. ## Acceptance notes - **Skills (governed, not edited here).** Two published skill sentences still narrate the v2 page and the unbounded refusal: `skills/objectstack-ui/SKILL.md` lines 109-110 and 117-121 ("its **own tab** on the tabbed page"; "gets its **own tab**"; "Promoting a child table to a tab"), and `skills/objectstack-data/rules/relationships.md` lines 347-353 ("(its own tab on the tabbed page)"; "a name matching no inbound FK is refused"). `skills/**` is Tier H, so they are left for a governed follow-up (carded as #27354). - **#27310's own file.** `ui/content/docs/guide/slotted-pages.md` (on #27310's head) said "one tab per related list flagged `relatedList: 'primary'`" and "→ **History**". It no longer exists on `main` (retired by #27329), so nothing is owed. - **Studio form help.** `object.form.ts`'s `relatedLists` help ("saving or validating the object refuses it") is shown only in Studio, whose pre-write check (PR #27303, merged) refuses such a name, so it is true for its reader and stays. - Hand-written `content/docs/protocol/objectql/security.mdx` also said "History tab"; #27310 rewrites that line, so it is not touched here. ## Verification Derived gates on head `456377baa`: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 106; all 106 exit 0. `dispatch-gates --ran` reconciles: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN. <details><summary>106 gate commands and exit codes</summary> ```text node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-adr-links.mjs :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 node scripts/check-comment-mask-adoption.mjs :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-dev-prereqs.mjs --self-test :: exit 0 node scripts/check-doc-route-spelling.mjs --advisory :: exit 0 node scripts/check-doc-route-spelling.mjs --self-test :: exit 0 node scripts/check-docs-section-name.mjs --self-test :: exit 0 node scripts/check-docs-section-name.mjs :: exit 0 node scripts/check-dts-emitted.mjs --self-test :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-issue-citations.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 node scripts/check-plugin-teardown-shape.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-section-landing-index.mjs --self-test :: exit 0 node scripts/check-section-landing-index.mjs :: exit 0 node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0 node scripts/docs-audit/check-affected-docs.mjs :: exit 0 node scripts/docs-audit/check-drift-comment.mjs :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0 pnpm --filter @objectstack/spec run check:api-surface :: exit 0 pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0 pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0 pnpm --filter @objectstack/spec run check:docs :: exit 0 pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm --filter @objectstack/spec run check:empty-state :: exit 0 pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0 pnpm --filter @objectstack/spec run check:export-origins :: exit 0 pnpm --filter @objectstack/spec run check:exported-any :: exit 0 pnpm --filter @objectstack/spec run check:generated :: exit 0 pnpm --filter @objectstack/spec run check:liveness :: exit 0 pnpm --filter @objectstack/spec run check:llms-txt :: exit 0 pnpm --filter @objectstack/spec run check:skill-examples :: exit 0 pnpm --filter @objectstack/spec run check:skill-refs :: exit 0 pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:corpus-claim-drift :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:doc-anchors :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:doc-frontmatter :: exit 0 pnpm check:docs-audit-scope :: exit 0 pnpm check:docs-redirects :: exit 0 pnpm check:docs-single-h1 :: exit 0 pnpm check:docs-spec-enumerations :: exit 0 pnpm check:docs-transcript-drift :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:dts-closure :: exit 0 pnpm check:dual-build-cjs-loads :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:issue-citations :: exit 0 pnpm check:keyed-text-bounds :: exit 0 pnpm check:lean-entry-closure :: exit 0 pnpm check:logger-receiver-detach :: exit 0 pnpm check:merge-driver :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:objectql-double-limit :: exit 0 pnpm check:org-identifier :: exit 0 pnpm check:page-declaration-shape :: exit 0 pnpm check:platform-checklist :: exit 0 pnpm check:platform-object-tenancy-census :: exit 0 pnpm check:pm-changeset-deadline-census :: exit 0 pnpm check:pm-prior-rulings :: exit 0 pnpm check:published-files :: exit 0 pnpm check:published-readme-links :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:react-page-adapter-contract :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:role-word :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:slot-lookup :: exit 0 pnpm check:sourcemap-no-sources-content :: exit 0 pnpm check:spec-docblock-symbol-anchors :: exit 0 pnpm check:spec-parsed-alias :: exit 0 pnpm check:system-context-census :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:vendor-version-stamps :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzgaGBuahtvHVCAa4ny)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzgaGBuahtvHVCAa4ny --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 881af5d commit ad3a640

12 files changed

Lines changed: 225 additions & 51 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@object-ui/plugin-detail': patch
4+
---
5+
6+
docs(spec): the related-list descriptions say which unknown names are refused, and `relatedList: 'primary'` and `enable.trackHistory` describe the current record page
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Description text only: no key, type, default, accepted value or export moves, and every stack that parsed, validated or published before still does.
12+
13+
- **`ObjectSchema.relatedLists` and `fieldGroups[i].relatedList`.** The descriptions promised that a name matching no inbound `lookup` / `master_detail` relationship is refused. They now say exactly where. A name whose child is in the stack but has no such relationship to this object is refused everywhere. A name whose child exists nowhere is refused by `os validate` / `os build` (which leave a `sys_` / `cloud_` / `ai_` name to the runtime) and by a metadata write that names no package. A write into a package stores it, because a child outside that package may be a downstream package's object that points in; the record page places nothing for it.
14+
- **`relatedList: 'primary'`.** A primary list is a full-width table in the record's details column, after the first field section, and inside the Details tab when the page is tabbed. It no longer gets a tab of its own; a supporting list is a card in the related column (the shared Related tab when tabbed).
15+
- **`enable.trackHistory`.** The record's field changes join its one timeline stream, under the Changes filter, instead of a History tab. On a page an author assigns, a `record:history` or `record:activity` block shows the record's field changes from its own activity, and neither reads this flag.
16+
- **`@object-ui/plugin-detail` README.** The wide-layout table's timeline column is the one stream (composer, discussion, activity and, with `enable.trackHistory`, field changes) rather than "Activity and History".
17+
18+
What to do: nothing.

‎content/docs/references/api/metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ Metadata query with filtering, sorting, and pagination
958958
| **titleFormat** | `string \| { dialect: 'template'; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField; a composite title that import, sort or search must match to a stored computed field (a non-formula field with an expression, computed and stored on every write) designated as nameField — a text formula designated as nameField stays legal for a title shown for display only. Placeholders may be written `{{field}}` or `{field}` — the title renderers treat the two as equivalent, normalizing `{{field}}` to `{field}` before substituting; neither spelling is judged at parse time. |
959959
| **imageField** | `string` | optional | The record's picture: names the field the record page header (record chrome) draws beside the title — one object-level declaration every record detail page reads, not a per-page header prop. Must name a field of this object whose type is `image` or `avatar`; any other name is refused. A record whose field is empty shows no picture (no initials or placeholder). |
960960
| **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
961-
| **relatedLists** | `string[]` | optional | Business order of this object's related lists on its record page, most important first. Each entry is a child object name (e.g. "task"), or object.field when that child has more than one relationship to this object (e.g. "event.related_to"). Listed relationships come first in this order; unlisted ones follow in the derived order (owned master_detail children, then lookup children). An entry that matches no inbound lookup / master_detail field of this object is refused. It orders only: relatedList: false on the relationship still hides a list, and relatedList: 'primary' still places a list in the record's body, so the order applies among the body tables and among the side cards. |
961+
| **relatedLists** | `string[]` | optional | Business order of this object's related lists on its record page, most important first. Each entry is a child object name (e.g. "task"), or object.field when that child has more than one relationship to this object (e.g. "event.related_to"). Listed relationships come first in this order; unlisted ones follow in the derived order (owned master_detail children, then lookup children). An entry whose child is in the stack but has no inbound lookup / master_detail field to this object is refused everywhere. An entry whose child exists nowhere is refused by os validate / os build (which leave a sys_ / cloud_ / ai_ name to the runtime) and by a metadata write that names no package; a write into a package stores it, since a child outside the package may be a downstream package's object that points here, and the record page places nothing for it. It orders only: relatedList: false on the relationship still hides a list, and relatedList: 'primary' still places a list in the record's body, so the order applies among the body tables and among the side cards. |
962962
| **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. When the named field carries options, it must be a single-value `select` / `radio` with inline `options`, each declaring its `kind` ('open' / 'won' / 'lost'); a picklist-backed or multi-value stage field is refused. A name that resolves to no declared field, or a field with no options, is not judged at parse. |
963963
| **editMode** | `Enum<'modal' \| 'page'>` | optional | Edit-interaction intent for records of this object: 'modal' opens the edit form as a dialog over the current view; 'page' navigates to a dedicated full-page edit route. Absent = the renderer picks its own default (objectui defaults to modal). Cross-renderer intent, not pixel styling (family). |
964964
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object; … }>` | optional | Built-in named list views (segmented tabs) shipped with the object schema — "views" mode, dropdown userFilters allowed, no page-only tabs (ADR-0047) |

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const result = CurrencyConfigSchema.parse(data);
7474
| **inlineTitle** | `string` | optional | Title for the inline master-detail grid |
7575
| **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. |
7676
| **inlineAmountField** | `string` | optional | Numeric child field summed for the inline grid total |
77-
| **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 beside the record (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 the field sections) as a full-width table (its own tab on the tabbed page). 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. |
77+
| **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. |
7878
| **relatedListTitle** | `string` | optional | Title for the detail-page related list |
7979
| **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. |
8080
| **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)