Skip to content

fix(studio): show, keep and author a controlled-by-parent object's optional master - #31202

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-a6g2ku
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-a6g2ku

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread
Fixes #30974

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 现在认得「可选主字段」:OWD 总览把 required: false 的唯一主从字段显示为主对象;Studio 保存不再把作者写的 required: false 改成 true;字段检查器里唯一主从字段的「必填」可以取消(写成 required: false),下方说明无主记录只有所有者和「查看所有记录」可见。
  • 为什么改:controlled_by_parent admits an optional master (1/3): spec, lint and the read owner branch: a null-master detail row is readable by its owner only #30905 裁决 A 允许受父级控制对象的唯一主从字段可选,Studio 仍按「必须必填」处理,保存会悄悄改写作者选定的访问模型(p2)。
  • 风险与回滚:只动 ui/packages/app-shell 的 Studio 代码和一条 i18n 文案,不改 spec、不改运行时;回滚即还原本 PR。省略 required 的主字段仍按必填处理,两个以上主从字段时锁定照旧。
  • 先看:owd-sharing.ts(主字段改由 spec 的 controlledByParentMasterOf 判定,保存只补省略的 required)、ObjectFieldInspector.tsx(cbpOptionalMasterAllowed 一段)、owd-sharing.test.ts(被 feat(spec,security): a controlled_by_parent object may declare an optional master #30940 改标的那一行翻回一致)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/owd-sharing.test.ts,预期全绿,其中 "one master without required" 与 "one optional master (required: false)" 两行都显示主对象 crm_account。
  • 需要你做的:无,席位落地。

What changes

ADR-0055 §1 as amended by #30905 ruling A lets a controlled_by_parent object's ONLY master_detail be declared required: false, an optional master whose records may name none. Studio still treated every master as required. This PR covers the card's three items:

  • Display (owd-sharing.ts, deriveMasterObject). Which field is the master is now the spec's own answer, controlledByParentMasterOf from @objectstack/spec/data, so an optional master is shown as the master and the two-master shapes stay as the spec rules them (Studio does not re-decide them). An omitted required on the object's only master_detail is read as required first, as the spec's stored-row conversion cbp-master-detail-required-defaulted reads it, which is ruling A's "Studio still defaults it to required".
  • Save (owd-sharing.ts, requireCbpMasterDetails, used by PackageOwdOverviewPanel.tsx and ObjectSettingsPanel.tsx). Switching to Controlled by parent still sets required: true on each master_detail that writes no required (the builder's force), and now keeps an explicit required: false. The overview's 409 prerequisite list (cbpPrerequisite) reads the same predicate, cbpMasterDetailDefaultsRequired.
  • Authoring (ObjectFieldInspector.tsx). On a cbp object's only master_detail the Required box is no longer locked on: clearing it writes required: false (never a dropped key, which would be defaulted back to required), and the help text under it, new key designer.field.requiredCbpOptionalMaster (en, zh), says a record that names no master is visible only to its owner and to View All Records on the object. Beside another master_detail the old lock and text stand, because the spec admits no optional master there (builder and relationship/master-detail-required refuse it).

Why

Triage graded it p2: a Studio save silently turned an author's optional master into a required one, which rewrites the access model they chose.

Tests

  • The two display-agreement rows PR feat(spec,security): a controlled_by_parent object may declare an optional master #30940 re-labelled flip back to agreement: "one master without required" in owd-sharing.test.ts and in PackageOwdOverviewPanel.cbpRequiredMaster-12326.test.tsx now name crm_account, and the spec parse accepts the shape. The per-row "parses" override column is gone: every row's parse verdict now equals "a master is named".
  • New rows: one optional master (required: false), a required: false master-detail beside the required one, two master_detail fields none required.
  • New: requireCbpMasterDetails keeps an explicit required: false (map, array, and mixed with an omitted one); the overview save keeps it, through the rendered dial and Save button.
  • Inspector, driven through the rendered checkbox: the only master-detail can be cleared to required: false and ticked back; beside another master-detail the box stays locked with the old reason.

Run on head 3c18a37 (the code head; f7f9807 after it changes only the changeset): the three files, 3/3 files, 38/38 tests passed.

Ablations (each mutation through scripts/ablation-replace.mjs; every restore proven blob == HEAD and `git diff HEAD` empty)
Mutation Red
deriveMasterObject without the omitted-required default 2 (both "one master without required" rows)
master judged by the old required === true count instead of controlledByParentMasterOf 3 (both optional-master rows, the overview save test)
save predicate back to required !== true 2 (both keeps-optional tests)
inspector commit back to v || undefined 2
inspector lock back to cbpMasterDetail && required === true 2
inspector help text back to the old key 1

Acceptance notes

  • The overview names the master but does not say whether it is optional; the inspector's help text is where the optional master is explained. No new overview string was added.
  • A stored object whose only master omits required shows an unticked Required box (the box shows what is written), while the overview names it as the master. Ticking writes required: true; switching the model to Controlled by parent writes it too.
  • ObjectSettingsPanel.tsx gets a comment-only edit: its comment said the save forces every master_detail required, which this change makes false.
  • No content/docs/frontend/ page describes the inspector's Required lock; the cbp docs belong to controlled_by_parent admits an optional master (3/3): docs and the authoring skills teach it instead of a copied owner field #30907.

Verification

All local readings on head f7f9807 unless noted; CI runs the rest of the farm.

  • Derived gate list (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack): 36 families, every one exit 0. --ran reconciliation: "36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED (a DERIVED zero — all 36 recorded an exit code and none of them is 3)". The loop ran on 3c18a37; the four changeset families were re-run on f7f9807 (exit 0), and pnpm check:type-check-debt, which first answered exit 3 (it queued behind my own lock hold, nothing measured), was re-run under the lock on f7f9807: exit 0.
  • pnpm --filter @object-ui/app-shell run type-check: exit 0. pnpm --filter @object-ui/i18n run type-check: exit 0.
  • pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/ packages/app-shell/src/views/metadata-admin/inspectors/ packages/i18n/: 659/659 files, 7206 passed, 14 skipped. The whole app-shell suite (2,210 files) is left to CI's sharded Test UI.
  • pnpm --dir ui exec eslint on the nine changed ui files: exit 0, 0 errors; the 17 warnings are all on lines this PR does not touch.
  • pnpm --dir ui run check:new-line-citations: "0 new citation(s)", exit 0.
  • Builds first: the @objectstack/spec, formula, client, lint, sdui-parser closures, then @object-ui/app-shell^..., both lock VERDICT command-exit 0.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (The changed ui files were linted locally, exit 0; the repo-wide run is CI's.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…master

ADR-0055 §1 as amended lets a controlled_by_parent object's only
master_detail be declared required: false. Studio now names that master
in the OWD overview (via the spec's controlledByParentMasterOf), keeps an
explicit required: false on save, and lets the field inspector clear
Required on the only master_detail, with help text on the null-master
owner rule.

Claude-Session: https://claude.ai/code/session_01FEkQ5mJLhZasj7G83LSvsv
Co-Authored-By: Claude <noreply@anthropic.com>
The diff adds one key to @object-ui/i18n's published studio catalogue,
which widens it.

Claude-Session: https://claude.ai/code/session_01FEkQ5mJLhZasj7G83LSvsv
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 7, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-project-manager · session_01FEkQ5nLA7wiVY5YWjT9Svr

Contract review

Served-tier: TIER_DEFAULT
Head-sha: f7f98072faba226b37b93efee7d08f8c561c2731
Local-runs: none

The lane seat's own review of record, owed by the Clause-②: yes (widening) declaration alone (face declaration). No path of the diff is a published schema (packages/spec untouched) or a governed path.

① Derived judgments

  • @object-ui/i18n (published; ./namespaces/studio export) gains one key, designer.field.requiredCbpOptionalMaster, in studio/en.ts and studio/zh.ts, and removes or rewords none (net diff of ui/packages/i18n: 0 removed lines). A widening: Clause-②: yes (widening) is RIGHT.
  • Metadata-protocol: none is RIGHT: the spec already admits an optional controlled_by_parent master (ADR-0055 §1 as amended, controlled_by_parent admits an optional master (1/3): spec, lint and the read owner branch: a null-master detail row is readable by its owner only #30905 ruling A, PR feat(spec,security): a controlled_by_parent object may declare an optional master #30940). Studio now stops rewriting what the spec accepts; it reads the master through the spec's own controlledByParentMasterOf (exported on main, packages/spec/src/data/controlled-by-parent-master.ts), not a second copy of the rule.
  • @object-ui/app-shell (owd-sharing.ts, PackageOwdOverviewPanel.tsx, ObjectFieldInspector.tsx, a comment in ObjectSettingsPanel.tsx) is private: no published surface.
  • Accept sets: no door changes. The save door keeps refusing what the spec refuses (two masters); Studio's write now keeps an explicit required: false and still defaults an omitted required to true.

② Semver level

  • patch on @object-ui/i18n (and on the private @object-ui/app-shell, harmless) with both declaration lines on the PR body and the changeset: RIGHT under ruling A.

③ Boundary flags

  • Comment-only edit in ObjectSettingsPanel.tsx, outside the claimed file list: its comment described the old force-required write, which this PR makes false. Accepted (same file family, mechanical, made-false prose).
  • The full app-shell suite is left to CI's Test UI shards: accepted, the check-runs are the verdict.
  • The claim said Clause-②: no by default; it is amended on the card to yes (widening) (as measured).
  • open_questions: none.

Implemented-by: claude/project-thread-a6g2ku
Reviewed-by: session_01FEkQ5nLA7wiVY5YWjT9Svr

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-project-manager · session_01FEkQ5nLA7wiVY5YWjT9Svr

席位核对 · ACCEPT · 2026-11-07T17:48Z · head f7f98072faba226b37b93efee7d08f8c561c2731

Seat domain:ui#1 reviewed the diff of its own dispatch (claim 6709225345 on #30974 with amendment 6710234740, card thread session_01FEkQ5mJLhZasj7G83LSvsv, report 6710669379). Read against GitHub, not the report.

  1. Shape: 10 files, +239 / -102. owd-sharing.ts: deriveMasterObject now asks the spec's controlledByParentMasterOf which field is the master (an omitted required on the only master_detail is read as true first), and the save force moves to one predicate, cbpMasterDetailDefaultsRequired (type master_detail and required === undefined), so an explicit required: false is kept; PackageOwdOverviewPanel.tsx reads the same predicate for its prerequisite list. ObjectFieldInspector.tsx: on a cbp object's only master_detail the Required box is free, clearing writes required: false (not a dropped key), and the help text switches to the new key; beside another master_detail the old lock stands. ObjectSettingsPanel.tsx is comment-only. One new studio string in en and zh. Body says Fixes #30974; no other closing keyword.
  2. Premise: controlledByParentMasterOf is exported from @objectstack/spec/data on origin/main (its own tests read { field, optional }), so Studio follows the spec's answer instead of re-deciding the two-master shapes. On main the save force was required !== true, which rewrote an explicit required: false: the card's defect.
  3. Gates / check-runs: 55 runs on this head, 44 success, 11 skipped, 0 red. Dev: the three test files 38/38 on code head 3c18a37 (f7f9807 changes only the changeset); 6 ablations each red then restored, listed in the body. mergeable_state clean.
  4. Accepted deviations / notes: the overview names an optional master without marking it optional (the inspector text explains it; no new overview string). A stored only-master that omits required shows an unticked box while the overview names it as master; ticking or switching the model writes required: true. Both are in the PR body.
  5. Out of scope / Skills-docs: the cbp docs page belongs to controlled_by_parent admits an optional master (3/3): docs and the authoring skills teach it instead of a copied owner field #30907. Metadata-protocol: none; no packages/spec/src path, so Skills-docs: is not owed. Nothing filed.
  6. Landing: check-governed-merges --pr 31202: not governed, 341 changed lines, so no human approval owed. Clause-②: yes (widening) (one added i18n key): its TIER_DEFAULT review is PASS on this head (6710679335). Changeset patch for @object-ui/app-shell and @object-ui/i18n. Landing on the maintainer's standing words 「按照 pm-dispatch 规则复核过的都授权」 (2026-10-25T11:35Z) and 「你应该使用 pm-dispatch 技能审核和落地pr」 (2026-11-07T16:59Z). Relay pr_ready, then automerge_enable. On merge Studio: a controlled-by-parent object's optional master can be neither authored nor shown, after #30905 made required: false on its master valid #30974 closes by Fixes; the seat removes pm:dispatched.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 7, 2026 17:50
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 7, 2026 17:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 1c66988 Nov 7, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-a6g2ku branch November 7, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: a controlled-by-parent object's optional master can be neither authored nor shown, after #30905 made required: false on its master valid

1 participant