Skip to content

feat(plugin-detail)!: a tab row under the record header; an open panel face is the active tab - #28394

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-hh88jx
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-hh88jx

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

domain:ui#2 · os-tesla · session_01L9qibzGCEznLfiQm2T5icF

Requested by Tesla · project thread

Part of #27524

Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: none — the narrowing retires a ui component export and an i18n key, not an author-writable metadata surface; git grep finds neither ArrangedRelatedTab nor detail.relatedEnlargedBack under skills/, content/ or docs/.

维护者速读

  • 改了什么:记录页头部底边多了一行常驻标签:「概览」、动态,再按读者的排列顺序列出每个关联面板,各带条数。能放几个由这一行自己的宽度算(窗口、侧栏、分屏拖宽都会重算),放不下的收进「+N ▾」,隐藏面板排在下拉末尾;当前标签永远在行上。放大视图就是当前标签:整宽列表画在标签行下面,头部不动,地址仍是 ?related=对象.字段(动态是 ?related=timeline),「概览」或 Esc 返回。
  • 为什么改:Record page v4: same-shaped related panels with no collapse, a persistent tab row under the header (the enlarged state is the active tab), Activity as the first panel and a tab, one top bar on every face, tiers by container width #27524 的 Target 8、9(画布 60 定稿),四个串行 PR 中的第三个;顶栏在 PR ④。
  • 风险与回滚:只动 ui 包(plugin-detail、app-shell、十个语言包)和文档。开页时现在给所有关联面板取条数,包括隐藏的和 ⊟ 关掉时的(标签要显示条数),同一个计数缓存去重。退役了 ArrangedRelatedTab 导出和 detail.relatedEnlargedBack 一个键,只有控制台自己用过。回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-detail/src/RecordTabRow.tsx 与 synth/recordTabRow.ts(标签行和放几个的规则);RecordPageColumns.tsx(标签从哪来、当前标签面);app-shell RecordDetailView.tsx 的 openEnlargedRelated(标签间切换替换历史)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-detail/src/synth/__tests__/recordTabRow-27524.test.ts packages/plugin-detail/src/__tests__/RecordTabRow-27524.test.tsx packages/plugin-detail/src/__tests__/RecordPageColumns.tabRow-27524.test.tsx packages/app-shell/src/views/RecordDetailView.recordTabRowUrl-27524.test.tsx,预期 4 个文件、26 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

Record page v4, Target items 8 and 9, PR ③ of the four serial PRs for #27524. It is cut from main after PR ② (#28116, 3d2da4fd1a) and leaves main coherent on its own. The top bar (Target 7) is PR ④.

Target item Where
1, 2, 3, 10, 11, 12 PR ① (merged)
4, 5, 6 PR ② #28116 (merged)
8 the tab row this PR
9 enlarged = the active tab this PR
7 the top bar (and ⊟ moving to it) PR ④

8. The tab row (RecordTabRow.tsx, synth/recordTabRow.ts, RecordPageColumns.tsx). A row at the bottom edge of the record header, on every face the record is drawn in: the page, the split pane, the overlay drawer, the maximized pane and a phone.

  • The tabs are 「概览」, then 动态, then one tab per related panel in record.related.arrangement order (resolveRelatedArrangement, the order the right column draws). Each tab carries its count: 动态 the stream's size, a related tab its list's count.
  • The counts are one set. The page asks useRelatedCounts once for every panel, hidden ones included, and hands the same counts to the right column (RelatedCardStack takes a new optional counts prop and then asks for none itself). 动态's count is the stream's own, reported by the record:activity block through RecordTimelinePanel's new count / onCount props in the peek face, and through RecordTimelineCountContext on the 动态 tab while its chip is 全部.
  • How many tabs show is computed from the row's own width. A ResizeObserver on the row re-measures on every resize (window, sidebar, a pane dragged wider), against a hidden measuring copy of every tab and of the widest 「+N ▾」. The rule is the pure layoutRecordTabRow:
    • every tab fits and no panel is hidden: all of them, no dropdown;
    • otherwise the budget is the row less the trigger and one gap. 「概览」 and the active tab are reserved, then tabs are taken in order until the first that does not fit;
    • 「概览」 and at least one more tab always show;
    • the active tab is always on the row. When it would have folded it takes the last slot, and the tab it displaced folds.
  • 「+N ▾」 is a dropdown listing the folded tabs with their counts, then a 「已隐藏」 heading and the hidden panels' tabs. Choosing one opens it, and it becomes the active tab.
  • The row is a tablist named 「记录页签」 and a single tab stop: ← → Home End move between the shown tabs, Enter or Space opens one.
  • A body table opened in place (its ⤢ in the details) gets a tab of its own while it is open.
  • No row is drawn where the view gives no way to open a face (onEnlargeRelated absent), or where there is nothing but 「概览」.

