Skip to content

Commit f48aa45

Browse files
refactor(app-shell): move ObjectView's view management handlers into a sibling hook (slice 11b) (#26542)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9VtKf3F9dzJPJM4RRtABaEc)_ Fixes #25992 (slice 11b, the last slice of the move-only split; slices 1 to 11a landed as #26152, #26233, #26310, #26336, #26353, #26374, #26396, #26412, #26434 and #26492). With this PR, every region of the seam table in #26336 that was marked for a slice has moved; what stays in `ObjectView.tsx` is the shared state (A, C, F, J), the panel and dialog state, the hook calls and the JSX. Clause-②: no Metadata-protocol: none ## What moved Base `a2e4b760` (the branch's merge of `main` at `73683ee1`, slice 11a merged). `ui/packages/app-shell/src/views/ObjectView.tsx` is 1480 lines there. Three blocks of `ObjectViewInner` move verbatim into one custom hook, `useObjectViewViewCrud`, in the new sibling module `objectViewViewCrud.tsx`. Every input is passed as an argument, typed from its source (no `any`), and the component destructures the 13 handlers under their original names. No identifier is renamed and no context is added. - **B: the view-config save and create doors** (lines 228–371). The objectui#12504 note on the retired `persistViewPatch`, the objectui#4155 block comment on the absent `persistViewFilter`, `handleViewConfigSave` and `handleViewCreate`, with their docblocks and dependency lists. - **B: `handleViewUpdate`** (lines 789–796), the panel's draft field update, with its docblock. - **I: view switching and the view-tab CRUD callbacks** (lines 798–1002). `handleViewChange`, the mobile view switcher items and `useMobileViewSwitcherRegistration`, `handleCreateView`, `handleViewAction`, `isSavedView`, `handleRenameView`, `handleDeleteView`, `handlePinView`, `handleSetDefaultView`, the objectui#12418 docblock and `handleReorderViews`, `handleConfigView` and `handleAddView`. The hook is called where `handleViewUpdate` stood, after `useObjectViewListUrlState`. It returns `handleViewConfigSave`, `handleViewCreate`, `handleViewUpdate`, `handleViewChange`, `handleCreateView`, `handleViewAction`, `handleRenameView`, `handleDeleteView`, `handlePinView`, `handleSetDefaultView`, `handleReorderViews`, `handleConfigView` and `handleAddView`. `isSavedView` and the mobile switcher items stay inside the hook, their only readers. **What stays.** The five `useState` calls of B (`showViewConfigPanel`, `viewConfigPanelMode`, `showCreateViewDialog`, `manageViewsOpen`, `viewDraft`, lines 217–226) stay in the component. `activeView` (F, line 758) reads `viewDraft` before the hook's inputs exist, and the JSX reads the panel and dialog flags. The hook receives the setters. **Hook order.** `handleViewConfigSave` and `handleViewCreate` (two `useCallback`s) used to run before the record count state, `useAuth` and every hook down to `useObjectViewListUrlState`. They now run after it, inside the new hook, right before `handleViewUpdate`. They could not be called at their old spot: their bodies call `setRefreshKey`, declared at line 401, so passing it to a hook there would hit the TDZ. Nothing between their old and new positions reads them (measured: no reference between lines 372 and 788). The hooks of `handleViewUpdate` and I keep their order and position. The seat's ruling allows this shift when stated. ### Imports Each import moves with its only readers: `formatMetadataError`, `viewTabLabel`, `persistRuntimeMetadata` / `createRuntimeMetadata` / `viewEnvelope`, `PREVIEW_QUERY_FLAG` / `PREVIEW_QUERY_VALUE`, `useMobileViewSwitcherRegistration`, `MobileViewSwitcherItem`, `isSavedViewId`, `buildAddViewSpec`, `setDefaultViewPatches`, `buildViewConfigSaveBody` and `dispatchViewPatches`. These names are still re-exported from `ObjectView.tsx` where they were before, and those lines are unchanged. Names the component still reads elsewhere (`useCallback`, `useMemo`, `toast`, `VIEW_TYPE_ICONS`) are imported by the module as well. Nothing new is exported from `ObjectView.tsx`, and the package entry is untouched. `ObjectView.tsx` goes from 1480 to 1151 lines. Move-only proof: the multiset of non-blank lines of the base `ObjectView.tsx` is contained in the new `ObjectView.tsx` plus the module, except 9 lines. Every one of the 9 is an import line or an import-list entry that lost a moved name: the `PREVIEW_QUERY_FLAG` and `PREVIEW_QUERY_VALUE` entries, the `../utils/viewIdentity.js` import (replaced by its trimmed form), the `buildAddViewSpec`, `setDefaultViewPatches` and `buildViewConfigSaveBody` entries of the `./objectViewTabs.js` list, and the three-line `./objectViewOptions.js` import of `dispatchViewPatches`. The module imports each of those names itself. The added lines are: - the trimmed import and the new import; - the module header and imports; - the hook signature and parameter types; - the `return`; - in `ObjectView.tsx`, the call with its destructuring and a two-line comment. ## Test edits Five source-scan readers move to the file that now holds what they read. Each edit is a path or file-list change, and no assertion changed: 1. `writeRefusalCensus-11583.test.ts`: the six `LEDGER` rows for `handleViewConfigSave`, `handleViewCreate`, `handleRenameView`, `handleDeleteView`, `handlePinView` and `handleSetDefaultView` change `file: 'ObjectView.tsx'` to `file: 'objectViewViewCrud.tsx'`. Callees, counts, pins and reasons are unchanged. The census walks `views/`, so it finds the sites in the new file. 2. `ui/scripts/__tests__/check-i18n-call-site-keys.test.ts`: the `deleteViewConfirm` row of the deleted-fallbacks list → `packages/app-shell/src/views/objectViewViewCrud.tsx`. Its absent and present markers are unchanged. 3. `ObjectView.overlayPatchOnly.test.ts`: the joined `objectViewCode` list gains `'objectViewViewCrud.tsx'`. The control's `handleViewConfigSave` and `persistRuntimeMetadata` reads are there now. 4. `view-filter-fold.ratchet.test.ts`: the joined `objectViewSrc` list gains `objectViewViewCrud.tsx`, for the `handleViewConfigSave` initializer the ratchet parses. 5. `ObjectView.unboundAdapterCall.test.ts`: the detach-then-call ratchet reads `ObjectView.tsx` and `objectViewViewCrud.tsx` joined, the same way as the two above. Without this edit it would still pass, but it would no longer read `handleSetDefaultView` and `handlePinView`, the handlers it guards. Edit 5 is the one the seat's list did not name. It is listed for the same reason as the others: the scan's subject moved. ## Seam table rows this PR changes (measured on `a2e4b760`) The full table (regions A to Q) is in #26336. | # | Concern | Lines on `a2e4b760` | Reads | Read by | Slice | |---|---|---|---|---|---| | B (state) | Panel and dialog open state, `viewDraft` | 217–226 | — | F, H, I, N, O, P, Q | stays (shared state) | | B | `handleViewConfigSave`, `handleViewCreate` and their notes | 228–371 | A, C (`setRefreshKey`) | Q | **11b, this PR** | | B | `handleViewUpdate` | 789–796 | B (`setViewDraft`), F (`baseView`) | Q | **11b, this PR** | | I | View switching, mobile switcher, view-tab CRUD callbacks | 798–1002 | A, B, C, F | N, Q | **11b, this PR** | ## Verification The full readings are in the os-dev-report comment on the card: - app-shell `type-check` after rebuilding its dependency closure; - 208 test files: every `ObjectView.*` test in `views/` (the view CRUD, refusal, create-view, default-view, view-config save and mobile FAB tests among them), the `AppContent*` tests, and every other test that reads `ObjectView.tsx` as source, including the five re-pointed ones; - eslint on the touched files; - the derived dispatch gates; - a Console build. ## Acceptance notes - eslint: 0 errors, and no new `no-explicit-any`. `ObjectView.tsx` plus the module report 83 warnings, against 80 for the base `ObjectView.tsx` alone. - Added: 10 `react-hooks/exhaustive-deps` warnings in the module, and 1 existing one (the `objectName` warning on `handleDeleteView`) now also names `setRefreshKey`. Each added warning names a state setter that is now a hook parameter (`setRefreshKey`, `setViewDraft`, `setShowViewConfigPanel`, `setViewConfigPanelMode`, `setShowCreateViewDialog`). The rule treats a `useState` setter as stable only in the component that declares it. The dependency lists are kept verbatim, because adding the setters would change code; the setters are stable, so behaviour is the same. Slice 8 drew the same class of warning at its hook boundary. - Gone: the 2 `exhaustive-deps` warnings on `handleViewChange`, the 4 `preserve-manual-memoization` skips on these handlers, and 1 `react-hooks/immutability` finding (`handleViewConfigSave` reading `setRefreshKey` before its declaration). - Unchanged: the other `exhaustive-deps` warnings (`listPrefs` in `handleReorderViews`, and the two on the `objectDef` effect) only moved lines. - Net: 80 + 10 − 2 − 4 − 1 = 83. - The test edits add no warning. Changeset: `.changeset/25992-objectview-view-crud-split.md` (patch, `@object-ui/app-shell`). --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a89074a commit f48aa45

