Repository navigation
Commit ad3a640
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
- .changeset
- content/docs
- references
- api
- data
- system
- ui
- packages/spec
- liveness
- src
- data
- system
- ui/packages/plugin-detail
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
958 | 958 | | |
959 | 959 | | |
960 | 960 | | |
961 | | - | |
| 961 | + | |
962 | 962 | | |
963 | 963 | | |
964 | 964 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
0 commit comments