Skip to content

refactor(app-shell): move ObjectView's identity import and record navigation into sibling hooks (slices 5+6) - #26353

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

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

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Part of #25992 (slices 5 and 6 of the move-only split, paired in one PR as the seat allowed; slices 1 to 4 landed as #26152, #26233, #26310 and #26336, and the card stays open for the slices listed below).

Clause-②: no
Metadata-protocol: none

What moved

Base b9cd9809 (slice 4 merged). ui/packages/app-shell/src/views/ObjectView.tsx is 3123 lines there. Two concerns of ObjectViewInner move verbatim into custom hooks in new sibling modules. In both, every input is passed as an argument, the component destructures the returned values under their original names, no identifier is renamed and no context is added.

Slice 5: identity import and the managed-environment Users marker → objectViewIdentity.tsx

Lines 688–752 move into useObjectViewIdentity({ objectDef, isAdmin, dataSource, refreshKey, t, actionRuntime }):

  • the identity-import gate (features from useExpressionContext, isIdentityImport, identityImportEnabled),
  • the managed-environment Users marker (managedUsers, the metadata-store ref, the grant-loader ref and managedUserMarker),
  • the import password-policy state, its ref, and identityDataSource.

It returns the six names the rest of the component reads: identityImportEnabled, managedUsers, managedUserMarker, identityPasswordPolicy, setIdentityPasswordPolicy, identityDataSource. The module is .tsx because the marker callback returns <UnauthorizedUserMarker>. The import-wizard open state (showImport, line 686) stays in the component, because the header and the wizard read it.

Slice 6: the record navigation overlay → objectViewRecordNavigation.ts

Two blocks move into useObjectViewRecordNavigation({ activeView, objectDef, objects, appName, viewId, searchParams, setSearchParams, location, navigate, routerRootHref, servedViews, viewLabel, unlabelledViewText, language }):

  • lines 1456–1490: detailNavigation (the authored or derived overlay mode and width) and drawerRecordId,
  • lines 1563–1686: handleNavOverlayNavigate, listRecordActions, navOverlay (useNavigationOverlay), handleDrawerClose, handleRowClick, handleExpandDrawer and the effect that syncs ?recordId= to the overlay.

It returns the five names the rest of the component reads: listRecordActions, navOverlay, handleDrawerClose, handleRowClick, handleExpandDrawer.

The block between the two (lines 1491–1562: the filter[...] URL equality filters and the uf_* user-filter selections) stays in the component. It is a different concern (list filter URL state, row H's neighbour in the seam table), and ObjectView.urlFilterSuffix-9196.test.ts pins const urlFilters, parseUrlEqualityFilterTriples and the ./drillUrlFilters.js import in ObjectView.tsx. Because the hook is called where the first block stood, the three hooks of that filter block (useMemo, useRef, useCallback) now run after the navigation hooks instead of between them. The two groups share no value, the order is the same on every render, and the only effect in either group, the ?recordId= sync, keeps its place among the component's effects.

Imports

Each import moves with its only readers: deriveRecordSurface, overlayWidthFor, RECORD_DRAWER_PARAM, ViewNavigationConfig, useNavigationOverlay, listRecordDetailUrl, listRecordActionsValue (still re-exported from ObjectView.tsx, line unchanged) go to the navigation module. useMetadata, useExpressionContext, createIdentityImportDataSource, IDENTITY_IMPORT_OBJECT, IdentityPasswordPolicy, MANAGED_USERS_OBJECT, UnauthorizedUserMarker, UserGrantLoader and isManagedEnvironment go to the identity module. viewTabLabel is imported by both, since the component still reads it. Nothing new is exported from ObjectView.tsx, and the package entry is untouched. ObjectView.tsx goes from 3123 to 2926 lines.

Move-only proof: the multiset of non-blank lines of the base ObjectView.tsx is contained in new ObjectView.tsx plus the two modules. The only base lines missing are the five import lines that lost a moved name, each replaced by its trimmed form. The added lines are the trimmed imports and the two new imports, the module headers and imports, the hook signatures and parameter types, the return objects, and in ObjectView.tsx the two calls with their destructuring and two-line comments.

Test edits

None. identityImportWriteModes-12697 reads ObjectView.tsx for the wizard's IDENTITY_IMPORT_WRITE_MODES spread and its import from ./identityImport.js; both stay in the component, and the import keeps that name. urlFilterSuffix-9196 is covered by keeping the filter block in place (above). No other source reader names a moved line.

Seam table of ObjectViewInner

The full table (regions A to Q) is in #26336. Measured again on b9cd9809, with the rows this PR changes:

# Concern Lines on b9cd9809 Reads Read by Slice
E Identity import + managed-environment Users marker 688–752 A (t), C (objectDef, isAdmin, refreshKey, actionRuntime), prop dataSource O, P, Q 5, this PR
L-nav Record navigation overlay, drawer URL state, row click, expand, record links 1456–1490, 1563–1686 A, C, F (activeView), props objects O, Q 6, this PR
L-filter filter[...] URL equality filters + uf_* user-filter selections 1491–1562 A, H (listIdentityKey, ownListParamsRef) O 8, with H
K Realtime refresh + record count (moved) 4, #26336

Remaining slices (the last closes the card)

  • Slice 7 — D: environment entitlements, toolbar actions and the create and import verdicts into a sibling hook.
  • Slice 8 — G + H + L-filter: header toolbar actions, the list URL state and the URL/user filter block into sibling hooks. Moving L-filter re-points ObjectView.urlFilterSuffix-9196.test.ts to the new file (path change only, under the seat's re-pointing allowance); otherwise it stays.
  • Slice 9 — N + P: the view tab bar and the plugin schema plus header chrome.
  • Slice 10 — Q: the JSX return's PageHeader, import wizard and NavigationOverlay blocks into sub-components with explicit props.
  • Slice 11 — B + I + O (closes the card): the view-config and view-tab CRUD handlers and renderListView, with the writeRefusalCensus-11583 rows and the relayRungCensus-7559 read re-pointed to the new file (path and file-key changes only), as the seat allowed.

The seat allows pairing two of these per PR. A, C, F, J and M (the held list refs, read only by renderListView, which go with O) stay where the table says.

Verification

See the os-dev-report comment on the card for the full readings: app-shell type-check, every ObjectView.* test in views/, the AppContent* tests, every other test that reads ObjectView.tsx as source, the app-shell tests that touch identity import, managed users or the record drawer, eslint on the 3 touched files, the derived dispatch gates, and a Console build.

Acceptance notes

  • eslint: 0 errors, and no new warning. The warnings in the moved code are the ones it carried in the component: refs read during render in the grant-loader block, any casts, and the two exhaustive-deps warnings on handleExpandDrawer (handleDrawerClose) and the drawer-sync effect (navOverlay). Dependency arrays and casts are kept verbatim because the slices are move-only. The three files report 152 warnings in total, against 158 for the base ObjectView.tsx alone.
  • The navigation hook's parameters are typed from where the component gets each value, not as any (seat review, lane any removal lint/types: 14,366 no-explicit-any warnings can never red CI — make it an error on packages/types/src, delete the vestigial (schema as any) reads, and type ComponentRendererProps with unknown #25918): activeView is ReturnType<typeof buildViewTabs>[number], objects is ConsoleObjectViewProps['objects'] and objectDef its element. The ConsoleObjectViewProps import from ./ObjectView.js is import type, so it is erased and adds no runtime cycle.

Changeset: .changeset/25992-objectview-identity-navigation-split.md (patch, @object-ui/app-shell).


🤖 Generated with Claude Code

https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz

claude added 11 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>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling labels Oct 26, 2026
…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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · seat domain:ui#1 · 2026-10-26T23:29Z

Head c416618a (read from the PR object).

  • Shape. Draft on main, Part of #25992: slices 5 and 6 paired under continuation 6427036268 and the seat's pairing allowance. Concern E (identity import, the managed-environment Users marker, the password policy and identityDataSource) moves into useObjectViewIdentity in objectViewIdentity.tsx. Concern L-nav (the record navigation overlay, the drawer and row handlers, and the recordId sync effect) moves into useObjectViewRecordNavigation in objectViewRecordNavigation.ts. ObjectView.tsx goes from 3123 to 2926 lines. Root changeset (patch). 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 26353: not governed.
  • Seat review, two rounds.
    • Move-only: the multiset proof holds, and the only base lines missing are the 5 trimmed imports. Both hooks take every input explicitly and return under the original names. No new context, and zero test edits.
    • The filter[...] / uf_* block stays in the component, because urlFilterSuffix-9196 pins it there. Its three hooks now run after the navigation hooks; they share no value, and their order is stable across renders. The body records this.
    • At the seat's ask, the navigation hook's parameters are typed from their sources (ReturnType<typeof buildViewTabs>[number], ConsoleObjectViewProps['objects'] and its element) instead of any, so the move adds no no-explicit-any warning.
  • Evidence (dev). app-shell type-check. 174 files and 3051 tests. Console build. After the main merge, the closure build, type-check, 272 tests and the Console build are green again. 30/30 gates, eslint 0 errors.
  • Serial constraints. No other open PR touches ObjectView.tsx or its siblings. app-shell is outside the sdui closure. The ⛔ paths are untouched.

Landing: ready + auto-merge through the relay.

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