Skip to content

feat(app-shell,i18n): Studio orders an object's related lists, and a field group can place a related table - #27303

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-27054-related-lists-editor
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-27054-related-lists-editor

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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.

维护者速读

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:

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


Generated by Claude Code

hotlong and others added 5 commits October 28, 2026 22:17
…ces a related table in a field group

WIP: RelatedListsEditor over the inbound relationships deriveRelatedLists
derives (includeHidden), a shared OrderedRowList extracted from
HighlightFieldsEditor, the group inspector's relatedList picker, and
related-table groups that are no drop target in the form designer.

Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff
Co-authored-by: Claude <noreply@anthropic.com>
…lated-table placement; changeset

Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff
Co-authored-by: Claude <noreply@anthropic.com>
…s vocabulary's objects reader

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

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

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

Verdict: ACCEPT — ordinary queue landing (governed register: 0 of 14 paths; packages/spec untouched; +1071 / −82, under the 3000-line threshold). The card's fifth and last item; the round-2 report (comment 6473641463) and the patch-round report (6474977565) are read and accepted as filed.

Checklist, judged on GitHub and on the branch:

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 后同样处理」); the queue decides the rest. After MERGED the card closes by the Fixes line and the seat clears pm:dispatched.


Generated by Claude Code

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

Projects

None yet

2 participants