Repository navigation
feat(spec,lint,app-shell)!: a field group's relatedList names body tables only - #27592
objectstack-fleet[bot] wants to merge 11 commits into
Conversation
…bles only One predicate, isBodyTableRelationship in @objectstack/spec/data, decides whether an inbound relationship is a body table; defineStack and the lint door member refuse a field group naming a side card or a hidden list; the record page and Studio read the same predicate. Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
… predicate verdicts and refusals Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
…0087 D3), changeset, liveness rows Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
…refusal Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
…names body tables only Written on top of the #27352 wording: the sentence saying relatedList: false on the FK still hides the table is replaced by the body-table rule, the refusal at defineStack and the metadata write door, and the remedy. A describe pin in related-list-body-table.test.ts; object.mdx regenerated. Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 166 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 29f7bc1e86112a772e9c4cffab7d84cea1290b0c && git checkout 29f7bc1e86112a772e9c4cffab7d84cea1290b0c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4a00b2b7c159685c8931d4fa84695a8d7bc04a7c 31b2326cd10779fea6b58c373c4eca1d4d476298 && git checkout -B drift-repro 4a00b2b7c159685c8931d4fa84695a8d7bc04a7c && git merge --no-ff 31b2326cd10779fea6b58c373c4eca1d4d476298
node scripts/docs-audit/affected-docs.mjs --json 4a00b2b7c159685c8931d4fa84695a8d7bc04a7c
|
…narrowed and ships minor A value an author could write is refused, which AGENTS.md reads as narrowed, so each package the diff moves takes minor. Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 31b2326 · 2026-10-29T15:19Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: ① Derived judgmentsRead off the diff at
② Semver level
③ Boundary flags
④ Design judgmentNarrowing face, against the published spec on Per
Narrowing floor, the door and the UI (read in the cited code):
Findings: no must-fix. Should-fix: (1) the Implemented-by: VERDICT: PASS Generated by Claude Code |
…ld remedy too The refusal sentence and the docblock offer three remedies; the describe, which Studio and the reference docs show, now offers the same three. Pin updated; object.mdx regenerated. Claude-Session: https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U Co-authored-by: Claude <noreply@anthropic.com>
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head e236829 · 2026-10-29T15:40Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Delta review. This record is for head ① Derived judgmentsRead off the compare and the head's tree, not the commit message.
② Semver levelUnchanged by the delta: ③ Boundary flags
④ Design judgmentNarrowing face, against the published spec on
Findings: no must-fix; no should-fix. Note: the describe's remedy condition says "also names a side card" where the sentence may also name a hidden list; harmless, recorded for the next prose pass. Pending: every check-run on Implemented-by: VERDICT: PASS Generated by Claude Code |
…-xkumfw # Conflicts: # ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Requested by Yibo Wang · project thread
Fixes #27478
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no file under
skills/**teachesfieldGroups[].relatedList;skills/objectstack-data/rules/relationships.mdandskills/objectstack-ui/SKILL.mdteach only the FK's ownrelatedList, whose meaning does not change.Seat ruling on #27478: option B (narrowing). Whether a related list is a body table is decided once, by the relationship's own keys on the child's FK (
relatedList: 'primary', or amaster_detailthat setsinlineEditand declares norelatedList), per ruling B on #27055. A field group'srelatedListonly positions a body table; it never promotes a side card. Option A would give body-ness two sources that can disagree (the FK saying side card, the group saying body). So a group naming a relationship that is not a body table is refused, with a prescription, and Studio offers only body tables.Landing order. PR #27352 rewrites the same
fieldGroups[i].relatedListdescribe and docblock. Per the seat, #27352 landed first and this PR writes its text on top of #27352's wording, replacing only its "relatedList: falsestill hides the table" sentence. No other #27352 line is touched.维护者速读
@objectstack/spec的唯一判定isBodyTableRelationship(这个子关系是不是记录页的主表);field group 的relatedList指向侧栏卡片或被隐藏的关系时,defineStack和对象写入口(lint 规则related-list-group-not-body)都会拒绝并给出改法;Studio 只列主表;记录页、Studio 和拒绝读同一个判定。master_detail+inlineEdit+relatedList: true现在是侧栏卡片(与该键的说明一致)。回滚即还原本 PR。packages/spec/src/data/related-list-body-table.ts(判定与拒绝句),packages/spec/src/stack.zod.ts(collectRelatedListNameErrors内的调用),ui/packages/app-shell/src/views/studio-design/ObjectGroupInspector.tsx。pnpm --filter @objectstack/spec exec vitest run src/data/related-list-body-table.test.ts src/stack-related-lists-order.test.ts,应全绿。What changes
Measured first, on
origin/main273137dAn object whose field group names a relationship that is not a body table parsed, passed
defineStackand the object write door, and drew nothing at the group. A probe over six child shapes (a plainlookup,lookupwithrelatedList: true,lookupwithrelatedList: false, amaster_detailwith noinlineEdit, and two body-table controls) answered the same for all six:ObjectSchema.safeParse: true,defineStack: accepted,door errors: [].buildDefaultTabs(plugin-detail) drewdetails(basic,terms)withrecord:related_list(task)left in Related, so a group namingtaskplaced nothing.The record page also had two copies of the rule. app-shell
isMainContentList(behindderiveRelatedLists'isPrimaryand Studio's relationship inspector) counted'primary', or amaster_detailwithinlineEditand norelatedList.RecordDetailView'sisBodywasisPrimaryORmaster_detail+inlineEdit, which ignoredrelatedList: true.One predicate
isBodyTableRelationship(field)inpackages/spec/src/data/related-list-body-table.ts, exported from@objectstack/spec/data. Its readers:deriveRelatedListssetsisPrimaryfrom it (isMainContentListis deleted), andRecordDetailView'sisBodyis nowisPrimary, so plugin-detail'sbodyTablePlacementreceives the spec verdict;RelatedTablePlacement(ObjectGroupInspector.tsx) offers only relationships withisPrimary, and the relationship inspector reads an absentrelatedListthrough it;defineStackand at the door.fieldGroupBodyTableRefusalwrites the one refusal sentence both surfaces print.The refusal
defineStack(os validate,os build):collectRelatedListNameErrorspushes the sentence forfieldGroups[i].relatedList, as aSTACK_CROSS_REFERENCE_INVALIDissue.PUT /api/v1/meta/object, Studio, MCP):validate-related-list-refsreportsrelated-list-group-not-body(error) atobjects.<name>.fieldGroups[i].relatedList, so the write answers422 INVALID_METADATA. It runs under the closure boundary the rule already documents: a child outside the judged collection is left unjudged, as the name check leaves it.os explain related-list-group-not-bodyhas an entry.object.fieldfor the one that is.relatedListsentries are not judged this way: they order body tables and side cards alike.Example sentence (from the spec test):
Describe and docblock
Written on top of #27352's wording (merged as ad3a640). Only the last sentence of the describe and of the docblock change.
Before (
fieldGroups[i].relatedListdescribe, last sentence):After:
The docblock says the same, names
isBodyTableRelationshipand the rulerelated-list-group-not-body, and keeps "Body tables no group places keep the record page's default position."content/docs/references/data/object.mdxis regenerated. The phrases are pinned inrelated-list-body-table.test.ts, beside #27352's pins inrecord-page-describes.test.ts, which stay green.field.zod.ts's "A field group'srelatedListcan place it among the field sections" (onrelatedList: 'primary') is true as written once this lands, so it is not edited (and it is #27352's line).One record page cell moves
A
master_detailFK that setsinlineEditand declaresrelatedList: trueis a side card, asrelatedList's description says ("write true to keep it a side card") and as Studio's relationship inspector writes it.RecordDetailViewused to draw it as a body table. It now moves to the related column. The seat confirmed this reading. Pinned byRecordDetailView.bodyTablePredicate-27478.test.tsx.ADR-0087 and stored rows
registered field-group-related-list-side-card-refused, a D3 semantic entry (packages/spec/src/migrations/entries/semantic/18.field-group-related-list-side-card-refused.ts) with no D2 conversion, because which remedy the author meant (promote the FK, or drop the placement) is not recorded.sys_metadataobject row carrying such a group is not rewritten and still loads and registers unchanged. Its record page draws what it drew before, except the one cell above. Its next save through the metadata write door is refused422atfieldGroups[i].relatedListuntil fixed, and so is a write of the child that turns a body table a stored group places into a side card (pinned inruntime-gate.object-writes.test.ts).47a0666) declares a field grouprelatedListat all. Deployed metadata was not measured.Metadata-protocol: narrowed: a value an author could write is now refused (AGENTS.md: "a value refused ... isnarrowed"), so every package this diff moves takesminor(@objectstack/spec,@objectstack/lint,@object-ui/app-shell,@object-ui/plugin-detail,@object-ui/i18n), and the BREAKING banner stays.Liveness
object.jsonfieldGroups.relatedList: planned → live (readersbodyTablePlacement,collectRelatedListNameErrors,validateRelatedListRefs).field.jsonrelatedList: evidence re-cited toderiveRelatedListsandisBodyTableRelationship.Why
A group naming a side card saved, published and silently drew nothing, and Studio offered it. Under ruling B the FK is the only source of body-ness, so the group is refused rather than honoured.
Verification
All on head
78c79004unless marked (head31b2326cchanges only the changeset's levels and declaration line), underos-verify-lock.packages/spec/src/data/related-list-body-table.test.ts: the predicate's verdict table (14 shapes plus null and undefined), the exact refusal sentence, and the describe pins.packages/spec/src/stack-related-lists-order.test.ts:defineStackrefuses a group naming a side card, an inline-editedmaster_detailwithrelatedList: true, a plainmaster_detailand a hidden list, and a mixed bare name; body-table controls pass.packages/lint/src/validate-related-list-refs.test.tsandruntime-gate.object-writes.test.ts: the door refuses a group naming a side card and publishes it once the FK is primary; a child write that makes a placed body table a side card is refused; the lint sentence equals thedefineStacksentence word for word.packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts:PUTanswers422with rulerelated-list-group-not-bodyatobjects.rl_quote.fieldGroups[0].relatedList; the control saves.ui/packages/app-shell/src/views/RecordDetailView.bodyTablePredicate-27478.test.tsx(mounted page): an inline-editedmaster_detailwithrelatedList: trueis a side card; with norelatedListit is drawn at its group in the details column.ui/packages/app-shell/src/views/studio-design/ObjectGroupInspector.relatedListPlacement-27054.test.tsx: Studio offers None plus body tables only, and a stored side-card value shows the not-a-body-table line.ui/packages/plugin-detail/src/synth/__tests__/buildDefaultPageSchema.bodyTables-27055.test.ts: a group naming a side card promotes nothing.scripts/ablation-replace.mjs, each restored to HEAD withgit diff HEADempty, on55a063ec):RecordDetailViewisBodyput back to the old inline copy: the mounted test went 1 failed, 1 passed (the control stayed green);defineStackpush deleted:stack-related-lists-orderwent 4 failed, 19 passed;@objectstack/spec839 files, 23799 tests passed;@objectstack/lint177 files, 7607 passed;@objectstack/metadata-protocol279 files passed, 4 skipped; touched ui files 3 files, 22 tests passed; uiscripts/169 files passed, 2 skipped.check:generated14 of 14 up to date.turbo run build --concurrency=2, 115 of 115 tasks.dispatch-gates --commandsderives 113; 113 run, all exit 0;--ranreconciles 113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN.!and the narrowing arm): full@objectstack/dogfood testexit 0, 350 files passed, 3 skipped (2817 tests passed, 20 skipped).check-changeset-no-majorwith this body as the event: exit 0 on31b2326c(level axis:no (narrowing)andnarrowed, no moved package gradedpatch);check-adr-0087-registrationexit 0Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzgpxjnsDPHLokR644U