Repository navigation
Commit 829fcf2
feat(app-shell,i18n): Studio orders an object's related lists, and a field group can place a related table (#27303)
Fixes #27054
Clause-②: no
Metadata-protocol: none
Round 2 of the card, its fifth and last item: Studio authors
`relatedLists` (the key #27069 shipped in PR #27215) as a drag-to-order
list of the object's inbound relationships, and a field group can place
a related table (`fieldGroups[].relatedList`). The other four items
landed in PR #27163.
## 维护者速读
- **改了什么**:Studio
对象「设置」页新增「关联列表顺序(relatedLists)」:列出所有指向本对象的查找/主从关系,可拖拽或用上下按钮排序,未列出的关系以灰色「按默认顺序」尾段显示且不可拖;下方预览记录页的最终顺序(正文表格
/ 侧栏卡片)。表单布局设计器的分组属性新增「在此放置关联表」选择器,写
`fieldGroups[].relatedList`;放置了关联表的分组在设计器里显示为该表,不再接受拖入字段。
- **为什么改**:#27069 落地了 `relatedLists` 与分组 `relatedList` 两个键,但 Studio
没有控件,作者只能手写,而且手写的错名会被 `defineStack` 拒绝。
- **风险与回滚**:只动 Studio(app-shell)与 studio 文案包;不改
spec、不碰记录页渲染。`deriveRelatedLists` 只加了一个记录页从不传的可选项
`includeHidden`,以及把条目匹配抽成一个导出函数(行为不变,#27069 的顺序 pin 照绿)。回滚 = revert 本
PR。
-
**先看**:`ui/packages/app-shell/src/views/studio-design/RelatedListsEditor.tsx`(写入前校验
`write`)、`relatedListVocabulary.ts`(词表 = `deriveRelatedLists` 加
`includeHidden`,不另写关系枚举)、`ObjectGroupInspector.tsx` 的
`RelatedTablePlacement`。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/studio-design/ObjectSettingsPanel.relatedListsOrder-27054.test.tsx`,预期
5 个用例全绿;截图见下方证据分支。
- **需要你做的**:无,席位落地。
## What changes
1. **Object settings, `relatedLists`** (`RelatedListsEditor`, mounted in
the Semantic roles card under highlight fields).
- The vocabulary is the record page's own derivation:
`deriveRelatedLists` asked with a new `includeHidden` option, so the
editor offers exactly what the record page reads, plus the FKs a
`relatedList: false` hides (an entry may still name those: `defineStack`
and `packages/lint` `validate-related-list-refs` count them, and all
three skip the audit FKs). No second relationship enumeration exists.
- Rows are the stored entries in order: drag one, or use its up / down
buttons (the keyboard path). Removing the last one removes the key.
- Every relationship no entry names trails, greyed, with `aria-disabled`
and no drag, in the derived order (owned `master_detail` children, then
lookups). The add picker offers exactly that tail. Each item is spelled
the child name, or `child.field` when that child points here through
more than one FK.
- Order only, never visibility: a hidden FK is listed and badged
`hidden`, and the switch stays on the field. A primary list is badged
`in body`.
- **Pre-write check.** Every write runs through
`acceptRelatedListEntries`. It keeps, in order and once, each entry that
names at least one inbound relationship. A stored entry that names none
keeps its row, shown in red with a sentence saying the next change
leaves it out. The add picker goes through the same check.
- Nothing is written while the object list is still loading
(`getTypeStatus('object')` is not `ready`). At that point a stored entry
naming another object's FK cannot be told from a misspelling, so a write
would drop it.
- A preview draws the record page's resulting order through
`deriveRelatedLists` itself, split into body tables and side cards (the
order applies within each).
2. **Field groups, `relatedList`** (`ObjectGroupInspector` →
`RelatedTablePlacement`). A "Place a related table here" picker over the
same vocabulary, checked by the same function before it writes. The
picker is withheld while the group still holds fields, because the
object parse refuses a field whose group is a table group. A
relationship another group already places is not offered again. Clearing
the picker removes the key.
3. **Form layout designer** (`ObjectFormDesigner`). A group that carries
`relatedList` is drawn in its place as a "Related table" card. It is no
field container: its droppable is disabled, and it is left out of the
container map that drags, keyboard steps and the commit read. Without
this the new picker would create a group that invites a drop the save
then refuses.
4. **Shared rows** (`OrderedRowList`). The drag + up / down + remove
rows are extracted from `HighlightFieldsEditor`, which now uses them
too, so the code is not copied. The round-1 highlight pins pass
unchanged.
5. `deriveRelatedLists.ts`: `relatedListEntryNames(entry, list)` is now
the one reading of an entry, shared by `resolveRelatedListOrder` and the
editors, and the optional `includeHidden` flags hidden FKs `isHidden`.
The record page passes neither, so its output is byte-identical.
## Tests (head `5c2b715589`)
- New pins `ObjectSettingsPanel.relatedListsOrder-27054.test.tsx` (5)
and `ObjectGroupInspector.relatedListPlacement-27054.test.tsx` (5).
These cover the three acceptance pins:
- a drag writes `['event.related_to', 'task']`, and `deriveRelatedLists`
over the real fixture registry changes its order to match;
- a stored `'nope'` / `'event.nope'` never reaches a written value;
- the unlisted relationships trail in the derived order, greyed and not
draggable.
The files also cover the add picker, removing the last entry, writing
nothing while loading, group placement, the withheld picker,
de-duplication across groups, and the designer's table card.
- Focused re-run on the final head: ObjectSettingsPanel*,
ObjectGroupInspector*, ObjectFormDesigner*, deriveRelatedLists* gave
`Test Files 21 passed (21) / Tests 181 passed (181)`.
- Wide run (before the merge of `0d45c0e71f`):
`packages/app-shell/src/views/studio-design/`,
`packages/app-shell/src/utils/__tests__/`,
`packages/app-shell/src/views/metadata-admin/inspectors/` and
`packages/i18n/` gave `Test Files 567 passed | 1 skipped (568) / Tests
6360 passed | 22 skipped`.
- `pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run
type-check`: exit 0 on the final head. `--listFilesOnly` shows both new
pins in the app-shell test program.
- **Reverse verification**, after the fix was committed, through
`scripts/ablation-replace.mjs`. The anchor `const accepted =
acceptRelatedListEntries(next, relationships);` was replaced with `const
accepted = [...next];`, and the tool reported anchor 1 → 0, replacement
0 → 1, blob `eb202671ad73` → `6d9ac4f61fbd`. Result: `Tests 1 failed | 4
passed (5)`, and the one red is exactly "a name that matches no inbound
relationship never enters a written value" (`expected [ Array(4) ] to
deeply equal [ 'task', 'note' ]`). Restore: blob == HEAD `eb202671ad73`,
`git diff HEAD` empty, `grep -c` after: anchor 1, mutant 0. The pin
imports `src` by relative path, so there is no dist leg.
- **Live**: showcase `--fresh` backend and the Console dev server, both
on private ports, on this branch. In a scratch writable package
(Customer + Ticket / Contract (master-detail) / Visit (two FKs) / Survey
(`relatedList: false`)) I authored an order and placed a table. The
publish door accepted the result: `relatedLists:
['desk_visit.referred_by', 'desk_ticket']`, `fieldGroups[0].relatedList:
'desk_contract'`. A stored `'nope'` showed as unmatched, and one move-up
wrote `['desk_ticket']`.
Screenshots, on the never-merged branch `claude/issue-27054-r2-evidence`
at `20360a7f0f`:
-
https://github.com/objectstack-ai/objectstack/blob/20360a7f0f51501cc574dddf0ff0c33b198ff626/r2-1-related-lists-editor.jpg
(the ordered list, the greyed tail with a hidden FK, the record-page
preview)
-
https://github.com/objectstack-ai/objectstack/blob/20360a7f0f51501cc574dddf0ff0c33b198ff626/r2-2-group-related-table.jpg
(group inspector picker, the designer's related-table card)
-
https://github.com/objectstack-ai/objectstack/blob/20360a7f0f51501cc574dddf0ff0c33b198ff626/r2-3-unmatched-entry.jpg
(a stored entry naming no relationship)
## Gates (head `5c2b715589`)
- I ran `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, which derived 34 families from 14 paths
against merge base `0d45c0e71`. I ran each one and captured its exit
code before any pipe. 33 exited 0.
- `pnpm --dir ui exec vitest run scripts/` exited 1 for host-only
reasons:
- two `select-affected-tests-wiring` cases fail on `mapfile: command not
found` (macOS bash 3.2);
- the rest were `Test timed out in 15000ms` under batch load.
Re-run alone, `check-i18n-call-site-keys`, `check-vi-mock-inherit`,
`markdown-test-inputs` and `check-hand-rolled-comment-mask` gave `Tests
259 passed (259)`. The `check-i18n-dead-keys` real-tree case stays NOT
MEASURED locally (it timed out). The gate itself (`pnpm --dir ui run
check:i18n-dead-keys`) exits 0, and none of the new keys is listed.
- `--ran` reconciliation: `Run reconciliation — 34 derived, 34 run, 0
NOT-MEASURED, 0 UNRUN`.
- Extra, outside the derived list, all exit 0: `pnpm
check:changeset-fixed` (roster under `.changeset`), `pnpm
check:i18n-stale-fill`, and `pnpm --dir ui run` with
`check:new-line-citations` (0 new), `check:i18n-keys`,
`check:i18n-drift` (18 keys added, 0 en values changed) and
`check:i18n-dead-keys`.
- `eslint --no-inline-config` over the changed files: 0 new findings.
The one error, `react-hooks/static-components` in
`ObjectSettingsPanel.tsx`, and the warnings in `deriveRelatedLists.ts` /
`ObjectFormDesigner.tsx` are on `main` already.
- Every locked command ran DECLARED UNLOCKED: this host has no usable
`flock`, so `os-verify-lock.sh` never took the shared lock.
- CI families: read from this PR's check-runs, not re-run locally.
## Acceptance notes
- **The placement has no runtime reader yet.** No reader of
`fieldGroups[].relatedList` is on `main`; the record page that draws it
is #27055, which is in flight (#27055 remains open and is not addressed
here). The key is spec-declared and passes the publish door, so
authoring it now is what the ruling asked for.
- **The publish door judges an unknown child name only on a whole
collection.** In the live run, `relatedLists: ['nope', 'desk_ticket']`
published from a package-scoped Studio publish with no refusal.
`validate-related-list-refs` documents why in its header: a named child
not in the stack is refused only when `wholeCollection` is set, and
stays unjudged under a package closure. The `.describe()` on
`relatedLists` says such an entry is refused. The editor's pre-write
check closes this for Studio. Raised in the report for the seat to
judge, not filed from here.
- The metadata-admin object designer (`ObjectFormCanvas`) also edits
`fieldGroups` and has no placement picker. The ruling named Studio's
field-group editor, so it is left as it is.
- The served object catalog is the vocabulary, with this object's draft
put in its place. A child object that exists only as an unpublished
draft in the same package is not offered until it is published, which
matches what the record page can draw.
---
_Generated by [Claude
Code](https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff)_
---------
Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2655ebd commit 829fcf2
15 files changed
Lines changed: 1075 additions & 82 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console/__tests__
- utils
- views/studio-design
- i18n/src/namespaces/studio
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
103 | 107 | | |
104 | 108 | | |
105 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
143 | 149 | | |
144 | 150 | | |
145 | 151 | | |
| |||
269 | 275 | | |
270 | 276 | | |
271 | 277 | | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | 278 | | |
276 | | - | |
277 | | - | |
| 279 | + | |
278 | 280 | | |
279 | 281 | | |
280 | 282 | | |
281 | 283 | | |
282 | 284 | | |
283 | 285 | | |
284 | 286 | | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
285 | 302 | | |
286 | 303 | | |
287 | 304 | | |
| |||
311 | 328 | | |
312 | 329 | | |
313 | 330 | | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
314 | 339 | | |
315 | 340 | | |
316 | 341 | | |
| |||
364 | 389 | | |
365 | 390 | | |
366 | 391 | | |
367 | | - | |
| 392 | + | |
| 393 | + | |
368 | 394 | | |
369 | 395 | | |
370 | 396 | | |
| |||
373 | 399 | | |
374 | 400 | | |
375 | 401 | | |
| 402 | + | |
376 | 403 | | |
377 | 404 | | |
378 | 405 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| |||
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
28 | | - | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| |||
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
64 | | - | |
65 | | - | |
66 | 65 | | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | 66 | | |
76 | 67 | | |
77 | 68 | | |
78 | 69 | | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
135 | 85 | | |
136 | 86 | | |
137 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
442 | 442 | | |
443 | 443 | | |
444 | 444 | | |
| 445 | + | |
445 | 446 | | |
446 | 447 | | |
447 | 448 | | |
| |||
461 | 462 | | |
462 | 463 | | |
463 | 464 | | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
464 | 471 | | |
465 | 472 | | |
466 | | - | |
| 473 | + | |
467 | 474 | | |
468 | 475 | | |
469 | 476 | | |
| |||
543 | 550 | | |
544 | 551 | | |
545 | 552 | | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
546 | 561 | | |
547 | 562 | | |
548 | 563 | | |
| |||
576 | 591 | | |
577 | 592 | | |
578 | 593 | | |
| 594 | + | |
579 | 595 | | |
580 | 596 | | |
581 | 597 | | |
| |||
627 | 643 | | |
628 | 644 | | |
629 | 645 | | |
630 | | - | |
631 | | - | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
632 | 658 | | |
633 | 659 | | |
634 | 660 | | |
| |||
857 | 883 | | |
858 | 884 | | |
859 | 885 | | |
860 | | - | |
| 886 | + | |
861 | 887 | | |
862 | 888 | | |
863 | 889 | | |
| |||
882 | 908 | | |
883 | 909 | | |
884 | 910 | | |
| 911 | + | |
885 | 912 | | |
886 | 913 | | |
887 | 914 | | |
| |||
0 commit comments