Skip to content

feat(app-shell): Studio authors relatedList, the related-list block's columns/sort/filter, ordered highlight fields, and slotted-by-default record pages - #27163

Merged
objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-27054-studio-related-list-controls
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-27054-studio-related-list-controls

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Part of #27054. This PR lands four of the card's five Studio items. The card's relatedLists drag-to-order editor stays open on the card: it waits for #27069 to ship the key, as the card and the dispatch both say.

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 现在能直接编辑记录页 v3 读取的几项元数据。查找/主从字段有「关联列表」三态控件(显示 / 不显示 / 主要(进正文))和筛选构建器;关联列表块露出列、排序、筛选、条数、「查看全部」、添加;重点字段变成可拖排序的有序列表(上限 6)带要点条预览;新建记录页默认是空槽的 slotted 页,「转成整页」是显式按钮。
  • 为什么改:这些键 spec 早已声明、记录页也在读,但 Studio 没有控件,作者只能手写 JSON;新建记录页还会把合成页整份拷进去,从第一天起就和默认页分叉。
  • 风险与回滚:只动 Studio 前端(app-shell)与 studio 文案包,不改任何 spec schema、不碰记录页运行时渲染。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx(RelatedListFields);ui/packages/app-shell/src/views/metadata-admin/previews/PagePreview.tsx(convertToFull,写回的是 spec 认可的落盘形状);ui/packages/app-shell/src/views/metadata-admin/anchors.ts(新建页 seed)。
  • 两分钟验证:在 Studio 打开 showcase 的 Invoice,点 Account 列的编辑按钮,字段面板下方应看到「Related list on the parent record」三态控件,「Primary (in body)」处于选中;截图见证据分支 claude/issue-27054-evidence。
  • 需要你做的:无,席位落地。

What changes

  1. Field inspector: related list on the parent record. On a lookup or master_detail field, a new section edits the two keys that describe the child collection on the parent's record page.
    • A three-state control writes relatedList. "Don't show" writes false and "Primary (in body)" writes 'primary'. "Show" leaves the key absent, the spec's own "true or absent = shown"; a stored true reads as Show.
    • relatedListFilter is edited with the same builder the dataset designer uses over the list toolbar's FilterBuilder. That field is now one shared component, FilterConditionField, moved out of the dataset inspector rather than copied. A stored filter the flat builder cannot hold (nested, OR) is kept as stored and named.
    • The section is absent on tree fields, which the spec gives no related list.
  2. record:related_list block inspector. The block now curates columns, sort, filter, limit, showViewAll and add, beside objectName, relationshipField and title. Every one is a prop RecordRelatedListProps already declares.
    • columns, sort and filter read the child object named by objectName. filter uses the list toolbar's builder (the filter-builder form widget's own field, now exported), so it writes spec ViewFilterRule[].
    • showViewAll shows the spec default (on) when unset. add is a JSON box with a shape sample, the only door that can add that nested key.
    • A stored value in the other arm of a union is shown as JSON rather than dropped into the row editor: a string sort, or columns given as saved-view column objects.
    • The limit hint reads 5, the renderer's own default, instead of 10.
  3. Object settings: highlightFields as an ordered list. Rows can be dragged or moved with up and down buttons (the keyboard path). Fields are appended up to six, and a longer stored list is shown whole. A strip preview below draws the picked order. Removing the last field removes the key. The editor is HighlightFieldsEditor in studio-design.
  4. New record pages start slotted. In the metadata page-create form, the kind now follows the page type until the author picks one: record gives slotted, everything else full. This is a new page-kind create-derive transform.
    • A record page is created kind: 'slotted' with no slots, which renders as the synthesized default and keeps following the object.
    • The synthesized regions are copied only when the author explicitly asks for a full page: by choosing full at create, or with the designer's new "Convert to full page" action.
    • That action re-synthesizes the regions in the persisted shape (field names, English default heading), exactly as the create-time seed does. It then switches the kind to full and drops slots.
    • An empty slotted page now opens on the slot editor with every slot inherited. Before, it opened a blank regions canvas whose "Add region" wrote regions a slotted page never reads.

Why

The record-page v3 epic makes the record page read these keys, and the spec already declares all of them, but Studio had no control for most of them. Authors had to hand-write JSON, or had no door at all: relatedList had no control, and the block's column, sort and filter props were absent. A new record page was seeded with a copy of the synthesized page, so every authored page forked from the default on the day it was created. The maintainer's rulings on the card fix the shape of each control.