9. The enlarged face is the active tab (RecordPageColumns.tsx, app-shell RecordDetailView.tsx).

  • Choosing a tab, a panel's header or 「查看全部 N 条」 draws that panel's whole face below the tab row at the page's full width. The header stays as it is: no compaction, no modal, no drill-down, no path in the top bar.
  • The face is a role="tabpanel". The ‹ back line and its own h2 are gone, because the tab names it. Esc (window capture, as in PR ②) and 「概览」 return to the page.
  • The URL stays ?related=object.field, ?related=timeline for 动态. Leaving 「概览」 pushes one history entry. Moving from tab to tab now replaces that entry, keeping its state, so one Back, Esc or 「概览」 always returns to the page. A deep link to a tab opens it as the active tab, and 「概览」 then replaces the address, since there is nothing to pop.
  • In the pane the face fills the pane body under the same row (no change to recordDrawerStack.tsx; the pane draws the same view).

Retired.

  • ArrangedRelatedTab (plugin-detail entry): it laid the reader's arrangement over the 关联 tab of the tabbed tier that PR ② retired, and only the console's record view imported it. Its full-face chrome path in RelatedList (arrangedFull) and its scope branch in the record:related-list renderer go with it. A page still drawn as its strip is drawn as written.
  • detail.relatedEnlargedBack leaves all ten packs.

Arrives in ten packs: detail.recordTabs, detail.recordTabOverview (概览 · Overview · 概要 · 개요 · Übersicht · Aperçu · Resumen · Visão geral · Обзор · نظرة عامة), detail.recordTabsMore (with each pack's plural forms) and detail.recordTabsHidden.

Declared. Clause-②: yes (narrowing) (the claim said no; measured on the diff, the retired export and the retired key narrow two published surfaces). Two changesets:

  • 27524-record-page-tab-row.md (plugin-detail, app-shell): ArrangedRelatedTab under not-required (runtime-export-retired …) with its FROM → TO line.
  • 27524-record-page-tab-row-i18n.md (i18n): TranslationKeys loses one key, under not-required (runtime-interface-only …#TranslationKeys), as PR ① and ② did.

Reads at open. A record open now counts every related panel, the hidden ones and those behind ⊟ off included (before, only the panels on screen), because their tabs show the counts. The requests are the same RelatedCountStore count requests, deduplicated with the panels' own. Nothing else a record open fetches changes. (Seat 2's #27627 / #28321 touches the data adapter; this PR does not.)

File surface beyond the order. RelatedList.tsx (the dead arrangedFull path), renderers/record-related-list.tsx (the retired scope branch), index.tsx (the export), recordTimelineStream.ts and renderers/record-activity.tsx (动态's count, for its tab), RelatedCardStack.tsx and RecordTimelinePanel.tsx (shared counts), the plugin-detail README and content/docs/ui/pages.mdx. navigation-overlay.tsx (#28294), the top bar (④) and #27545's split / overlay / maximize mechanics are untouched.

Tests

New:

  • plugin-detail/src/synth/__tests__/recordTabRow-27524.test.ts (6): the layout rule at a wide, a medium and a narrow width; the active tab displacing the last tab, fitting beside them, or keeping its place; 「概览」 and the active tab at the narrowest; hidden panels forcing the dropdown and taking the trigger's width.
  • plugin-detail/src/__tests__/RecordTabRow-27524.test.tsx (7), with given widths and a stub ResizeObserver: the shown count follows the width and is recomputed on resize; the active tab is always on the row; the count chips; 「+N ▾」 lists the folded tabs with counts, then the hidden ones, and choosing one selects it; a hidden panel makes the dropdown on a wide row; clicking; the keyboard (one tab stop, ← → Home End).
  • plugin-detail/src/__tests__/RecordPageColumns.tabRow-27524.test.tsx (10), the real RecordPageColumns: the tabs in arrangement order with hidden panels in the dropdown; counts for every panel from one request each (hidden ones included) and 动态's from the stream; selecting a tab and 「概览」; no row without a way to open; the stack tier; the enlarged face keeps the header, is a tabpanel and draws no back line or title; the 动态 face; a body table's tab; Esc.
  • app-shell/src/views/RecordDetailView.recordTabRowUrl-27524.test.tsx (3), a real RecordDetailView on the browser's own history: a tab pushes one entry and tab to tab replaces it (three tabs visited, one entry), 「概览」 returns; Esc and the browser's Back return in one step at 720; a deep link opens its tab and 「概览」 replaces the address without growing history.

Rewritten to the new anatomy:

  • RecordDetailView.recordPageTiers-26678 (6): the page's only tablist is its tab row on every tier and embedded (overview, timeline, showcase_task.project, showcase_note.project), 「概览」 active; ?related=timeline makes 动态 the active tab.
  • RelatedCardStack-27055: the enlarged face's back-button case reads 「概览」 and Esc; the ArrangedRelatedTab cases leave with the component.
  • RecordPageColumns.enlargedEscapeInPane-27524: Esc is pressed on the face, which no longer draws a back button to focus.
  • RelatedPanel.header-27524: the case for the arranged full face (the retired tabbed tier's 关联 tab) leaves with that face.
Ablations (each through node scripts/ablation-replace.mjs, restore proven blob == HEAD)
  • A1, the active tab's reservation removed from layoutRecordTabRow (chosen.add(active)): Tests 2 failed | 11 passed (13), the rule's and the drawn row's "active tab always shown" cases.
  • A2, hidden panels dropped from the page's count targets: Tests 1 failed | 9 passed (10), "each panel tab carries its count — the hidden ones counted too".
  • A3, openEnlargedRelated pushing on every tab change (the replace branch removed): Tests 2 failed | 1 passed (3), the one-entry history case and the Esc / Back case.

Acceptance notes

  • Target 8: the row is on every face (page, pane, drawer, phone), 「概览」 then 动态 then the panels in arrangement order with counts; the count of shown tabs comes from the row's width with a ResizeObserver; the rest in 「+N ▾」 with counts; the active tab always shown, in the last slot when it would fold; 「概览」 plus at least one more always; hidden panels at the dropdown's end.
  • Target 9: the enlarged state is the active tab, the full-width list under the row; ?related=object.field / ?related=timeline; Esc or 「概览」 returns; not a modal, not a drill-down, no top-bar path, the header not compacted; in the pane it fills the pane body.
  • Screenshots on examples/app-crm, not HotCRM, as in ① and ②. Ten shots *.png in /mnt/project-files/ui-seat-2/r2026-10-31/out/27524-pr3-shots/ (project files, not a repository branch), zh, through the console's dev server on this branch:
    • page-1440 and page-1200: six tabs, no dropdown (概览 · 动态 · 活动列表 2 · 联系人列表 1 · 线索列表 1 · 商机列表 4).
    • page-390: 概览 · 动态 · 活动列表 · 联系人列表, then 「+2」; page-390-more-open: the dropdown lists 线索列表 1 and 商机列表 4; page-390-folded-active: 商机列表 chosen from it takes the last slot (联系人列表 folds, still 「+2」) and its list draws below.
    • page-1440-enlarged (联系人列表 active), page-1200-enlarged (商机列表 active, every column), page-1440-timeline (?related=timeline, the full stream with its chips).
    • pane-720 and pane-720-enlarged: the split pane beside the list (row 623 px wide), all six tabs; switching tabs replaced the URL, Esc returned to 「概览」 with the pane still open, and Back then closed the pane.
  • Proxy preview review against the v4 boards at a7825499aa (RecordPageV4Enlarged, RecordDrawerV4Enlarged, and the rows on RecordPageV4Panels / TwoCol / RecordDrawerV4Peek):
    • Matches: the row's place (under the highlights, above the body), height 36 px, 13 px labels, the active tab's primary underline, the 10.5 px count chip, 「+N ▾」 at the row's end, no title or back line on the enlarged face, the header unchanged above it.
    • Differs, noted and not changed here: the enlarged list keeps RelatedList's own card header (title, count, 新建) and its 查看全部 foot, where the board draws a bare toolbar (filter, 全部 / 我的 / 逾期, 新建, 导出) over a table filling the rest of the height. That toolbar is not something the related list has today.
    • On a deep link to another tab (page-1440-enlarged), 动态's tab has no count until the stream is drawn once: its count is the stream's own size, which only the stream reads.
  • The 动态 count reads 0 in the shots because app-crm's Acme Corp has no activity; the chip is drawn for a known 0.

Known gaps

  • The enlarged related list keeps RelatedList's own card header (title, count, 新建) and its 查看全部 foot. The v4 board (RecordPageV4Enlarged / RecordDrawerV4Enlarged at a7825499aa) draws a bare toolbar there (filter, 全部 / 我的 / 逾期, 新建, 导出) over a table that fills the rest of the height. The related list has no such toolbar today; this PR does not add one.
  • On a deep link to another tab, 动态's tab has no count until the stream is drawn once (its count is the stream's own size).

Verification

Tests, type-check and the derived gates on 572d6b1

Head 572d6b18fb, change set vs the merge base a878ff6c26: 36 paths, 1,816 (+1,478 / −338) changed lines.

  • Under the verify lock: pnpm --dir ui exec vitest run packages/plugin-detail packages/i18n plus 226 test files across ui/ that name a module, test id or key this PR changes (RecordDetailView, RecordPageColumns, RelatedCardStack, ArrangedRelatedTab, relatedEnlargedBack, data-related-enlarged, RECORD_RELATED_ENLARGE, record-tab; derived by git grep) → Test Files 672 passed | 1 skipped (673), Tests 6395 passed | 31 skipped (6426), VERDICT command-exit 0 (on 7a0555e2; 572d6b18fb only adds objectName to one pin's record context, and that file then ran Tests 10 passed (10)). A first broad run found the six recordPageTiers-26678 cases (they asserted no tablist) and the retired full-face case in RelatedPanel.header-27524, both rewritten above.
  • type-check (tsc --noEmit + tsc -p tsconfig.test.json) of @object-ui/i18n, @object-ui/plugin-detail and @object-ui/app-shell, under the lock: each exit 0 on 572d6b18fb. The first plugin-detail run was exit 2 on the new pin's missing objectName (TS2741), fixed in 572d6b18fb.
  • eslint on the 31 added or modified ts/tsx files under ui/: 0 errors. pnpm --dir ui run check:i18n-drift: exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 66 commands, the same list on c1734bc8 and on 572d6b18fb. Each was run on 572d6b18fb with its exit code captured before any pipe (the three heavy ones under the lock); all 66 are :: exit 0, node scripts/check-sdui-manifest.mjs among them. --ran reconcile: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN.
  • Dogfood (the title has !), once on 572d6b18fb under the lock: pnpm --filter @objectstack/dogfood test → VERDICT command-exit 0, Test Files 361 passed | 3 skipped (364), Tests 2917 passed | 28 skipped (2945).
  • CI on 572d6b18fb: all 59 check-runs concluded success, or skipped by path filter or opt-in; Test UI (16/16), Type Check UI, Lint UI, Live E2E UI and Build & E2E UI among them.
  • main has moved 8 commits past the merge base; git merge-tree --write-tree origin/main HEAD is clean, and no merge was pushed.
  • Not run locally, CI's own: the whole ui/ suite (Test UI; app-shell alone is 1,379 test files), the other type-check lanes, the repo-wide scans.
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:role-word :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --dir ui run check:i18n-drift :: exit 0

Generated by Claude Code

…anel face is the active tab

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzGCEznLfiQm2T5icF
…ace; changesets and docs

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzGCEznLfiQm2T5icF
… drives

The tier pins read the page's one tablist as its tab row; a new pin walks
the row through the browser's history: one pushed entry, tab to tab
replaces it, and 概览, Esc or Back return in one step. The retired full
related face's header pin leaves with the face.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzGCEznLfiQm2T5icF
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 572d6b18fb5b3e2ab159f75b199b045af59df237 · 2026-10-31T11:14Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 572d6b18fb5b3e2ab159f75b199b045af59df237
Local-runs: none
Scope: ui/packages/plugin-detail/src/synth/recordTabRow.ts (layoutRecordTabRow), src/RecordTabRow.tsx, src/RecordPageColumns.tsx (the tabs, the counts, the active-tab face), src/index.tsx (the ArrangedRelatedTab export retired), src/ArrangedRelatedTab.tsx, src/RelatedList.tsx (arrangedFull path), src/renderers/record-related-list.tsx (the scope branch), src/RelatedCardStack.tsx (counts), src/RecordTimelinePanel.tsx and recordTimelineStream.ts and renderers/record-activity.tsx (动态's count), src/useRelatedArrangement.ts and synth/relatedArrangement.ts, ui/packages/app-shell/src/views/RecordDetailView.tsx (openEnlargedRelated replace branch, the ArrangedRelatedTab use removed), the ten i18n packs (detail.recordTabs, recordTabOverview, recordTabsMore with plurals, recordTabsHidden; relatedEnlargedBack retired), the plugin-detail README and content/docs/ui/pages.mdx, the four new and four rewritten test files, .changeset/27524-record-page-tab-row.md, .changeset/27524-record-page-tab-row-i18n.md

Inputs: card #27524 (record page v4; the seat's PR ③ claim 6540954535 for Targets 8 and 9 cut from main after PR ② #28116, its amendment 6542386083 (Clause-② re-measured yes (narrowing), the README and pages.mdx added to the surface, the preview gate of ruling 6520722066), the dev report 6542370280), PR #28394 (body with the target table, the layout rule, the retirements, the proxy preview review against the v4 boards at a782549 with its matches and the two noted differences, the file list of 36 files, the diff of 1816 lines against main read whole for every hand-written hunk, 5 commits from the merge-base), the 72 check-runs on this head (read 2026-10-31T11:18Z: all 72 runs completed, none failed, none cancelled, none in progress; success: Build & E2E UI, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Dogfood Regression Gate, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test Core (all 8), Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter; skipped: 18.x upgrade harness (opt-in), Auto Label, Build Core, Check PR Size, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), UI Published Artifacts), and the tree at this head for every reader of the two retired names outside ui/packages/plugin-detail (git grep -w for ArrangedRelatedTab and relatedEnlargedBack over the whole tree minus changelogs and changesets: none; the console's record view was the one importer and it is edited here), the en.ts keys before and after, and RecordDetailView.tsx on main (the ?related= push and pop of #27055). The head is mergeable and clean against today's main; RecordDetailView.tsx and the ten packs moved on main since the merge-base (#28392) in other hunks, and the seat's merge-tree read is clean.

① Derived judgments

  • Target 8 is a pure rule drawn by measurement - right. layoutRecordTabRow takes the measured widths, the row's width, the gap, the trigger's width, the active index and the count of always-folded tabs, and answers the shown and folded indexes: everything fits with no hidden panel means every tab and no trigger; otherwise the budget is the row less the trigger and one gap, 「概览」 and the active tab are reserved, tabs fill in order to the first that does not fit, 「概览」 plus at least one more always show, and a reserved active tab from further on lands in the last slot with the tab it displaced folding. The component measures with a ResizeObserver and a hidden copy of every tab and the widest trigger, so a window, sidebar or pane resize re-lays the row. Six rule cases and seven drawn-row cases pin it, and the ablation of the active-tab reservation reds both.
  • The counts are one set - right. The page asks useRelatedCounts once for every panel, hidden ones included, and hands the same counts to the right column, which then asks for none itself; 动态's count is the stream's own, reported by the record:activity block through the panel's count / onCount and the context on the 动态 tab. The read at open grows by the hidden panels' counts only, through the same deduplicated RelatedCountStore requests; nothing else a record open fetches changes, and the data adapter is feat(console): entering an app loads its metadata bundle once, and a record open reads no metadata #28321's, untouched here.
  • Target 9 keeps the URL contract and fixes the history - right. The face is a tabpanel under the row at full width with the header untouched; ?related=object.field and ?related=timeline stay the addresses, so a reload and a shared link still open the tab. Leaving 「概览」 pushes one entry as console record page v3: frameless detail sections, one merged timeline (discussion + activity + field changes), compact related cards, header 1 primary + 2 secondary + ⋯ — the synthesized default page, drawn from the object's roles; the dead BuildPageOptions hints go #27055 did; moving from tab to tab now replaces it, keeping its state, so Back, Esc and 「概览」 all return to the page in one step, and a deep link's 「概览」 replaces the address since nothing was pushed. Three cases on the browser's own history pin it, and the ablation of the replace branch reds two.
  • The retirements are what the declaration of record says - right. ArrangedRelatedTab laid the reader's arrangement over the 关联 tab of the tabbed tier PR ② retired, and the console's record view was its one importer; it leaves the entry with its arrangedFull chrome path and the renderer's scope branch, and a page still drawn as its strip is drawn as written. detail.relatedEnlargedBack was the back line the active tab replaces; it leaves all ten packs, and TranslationKeys narrows by one key under the runtime-interface-only marker as PR ① and ② did. The lane's grep finds no other reader of either name. Metadata-protocol: none holds: no spec path, nothing an author writes.
  • The accessibility contract is whole - right. The row is a tablist named by detail.recordTabs, one tab stop with arrow, Home and End movement and Enter or Space to open; the trigger carries detail.recordTabsMore with plural forms in every pack; the face is a tabpanel. The rewritten tier pins assert the page's only tablist is this row on every tier and embedded.
  • Check-runs as gate verdicts - read as above; every latest run per name on this head completed green or skipped (one Check Changeset run was cancelled and superseded by its green re-run two minutes later).

② Semver level

③ Boundary flags

  • The enlarged face keeps RelatedList's own card header and foot - the v4 board draws a bare toolbar (filter, 全部 / 我的 / 逾期, 新建, 导出) over a table filling the height; the related list has no such toolbar today. Noted in the preview review and the known gaps, not changed here; the seat's preview gate before ACCEPT is where that difference is ruled, and it is worth a line on PR ④ or a follow-up card so it is not lost.
  • 动态's count on a deep link - absent until the stream is drawn once, since only the stream reads its size. Known gap, recorded.
  • Hidden-panel counts at open - a small read increase per hidden panel, deduplicated; accepted as the price of showing counts on their tabs.
  • The seat lands - no Tier H path (no docs/adr, no skills/**, 1816 lines, under 3000), no human gate; no name approval owed (ui component and i18n keys, no spec key). The preview gate against the v4 boards (ruling 6520722066) is the seat's own step before ACCEPT.

④ Design judgment

  • Tried: a row where the active tab alone is wider than the budget (reserved regardless, with 「概览」; the rule never drops it); activeIndex out of range (read as 0); a page with only 「概览」 (no row drawn, by the component's guard); a tab change while the pane is maximized (same view, same replace). Keeping the rule pure and the measurement in the component is what makes the fold deterministic for a given width. Nothing to add.

Verdict reasoning. The tab row is a pure width rule drawn by measurement with the active tab always on the row, the counts come from one deduplicated set, the enlarged face is the active tab under the untouched header with the same URL addresses and a history that returns in one step, the two retirements have no reader left and are declared with their markers and the right level, the row and face carry their roles and names in every pack, the pins hold both ways including on real browser history, and every latest check on this head is green or skipped.

Implemented-by: claude/project-thread-hh88jx
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK

维护者速读

席位核对

ACCEPT · head 572d6b18fb5b3e2ab159f75b199b045af59df237 · 2026-10-31T11:19Z

  1. 形态: 36 files, +1478/−338; Part of #27524 (PR ③; ④ carries Fixes); 0 governed paths, 1816 lines ≤ 3000 (check-governed-merges --pr 28394).
  2. 前提实测: cut from main after ② feat(plugin-detail)!: one record page laid out by its own width; 动态 is the right column's first panel #28116 (3d2da4fd1a); surface per claim 6540954535 plus amendment 6542386083 (two prose files); navigation-overlay.tsx and the top bar untouched.
  3. 门禁 check-runs: 72 on the head, 55 success + 16 skipped + 1 cancelled (Check Changeset 10:47:30Z, superseded by its 10:49:21Z success), 0 failing; mergeable_state clean.
  4. 契约复审: Clause-②: yes (narrowing), Skills-docs: none — … line present and checked by the dev's git grep; PASS 6542695839 at CONTRACT_REVIEW_TIER on this head, nothing blocking.
  5. 预览闸门 (ruling 6520722066): seat read the shots against the v4 boards at a7825499aa — page 1440 (row with counts under the header), page 1440 enlarged (active tab, header untouched, ?related=), 390 folded-active (active tab takes the last slot, +2 ▾). Matches Targets 8 and 9. Ruled difference: the enlarged list keeps RelatedList's card header/foot where the board draws the filter, 全部/我的/逾期, 新建, 导出 toolbar; 动态 count absent on a deep link until the stream draws. Both stay on Record page v4: same-shaped related panels with no collapse, a persistent tab row under the header (the enlarged state is the active tab), Activity as the first panel and a tab, one top bar on every face, tiers by container width #27524 with ④ (the card stays open), no new card.
  6. 落地条款: the seat runs pr_ready and auto-merge once the maintainer gives the word in the seat thread.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 31, 2026 14:23
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit bf74607 Oct 31, 2026
75 of 76 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-hh88jx branch October 31, 2026 14:45
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

Development

Successfully merging this pull request may close these issues.

1 participant