8 files changed

Lines changed: 526 additions & 384 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
refactor(app-shell): the Console `ObjectView`'s view management handlers move into a sibling hook
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The view-config panel's save and create handlers, the draft field update, view switching, the mobile view switcher registration and the view-tab CRUD callbacks (rename, delete, pin, set default, reorder, configure, add) move verbatim out of `ObjectViewInner` in `views/ObjectView.tsx` into `useObjectViewViewCrud` in `objectViewViewCrud.tsx`. The panel and dialog state stays in the component, which passes every input in and reads the returned handlers. No logic, name or rendered output changes, and the package entry is unchanged.
11+
12+
What to do: nothing.

‎ui/packages/app-shell/src/views/ObjectView.overlayPatchOnly.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ describe('objectui#5233 — what must NOT be narrowed', () => {
308308
*/
309309
describe('objectui#12504 census — the toolbar-to-view write family is retired', () => {
310310
const here = path.dirname(fileURLToPath(import.meta.url));
311-
const objectViewCode = stripComments(['ObjectView.tsx', 'objectViewListRenderer.tsx'].map((file) => readFileSync(path.join(here, file), 'utf8')).join('\n'));
311+
const objectViewCode = stripComments(['ObjectView.tsx', 'objectViewListRenderer.tsx', 'objectViewViewCrud.tsx'].map((file) => readFileSync(path.join(here, file), 'utf8')).join('\n'));
312312
const dataSourceCode = stripComments(
313313
readFileSync(path.join(here, '..', '..', '..', 'types', 'src', 'data.ts'), 'utf8'),
314314
);

‎ui/packages/app-shell/src/views/ObjectView.tsx‎

Lines changed: 43 additions & 372 deletions
Large diffs are not rendered by default.

‎ui/packages/app-shell/src/views/ObjectView.unboundAdapterCall.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ describe('set as default (#4463)', () => {
188188

189189
describe('the detach-then-call pattern stays gone (ratchet)', () => {
190190
it('ObjectView.tsx never assigns an adapter method to a local', () => {
191-
const source = readFileSync(
192-
fileURLToPath(new URL('./ObjectView.tsx', import.meta.url)),
191+
const source = ['./ObjectView.tsx', './objectViewViewCrud.tsx'].map((file) => readFileSync(
192+
fileURLToPath(new URL(file, import.meta.url)),
193193
'utf8',
194-
);
194+
)).join('\n');
195195

196196
// Comments are stripped FIRST, and that is not a detail: the fix's own
197197
// docblock quotes the broken line verbatim to explain it, so a scan over the

‎ui/packages/app-shell/src/views/objectViewViewCrud.tsx‎

Lines changed: 459 additions & 0 deletions
Large diffs are not rendered by default.

‎ui/packages/app-shell/src/views/view-filter-fold.ratchet.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
4343
const objectViewPath = path.join(here, 'ObjectView.tsx');
4444
const widgetsPath = path.join(here, 'metadata-admin/widgets.tsx');
4545

46-
const objectViewSrc = [objectViewPath, path.join(here, 'objectViewListRenderer.tsx')].map((file) => readFileSync(file, 'utf8')).join('\n');
46+
const objectViewSrc = [objectViewPath, path.join(here, 'objectViewListRenderer.tsx'), path.join(here, 'objectViewViewCrud.tsx')].map((file) => readFileSync(file, 'utf8')).join('\n');
4747
const widgetsSrc = readFileSync(widgetsPath, 'utf8');
4848

4949
/**

‎ui/packages/app-shell/src/views/writeRefusalCensus-11583.test.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,32 +88,32 @@ const LEDGER: Row[] = [
8888
reason: 'Issues one `updateView` per patch and hands every promise back; set-as-default and reorder await them and answer for the refusal.',
8989
},
9090
{
91-
file: 'ObjectView.tsx', symbol: 'handleViewConfigSave', callee: 'persistRuntimeMetadata', calls: 1,
91+
file: 'objectViewViewCrud.tsx', symbol: 'handleViewConfigSave', callee: 'persistRuntimeMetadata', calls: 1,
9292
kind: 'surfaces', pin: 'ObjectView.viewWriteRefusal-11583.test.tsx',
9393
reason: 'The view-config panel\'s edit Save: a toast with the door\'s message, and the panel is told the save failed, so it stays dirty (objectui#11583).',
9494
},
9595
{
96-
file: 'ObjectView.tsx', symbol: 'handleViewCreate', callee: 'createRuntimeMetadata', calls: 1,
96+
file: 'objectViewViewCrud.tsx', symbol: 'handleViewCreate', callee: 'createRuntimeMetadata', calls: 1,
9797
kind: 'surfaces', pin: 'ObjectView.createViewRefusal-11578.test.tsx',
9898
reason: 'Create View: a toast with the door\'s message, and the dialog stays open (objectui#11578).',
9999
},
100100
{
101-
file: 'ObjectView.tsx', symbol: 'handleRenameView', callee: 'updateView', calls: 1,
101+
file: 'objectViewViewCrud.tsx', symbol: 'handleRenameView', callee: 'updateView', calls: 1,
102102
kind: 'surfaces', pin: 'ObjectView.viewWriteRefusal-11583.test.tsx',
103103
reason: 'Rename: the translated `objectViewActions.renameFailed` title, with the door\'s message as the description (objectui#12417).',
104104
},
105105
{
106-
file: 'ObjectView.tsx', symbol: 'handleDeleteView', callee: 'deleteView', calls: 1,
106+
file: 'objectViewViewCrud.tsx', symbol: 'handleDeleteView', callee: 'deleteView', calls: 1,
107107
kind: 'surfaces', pin: 'ObjectView.viewWriteRefusal-11583.test.tsx',
108108
reason: 'Delete: the translated `objectViewActions.deleteFailed` title, with the door\'s message as the description (objectui#12398).',
109109
},
110110
{
111-
file: 'ObjectView.tsx', symbol: 'handlePinView', callee: 'updateView', calls: 1,
111+
file: 'objectViewViewCrud.tsx', symbol: 'handlePinView', callee: 'updateView', calls: 1,
112112
kind: 'surfaces', pin: 'ObjectView.viewWriteRefusal-11583.test.tsx',
113113
reason: 'Pin and unpin: a toast with the door\'s message (objectui#11583).',
114114
},
115115
{
116-
file: 'ObjectView.tsx', symbol: 'handleSetDefaultView', callee: 'dispatchViewPatches', calls: 1,
116+
file: 'objectViewViewCrud.tsx', symbol: 'handleSetDefaultView', callee: 'dispatchViewPatches', calls: 1,
117117
kind: 'surfaces', pin: 'ObjectView.viewWriteRefusal-11583.test.tsx',
118118
reason: 'Set as default: a toast with the door\'s message; it was a bare untranslated literal with no description until the objectui#11583 patch round.',
119119
},

‎ui/scripts/__tests__/check-i18n-call-site-keys.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ export const A = (name: string, label: string, n: number) => {
10881088
"t('marketplace.detail.reseedLocalSuccess', { inserted, updated })",
10891089
],
10901090
[
1091-
'packages/app-shell/src/views/ObjectView.tsx',
1091+
'packages/app-shell/src/views/objectViewViewCrud.tsx',
10921092
// Tight on purpose: the file still holds a legitimate *computed*
10931093
// `defaultValue` ending in the same sentence (line ~1657), and a marker
10941094
// loose enough to catch that one would fail for the wrong reason.

0 commit comments

Comments
 (0)