Tests

All test runs are on head 23a697c2 (after merging main), run from the repository root.

  • New pins:
    • ObjectFieldInspector.relatedList-27054.test.tsx: 「主要」 on the task → lead FK writes 'primary', and the lead page built by the real deriveRelatedLists and buildDefaultPageSchema chain lists tasks first. Before the click, the control order is notes first.
    • PageBlockInspector.relatedListProps-27054.test.tsx: columns, filter and limit round-trip, and every written block passes ComponentPropsMap['record:related_list'].
    • ObjectSettingsPanel.highlightOrder-27054.test.tsx: order is preserved through buttons, drag and add, plus the cap of six.
    • PagePreview.slottedDefault-27054.test.tsx and anchors.page-seed.test.ts: a new record page is kind: 'slotted' with empty slots and passes PageSchema. Convert-to-full and full-create both write blocks that pass their spec props rows.
  • Re-pinned (patch round 1, head 7731242e37): recordDetailSections.agreement-12442.test.ts — the seed-and-runtime agreement now holds on the explicit-full path (fullSeedFor), and a new case pins that a record page created with no kind or slotted seeds exactly { kind: 'slotted' } with no record:details node; the three-call-sites block is unchanged.
  • Updated pins:
    • block-config-i18n.test.ts: one more add button, one more literal placeholder, and the limit literal is now 5.
    • block-config.test.ts: the new filter-rules kind.
    • block-config-schema-parity-8216.test.ts: the census now judges the row editor's element through the single array arm of a string | array union, which is more coverage, not less.
Test and type-check readings
pnpm --dir ui exec vitest run --maxWorkers=2 (touched families: metadata-admin page seed, PagePreview, PageBlockCanvas, block-config, PageBlockInspector, ObjectFieldInspector, DatasetDefaultInspector, datasetFilterCondition, ResourceEditPage, widgets, ObjectSettingsPanel, packages/i18n)
 Test Files  198 passed (198)
      Tests  2268 passed | 13 skipped (2281)

pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-check
os-verify-lock: VERDICT command-exit 0

Before the merge, on 997f09f2 plus the label rename:
pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/views/studio-design/ packages/i18n/
 Test Files  1 failed | 969 passed (970)   -> the one failure was this branch's "Hidden" label colliding with the field's own Hidden checkbox; renamed to Show / Don't show and re-run:
 ObjectFieldInspector*  Test Files 23 passed (23); StudioDesignSurface* + DataPillar*  Test Files 127 passed (127)
Ablations (one-shot, restored by blob hash)
anchors.ts: "if (draft.kind !== 'full') return { kind: 'slotted' };" neutralised
  -> anchors.page-seed.test.ts: Tests 2 failed | 6 passed; restored blob == HEAD, git diff HEAD empty
PagePreview.tsx convertToFull: toAuthoredDetailSections(...) replaced by identity
  -> PagePreview.slottedDefault-27054.test.tsx: 1 failed, "record:details sections.0.fields.0: Invalid input: expected string, received object"; restored blob == HEAD

Acceptance notes

Verification

  • Screenshots are on the never-merged branch claude/issue-27054-evidence. They come from a local showcase app (--fresh database) running this branch.
    • The field inspector on showcase_invoice.account with 「Primary (in body)」 selected.
    • The page-create form switching Kind to slotted for a record page.
    • The designer's default-page note and "Convert to full page".
    • The converted page saved with no spec banner.
    • The ordered highlight list with its strip preview.
  • Local gates: the cloud run's gate block is in report comment 6464856235 on the card (head 23a697c2), the patch round's in 6466884994 (head 7731242e37; 34 / 34 derived, the vitest run scripts/ red is the macOS host gap). CI on 7731242e37: all nine required contexts success, Test UI 16 / 16 shards.
  • Final head: 7731242e37 (the cloud head 23a697c2 merged with main b8615afaba plus the re-pin).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No spec change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; eslint over the changed files reports no new error.
  • 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: 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 maintainer brief and quoted rulings excepted).
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT


Generated by Claude Code

claude added 10 commits October 28, 2026 05:43
…converting it to a full page is an explicit action

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
…den / primary) and relatedListFilter with the shared filter builder

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
…, filter, limit, showViewAll and add

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
…, drag-to-reorder list of up to six with a strip preview

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
…-record-page controls

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
… from the field's own Hidden checkbox

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
…hape, so the converted page passes the spec

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
@github-actions github-actions Bot added size/xl 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 Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · PR #27163 ← #27054 · epic PM session_cdb7a717-def7-4e71-9988-f089fbaa18ff · 2026-10-28T09:27Z

