Skip to content

refactor(app-shell): move ObjectView's view tab strip, plugin schema and header chrome into sibling hooks (slice 9) - #26412

Merged
objectstack-fleet[bot] merged 20 commits into
mainfrom
claude/project-thread-x004t9
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 20 commits into
mainfrom
claude/project-thread-x004t9

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Part of #25992 (slice 9 of the move-only split; slices 1 to 8 landed as #26152, #26233, #26310, #26336, #26353, #26374 and #26396, and the card stays open for the slices listed below).

Clause-②: no
Metadata-protocol: none

What moved

Base e4434f75 (slice 8 merged, latest main merged in). ui/packages/app-shell/src/views/ObjectView.tsx is 2554 lines there. Three concerns of ObjectViewInner move verbatim into custom hooks in three new sibling modules. In each, every input is passed as an argument, typed from its source (no any), the component destructures the returned values under their original names, no identifier is renamed and no context is added.

N: the view tab strip → objectViewTabBar.tsx

Lines 1131–1206 move into useObjectViewTabBar({ views, savedViews, objectDef, servedViews, viewLabel, unlabelledViewText, language, t, viewAccess, activeViewId, handleViewChange, isAdmin, handleAddView, handleRenameView, handleDeleteView, handlePinView, handleSetDefaultView, handleConfigView, setManageViewsOpen }). That covers the View Tabs docblock, useViewTabsFit(), useDisplayLocale(), viewTabItems, the <ViewTabBar> node (viewTabBar, objectui#12440) and its own-row face viewTabRow (objectui#13114). It returns viewTabItems, viewTabBar and viewTabRow. The view handlers it wires are typed from the ViewTabBarProps callbacks they are handed to.

P (plugin schema): the merged views and ObjectViewSchema → objectViewPluginSchema.ts

Lines 1975–2034 move into useObjectViewPluginSchema({ views, activeViewId, viewDraft, activeView, objectDef, onEdit, isAdmin, location, navigate, viewId, servedViews, viewLabel, unlabelledViewText, language }): mergedViews, the active view's userActions toolbar policy (objectui#11013) and objectViewSchema. It returns mergedViews and objectViewSchema. viewDraft is declared in the component as useState<Record<string, any> | null>, so it is typed as Partial<ReturnType<typeof buildViewTabs>[number]> | null: a partial of the tab it is merged over, which is the only way the block reads it.

P (header chrome): the create button, folded header actions and refused-view face → objectViewHeaderChrome.tsx

Lines 2036–2146 move into useObjectViewHeaderChrome({ objectCanCreate, createVisible, createDisabled, actions, t, listActions, toolbarActionsWithDeepLink, identityImportEnabled, objectCanImport, importVisible, importDisabled, setShowImport, listFavorite, activeViewHasPreference, handleResetView, isEnvironmentList, toolbarBar, viewAccess }): showCreate, listFabClearance, createButton, toolbarRowActions, primaryToolbarAction, importShown, foldedMenuBar, foldedHeaderActions (objectui#12705) and refusedViewFace (objectui#12112). It returns showCreate, listFabClearance, createButton, importShown, foldedHeaderActions and refusedViewFace. The block calls no React hook; it keeps the use name so it reads like its siblings and stays at the top level of the component.

Each hook is called where its block stood, so the order of React hooks in the component is unchanged.

Imports

Each import moves with its only readers. ViewTabBar, ViewTabItem, useViewTabsFit, useDisplayLocale and pickLocalized go to the tab bar module. Empty, EmptyTitle, EmptyDescription, Lock, ActionBarSchema, UIActionSchema and MOBILE_FAB_LIST_CLEARANCE go to the header chrome module. declaredPrimaryToolbarAction also goes there; it is still re-exported from ObjectView.tsx, line unchanged. Imports still read elsewhere in the component (viewTabLabel, viewRowId, VIEW_TYPE_ICONS, normalizeListViewSchema, Button, Plus, SchemaRenderer) are imported by the modules as well. Nothing new is exported from ObjectView.tsx, and the package entry is untouched. ObjectView.tsx goes from 2554 to 2374 lines.

Move-only proof: the multiset of non-blank lines of the base ObjectView.tsx is contained in new ObjectView.tsx plus the three modules. The only base lines missing are 11 import lines or import-list entries that lost a moved name: the @object-ui/plugin-view value and type imports, Empty / EmptyTitle / EmptyDescription in the @object-ui/components list, the lucide-react, @object-ui/types, @object-ui/i18n and ./objectViewChrome.js imports, and the declaredPrimaryToolbarAction and MOBILE_FAB_LIST_CLEARANCE entries. The trimmed lines replace them. The added lines are the trimmed imports and the three new imports, the module headers and imports, the hook signatures and parameter types, the return objects, and in ObjectView.tsx the three calls with their destructuring and two-line comments.

Test edits

None. No source reader of ObjectView.tsx names a moved line: the readers that mention ViewTabBar, userActions or normalizeListViewSchema read plugin-view/src/ObjectView.tsx, mock the module, or anchor on fullSchema inside renderListView, which stays.

Seam table rows this PR changes (measured on e4434f75)

The full table (regions A to Q) is in #26336.

# Concern Lines on e4434f75 Reads Read by Slice
N View tab items, ViewTabBar, its own-row face 1131–1206 A, C, F (savedViews, views, activeViewId, viewAccess), I (view handlers) O, Q 9, this PR
P (schema) Merged views, userActions toolbar policy, ObjectViewSchema 1975–2034 A, B (viewDraft), C, F Q 9, this PR
P (header chrome) Create button, folded header actions, refused-view face 2036–2146 A, C (listFavorite), D, E, F (viewAccess), G, H, J (actions) Q 9, this PR

Remaining slices (the last closes the card)

  • Slice 10 — Q: the JSX return's PageHeader, import wizard and NavigationOverlay blocks into sub-components with explicit props.
  • Slice 11 — B + I + O + G-columns (closes the card): the view-config and view-tab CRUD handlers, renderListView and the column flags only it reads. The writeRefusalCensus-11583 rows, the relayRungCensus-7559 read and the check-i18n-call-site-keys.test.ts deleteViewConfirm row are re-pointed (path and file-key changes only).

Verification

See the os-dev-report comment on the card for the full readings: app-shell type-check after rebuilding its dependency closure, every ObjectView.* test in views/ (the view tab, header fold, mobile FAB, refused view and favorites tests among them), the AppContent* tests, every other test that reads ObjectView.tsx as source, eslint on the 4 touched files, the derived dispatch gates, and a Console build.

Acceptance notes

  • eslint: 0 errors, no new warning. The four files report 113 warnings against 116 for the base ObjectView.tsx alone: three react-hooks/preserve-manual-memoization warnings are no longer raised, and the two exhaustive-deps warnings on handleViewChange only moved lines.

Changeset: .changeset/25992-objectview-tab-bar-header-chrome-split.md (patch, @object-ui/app-shell).


🤖 Generated with Claude Code

https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz

claude added 20 commits October 26, 2026 14:40
…tViewOptions.ts (slice 2)

The timeline/calendar/gantt/gallery/kanban ViewOptions, storedLegacyOptions,
isSameOptionsValue and dispatchViewPatches move verbatim out of
views/ObjectView.tsx into views/objectViewOptions.ts. ObjectView.tsx imports
them back and re-exports every name it exported before.

Test edits forced by the move: the four source scans that read the
ObjectView face (calendarBinding-7029, galleryBinding-7547, ganttBinding-7070,
titleFieldConvergence) now read ObjectView.tsx plus objectViewOptions.ts, and
the writeRefusalCensus-11583 LEDGER row for dispatchViewPatches is keyed by
its new file.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…ome.tsx (slice 3)

ObjectNotFoundState, the saved-view tab strip's viewport check
(VIEW_TABS_MIN_WIDTH, viewTabsFitNow, useViewTabsFit) and
keepInlineEditModeForTheSession move verbatim out of views/ObjectView.tsx
into views/objectViewChrome.tsx. ObjectView.tsx imports them back and
re-exports ObjectNotFoundState, the one name it exported. No test edits.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…unt into a sibling hook

Slice 4 of the move-only ObjectView split. The realtime subscription,
the reconnection conflict resolution and the footer record-count fetch
move verbatim from ObjectViewInner into useObjectViewLiveData in
views/objectViewLiveData.ts. The component calls the hook at the same
position (hook order unchanged) and passes its inputs explicitly; the
recordCount state stays in the component. No test edits.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…igation into sibling hooks

Slices 5 and 6 of the move-only ObjectView split, paired in one PR.

- The identity-import gate, its password-policy state and data source,
  and the managed-environment Users marker move verbatim into
  useObjectViewIdentity in views/objectViewIdentity.tsx.
- The record navigation overlay (detail navigation config, drawer
  recordId URL state, row click, expand to page, list record links)
  moves verbatim into useObjectViewRecordNavigation in
  views/objectViewRecordNavigation.ts. The URL equality filters and the
  user-filter selections between its two blocks stay in the component.

Inputs are passed explicitly; the component destructures the returned
values under their original names. No test edits.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…m their sources

activeView, objectDef and objects were written as `any` in the new
hook's parameter type. They now name where the component gets each
value: the element of buildViewTabs' result, and
ConsoleObjectViewProps['objects'] and its element. No new
no-explicit-any warning; the moved bodies are unchanged.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…a sibling hook

Slice 7 of the move-only ObjectView split. The environment list's
entitlements, the list actions and their localized toolbar copies, and
the create and import buttons' toolbar-scope predicates move verbatim
into useObjectViewToolbarAffordances in
views/objectViewToolbarAffordances.ts. The resolveAffordance verdict
memo stays in the component (the affordance grant census reads it
there) and is passed in with the other inputs, typed from their
sources. No test edits.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…ate into sibling hooks

Slice 8 of the move-only ObjectView split.

- The header's list_toolbar actions (less the record-scoped ones the
  selection bar carries), the nav runAction deep link and the
  action:bar node move verbatim into useObjectViewHeaderToolbar in
  views/objectViewHeaderToolbar.ts.
- The list's uf_ URL state (seed, write-back, Reset view) moves into
  useObjectViewListUrlState, and the filter[...] equality filters with
  the restored user-filter selections into useObjectViewUrlFilters,
  both in views/objectViewListUrlState.ts.

Hook parameters are typed from their sources. One test edit, path
only: ObjectView.urlFilterSuffix-9196 reads objectViewListUrlState.ts,
where the scanned block now lives.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
…and header chrome into sibling hooks

Move-only split of ObjectViewInner: the view tab items, ViewTabBar and
its own-row face go to objectViewTabBar.tsx; the merged views and the
plugin ObjectViewSchema go to objectViewPluginSchema.ts; the create
button, folded header actions menu and refused-view face go to
objectViewHeaderChrome.tsx. Each hook is called where its block stood.

Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · seat domain:ui#1 · 2026-10-27T03:19Z

Head cd7c7925 (read from the PR object). It is built on main e4434f75, which is after slice 8 (#26396).

  • Shape. Draft on main, Part of #25992, slice 9 (N + P). Three hooks are each called where their block stood, so the hook order is unchanged:
    • N (the tab bar) → useObjectViewTabBar in objectViewTabBar.tsx. Its view handlers are typed from the ViewTabBarProps callbacks.
    • P schema (mergedViews, the userActions policy, objectViewSchema) → useObjectViewPluginSchema in objectViewPluginSchema.ts.
    • P header chrome (the create button, folded header actions, the refused-view face) → useObjectViewHeaderChrome in objectViewHeaderChrome.tsx. It calls no React hook, and the use name matches its siblings.
      ObjectView.tsx goes from 2554 to 2374 lines. No test edits: no source reader of ObjectView.tsx names a moved line. Root changeset. Clause-②: no, Metadata-protocol: none.
  • Checks pinned to the head. All check runs completed: 0 failing, 0 pending. The seat's landing script checked this before posting.
  • Governed surface. check-governed-merges.mjs --pr 26412: not governed.
  • Seat review.
    • Move-only: the multiset proof holds, and the only base lines missing are 11 trimmed import lines or entries.
    • Parameters are typed from their sources, with no any. viewDraft's parameter is Partial<ReturnType<typeof buildViewTabs>[number]> | null, which is how the moved block reads it. The state's own Record<string, any> | null assigns to it, so the call site is unchanged and type-check is clean. The seat accepts that reading.
    • eslint: 0 errors, and 113 warnings against 116 at base, with no new rule hit.
  • Evidence (dev). Closure build and type-check exit 0. 197 files / 3335 tests. Console build under the lock. 28/28 gates, 0 NOT-MEASURED.
  • Serial constraints. No other open PR touches ObjectView.tsx or its objectView* siblings. app-shell has no sdui record. The ⛔ paths are untouched.

Landing: ready + auto-merge through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 03:20
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 03:20
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 40443830059 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test UI (2/16) — 失败步骤: Run the ui tests (shard 2/16)

    × pins every workflow-anchored claim to the version .github/workflows declares 12ms
    × reads every declaration the tree contains, so the anchor cannot shrink unnoticed 45ms
    FAIL   unit  scripts/__tests__/doc-version-claims.test.ts > doc version claims - the workflow-version assertion > pins every workflow-anchored claim to the version .github/workflows declares
      ↳ 失败原因: AssertionError: implausibly few node-version declarations were read from ../.github/workflows - the anchor is thinner than the claims it backs, or the parser stopped reading them: expected 1 to be gre
    FAIL   unit  scripts/__tests__/doc-version-claims.test.ts > doc version claims - the workflow-version assertion > reads every declaration the tree contains, so the anchor cannot shrink unnoticed
      ↳ 失败原因: AssertionError: control for the agreement above: no node-version line was found in .github/workflows at all, so two empty lists just agreed with each other: expected 1 to be greater than or equal to 1
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 10 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

The merge-queue build 40443830059 failed in Test UI (2/16) on ui/scripts/__tests__/doc-version-claims.test.ts, which reports no node-version declarations under .github/workflows. That failure does not come from this PR:

No fix is needed on this PR, and I'm not porting one. The fix belongs to #26419, where #26422 tracks it. Re-queueing this PR on a stack that no longer carries #26419 is expected to go green.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · re-queue · seat domain:ui#1 · 2026-10-27T03:34Z

Head cd7c7925, unchanged. Merge-queue build 40443830059 ejected this PR on Test UI 2/16. The failure was doc-version-claims finding no node-version: lines, which was inherited from #26419 queued ahead of it. #26419 has since been taken back to draft and out of the queue, and its ui blocker is #26421. This PR's own checks are all green, and the card thread's triage on this PR confirms that the test passes on this head. Auto-merge is re-enabled through the relay. The ACCEPT 6432905147 stands.

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/l tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants