You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit fd73f2b
Browse filesBrowse the repository at this point in the historyBrowse files
authored
feat(app-shell): Studio authors relatedList, the related-list block's columns/sort/filter, ordered highlight fields, and slotted-by-default record pages (#27163)
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.
<details><summary>Test and type-check readings</summary>
```text
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 997f09f 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)
```
</details>
<details><summary>Ablations (one-shot, restored by blob hash)</summary>
```text
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
```
</details>
## Acceptance notes
- **Live verification found and fixed a defect in this branch.** The
first version of "Convert to full page" copied the canvas's render
schema, whose detail sections hold runtime field objects. On a real
showcase page the converted draft drew a 16-error spec banner. The
action now writes the persisted shape, and the props-row pin above
reproduces the original error when the fix is ablated.
- **Primary's wording follows the v3 ruling.** The label is "Primary (in
body)" / 「主要(进正文)」 and the help text describes a full-width table in the
record body. That rendering is #27069's half; until it lands, the
runtime still promotes a primary list to its own tab. The dispatch asked
the help text to cite #27069, but runtime strings carry no tracker
number in this repo, so the reference sits in the code comment on
`RelatedListFields`.
- **"Show" leaves `relatedList` absent rather than writing `true`.** The
spec reads both the same, and the inspector's convention is that unset
deletes the key.
- **The Setup metadata drawer's `relatedListFilter` row is still a JSON
box.** The spec's field form (`field.form.ts`) renders there through
`SchemaForm`, which has no object-field context in that drawer. Plumbing
one would also re-bind every `*Field` row of the field form to the wrong
object: `displayField` names a field of the referenced object. The
filter builder is in the Studio field inspector, where the three-state
control lives, so `field.form.ts` and `page.form.ts` are unchanged.
- **The file surface differs from the card.** `block-config.ts` and
`PageBlockInspector.tsx` live under `metadata-admin/` (previews and
inspectors), and `ObjectSettingsPanel.tsx` under `studio-design/`. The
fix also touches `PagePreview.tsx`, `PageBlockCanvas.tsx`,
`createDerive.ts`, `registry.ts`, `widgets.tsx` (one export) and
`DatasetDefaultInspector.tsx` (the shared filter field moved out). New
files are `FilterConditionField.tsx` and `HighlightFieldsEditor.tsx`.
- **Observed, not fixed (pre-existing).** In the page designer canvas,
an authored `record:details` that names its fields draws a location
field as "[Object]" and a select value raw. This is the same output the
existing full-create seed already produces on `main`. Another is that
`PageBlockInspector`'s JSON fields render a `Label` that is not
associated with their textarea.
## Verification
- Screenshots are on the never-merged branch
[`claude/issue-27054-evidence`](https://github.com/objectstack-ai/objectstack/tree/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
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No spec change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; eslint over the changed files reports no new
error.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the maintainer brief
and quoted rulings excepted).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Q4EjjRbbbwV5u1Vx4Ge7TT)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
feat(app-shell): Studio authors what the record page reads about related lists, highlight fields, and new record pages
8
+
9
+
Clause-②: no
10
+
Metadata-protocol: none
11
+
12
+
-**Field inspector, lookup and master-detail fields.** A "Related list on the parent record" section with a three-state control (Show / Don't show / Primary (in body)) writes `relatedList`: Don't show writes `false`, Primary writes `'primary'`, and Show leaves the key absent, the spec's own "true or absent = shown". Below it, `relatedListFilter` is edited with the same filter builder the dataset designer and the list toolbar use, instead of being absent from the inspector. A stored filter the builder cannot hold (nested, OR) is kept as stored and named.
13
+
-**Related-list block inspector.** The `record:related_list` block exposes `columns` (the child object's fields), `sort` (field and order rows), `filter` (the list toolbar's filter builder), `limit`, `showViewAll` (shown on when unset, the spec default) and `add`, beside the object, relationship field and title. A stored value in the other arm of a union, such as a string `sort` or `columns` given as saved-view column objects, is shown as JSON rather than as an empty or garbled list. The limit hint now reads 5, the renderer's own default.
14
+
-**Object settings.**`highlightFields` is an ordered list: drag a row or use the move buttons to reorder, add fields up to six, and see the highlight strip preview in the picked order. Removing the last field removes the key.
15
+
-**New record pages.** A record page created in the metadata editor now starts `kind: 'slotted'` with no slots, which is the synthesized default page, and it keeps following the object. The create form's kind follows the page type until the author picks one. Choosing `full` at create still copies the synthesized regions. In the page designer, a slotted record page says it is the default page and offers "Convert to full page", which copies the regions it renders, switches the kind to `full` and drops `slots`. An empty slotted page now opens on the slot editor instead of a blank regions canvas.
@@ -185,22 +189,34 @@ export function registerBuiltinAnchors(): void {
185
189
title: 'Kind',
186
190
enum: ['full','slotted'],
187
191
default: 'full',
188
-
description: 'full = the whole page; slotted = override only named slots of the default.',
192
+
description: 'A record page starts slotted: no slot is overridden, so it shows the default record page and keeps up with the object. Choose full to own the whole layout, starting from a copy of the default.',
0 commit comments