Verdict: REWORK (patch round on this PR, same claim) — not governed (0 of 27 paths; packages/spec untouched), +1573 / −152. The report on the card (comment 6464856235) is read and accepted as filed: 34 / 34 gates exit 0 with the --ran reconciliation, two ablations red-then-restored, the live Studio run that caught the convert-to-full persisted-shape defect. The seat's spot checks on 23a697c2 hold: anchors.ts seeds a record page slotted unless the author picked full; createDerive.ts's page-kind transform; RelatedListFields reads true / absent as Show, writes false / 'primary', deletes the key for Show, absent on tree; relatedListFilter goes through the one shared FilterConditionField.

Why rework — one red that is this PR's, pinned to head 23a697c2: Test UI (9/16) fails packages/app-shell/src/views/recordDetailSections.agreement-12442.test.ts (3 cases: "the page carries exactly one record:details node: expected [] to have a length of 1", and the CONTROL "expected undefined to deeply equal [ { name: 'main', … } ]"; job 122723053671). That pin (objectui#12442) asserts the page-create seed carries the runtime's detail sections — the very behaviour this card retires: a new record page is slotted with no regions, and the regions are copied only on an explicit full. The pin is outside the families the report ran. It is not a flake: its subject changed by ruling.

Patch round (one stroke, same branch):

  1. Re-pin recordDetailSections.agreement-12442.test.ts to the contract after this PR: the seed-and-runtime agreement holds on the explicit-full path (the draft carries kind: 'full'), where the seeded record:details still carries the runtime's sections in the persisted shape and the CONTROL object with fieldGroups still seeds { group } references; add one case that the default (no explicit full) seeds kind: 'slotted' with no regions — the agreement's new first half. Keep the "three call sites, one derivation" assertion as it is.
  2. Run that file plus the suites the body names; rerun the derived gates on the final head; --ran reconciliation.
  3. Push with a user token (the cloud session's pushes come through the Claude GitHub App, which does not fire this repository's PR workflows); report as a comment on Studio: the object and field inspectors author what the v3 record page reads — relatedList (show / hide / primary), related-list columns / filter / limit on the block, and a new record page starts empty instead of a copy of the synthesized regions #27054 (first line os-dev-report, round patch round 1).

Out-of-scope findings (two class a, reach measured through public doors) will be filed by the seat at ACCEPT: the page designer canvas drawing a location field as [Object] and a select value raw (pre-existing on main), and InspectorJsonField's label not associated with its textarea. The Setup drawer's relatedListFilter JSON row: Acceptance notes.

Deviations recorded: the cloud session ran on Fable for its first minutes until the maintainer switched it to opus; it cannot receive this review, so the patch round runs as a local os-dev agent on the same branch under the same claim; labels_add 0 writes (none named).


Generated by Claude Code

hotlong and others added 2 commits October 28, 2026 17:30
…icit-full path

A record page is now created kind slotted with no regions, so the
seed-and-runtime agreement holds on the copy an explicit full makes: the
seeded record:details still carries the runtime's sections in the persisted
shape, and the fieldGroups CONTROL still seeds group references. One new case
pins the default half: a draft with no kind, or slotted, seeds
{ kind: 'slotted' } and no record:details at all.

Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · PR #27163 ← #27054 · epic PM session_cdb7a717-def7-4e71-9988-f089fbaa18ff · 2026-10-28T10:45Z

Verdict: ACCEPT — ordinary queue landing (governed register: 0 of 28 paths; packages/spec untouched; +1614 / −164, under the 3000-line threshold). Supersedes the REWORK above: the cloud session's build (report 6464856235) plus one local patch round (report 6466884994), both under the same claim.

Checklist, judged on GitHub and on the branch:

  • Shape: draft → main; first line Part of #27054 by design — the card's fifth item (the relatedLists drag-to-order editor) waits for spec: an object declares the business order of its related lists — relatedLists: ['task', 'event', …], an ordered list of child relationships like highlightFields is of fields; unlisted ones follow the derived order; a name that matches no inbound relationship is refused #27069 to ship the key, as the card and the dispatch both say, so the card stays open behind that one; one closing-keyword scan clean; Clause-②: no and Metadata-protocol: none line-initial in the body and in .changeset/27054-studio-related-list-controls.md. No content/docs/releases/.
  • Scope: 27 files, all under metadata-admin/**, studio-design/** and the studio i18n namespace; the card's field.form.ts / page.form.ts rows are deliberately unchanged, with the measured reason (the Setup drawer's SchemaForm has no object-field context; plumbing one would re-bind every *Field row to the wrong object) — accepted: the builder lives where the three-state control lives, and the Setup drawer's JSON row is an acceptance note. The surface beyond the card (PagePreview.tsx, PageBlockCanvas.tsx, createDerive.ts, registry.ts, widgets.tsx, DatasetDefaultInspector.tsx, two new files) is named with its reason in the body.
  • Patch round 1 (head 7731242e37): recordDetailSections.agreement-12442.test.ts re-pinned to the contract — the seed-and-runtime agreement asserted on the explicit-full path through one fullSeedFor helper, a new case pinning that no kind or slotted seeds exactly { kind: 'slotted' } with no record:details node, the three-call-sites block unchanged; anchors.ts untouched. Whole app-shell views directory 1246 files / 11776 tests green, so no other old pin conflicts with the new contract; ablation of the 'full' argument reds the four explicit-full cases with the CI job's own signatures and restores to the HEAD blob.
  • Spot checks (seat readings on 7731242e37): anchors.ts seeds a record page as kind: 'slotted' unless the author picked full, and copies regions only then; createDerive.ts adds the page-kind transform (record → slotted, else full); ObjectFieldInspector.tsx RelatedListFields reads true / absent as Show, writes false for Don't show and 'primary' for Primary, deletes the key for Show, and is absent on tree fields; relatedListFilter goes through the shared FilterConditionField extracted from the dataset inspector, not a second builder.
  • Rulings: Primary's label reads 「主要(进正文)」 per the v3 ruling; the spec: an object declares the business order of its related lists — relatedLists: ['task', 'event', …], an ordered list of child relationships like highlightFields is of fields; unlisted ones follow the derived order; a name that matches no inbound relationship is refused #27069 reference sits in the code comment because runtime strings carry no tracker numbers here (AGENTS.md wins over the dispatch's wording) — accepted.
  • Tests and gates: 198 files / 2268 tests on the touched families, app-shell + i18n type-check exit 0; 34 / 34 derived gates exit 0 with the --ran reconciliation quoted; two ablations (the slotted seed line, convertToFull's persisted-shape conversion) each red on their pin and restored to the HEAD blob. Live Studio run on the showcase app caught and fixed the convert-to-full persisted-shape defect before the PR. Pinned to head 7731242e37: 43 success, 11 skipped, 0 other — the nine required contexts all success, Test UI 16 / 16 shards. Verify lock: cloud container (no disclosure needed).
  • Screenshots: evidence branch claude/issue-27054-evidence @ bf202d60 (field inspector with Primary selected; create form switching to slotted; the designer's default-page note and Convert to full page; the converted page without a spec banner; the ordered highlight list) — read by the seat.
  • Out-of-scope findings (two class a, reach measured through public doors): filed as Page designer canvas draws a location field as "[Object]" and a select value raw when an authored record:details names its fields #27177 (page designer canvas draws a location field as [Object] and a select value raw, pre-existing on main) and Studio page block inspector: InspectorJsonField's Label is not associated with its textarea, so curated JSON props have no accessible name #27178 (InspectorJsonField's label is not associated with its textarea). The Setup drawer's relatedListFilter JSON row: Acceptance notes.
  • Deviations recorded: the cloud session ran on Fable for its first minutes until the maintainer switched it to opus; its pushes came through the Claude GitHub App, which does not fire this repository's PR workflows — the patch round's user-token push is what put the final head under CI; it cannot receive a review, so the patch round ran as a local os-dev agent on the same branch; labels_add 0 writes (none named).

Landing: ready + auto-merge in this stroke on the maintainer's standing authorization for this epic (verbatim, 2026-10-28: 「授权你用车队身份给 #26758 翻 ready 并挂 auto-merge(squash),后续本 epic 的 PR 复核 ACCEPT 后同样处理」). After MERGED the card stays open for its fifth item: pm:dispatched → pm:blocked with Blocked-by: #27069.


Generated by Claude Code

Merged via the queue into main with commit fd73f2b Oct 28, 2026
59 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-27054-studio-related-list-controls branch October 28, 2026 11:02
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/xl 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.

3 participants