Skip to content

Commit 73683ee

Browse files
refactor(app-shell): move ObjectView's renderListView into a sibling hook (slice 11a) (#26492)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9VtKf3F9dzJPJM4RRtABaEc)_ Part of #25992 (slice 11a of the move-only split; slices 1 to 10 landed as #26152, #26233, #26310, #26336, #26353, #26374, #26396, #26412 and #26434). Slice 11 is split in two, as the seat agreed: this PR moves `renderListView`, and slice 11b (the view-config and view-tab CRUD handlers) closes the card. Clause-②: no Metadata-protocol: none ## What moved Base `8cac64f2` (slice 10 merged). `ui/packages/app-shell/src/views/ObjectView.tsx` is 2263 lines there. Three blocks of `ObjectViewInner` move verbatim into one custom hook, `useObjectViewListRenderer`, in the new sibling module `objectViewListRenderer.tsx`. Every input is passed as an argument, typed from its source (no `any`), and the component destructures `renderListView` under its original name. No identifier is renamed and no context is added. - **O: `renderListView`** (lines 1145–1910). The `useCallback` the plugin `ObjectView` calls to draw a view. It covers the `type: 'chart'` face through `ObjectChart`, the `ListView` face with the relay of the view's keys onto `fullSchema`, the toolbar doors that write the user's own list state through `listPrefs.patchView`, the held `options` and `filter` identities, and the dependency list, all kept verbatim. - **G-columns: the column-declaration flags** (lines 783–806). `activeTabDeclaresColumns`, `draftColumnsEdited`, `activeViewDeclaresColumns` and `activeViewIsSaved`, with their docblocks. Only `renderListView` reads them. - **M: the held list identity refs** (lines 1109–1119). `heldListOptions` and `heldListFilter`, the two `useRef`s with their docblocks (objectui#10046, #7237). Only `renderListView` reads them. The hook is called where `renderListView` stood, after the view tab bar hook. The block it returns is unchanged. **Hook order.** The two `useRef` calls of M used to run before `useObjectViewTabBar` (whose `useViewTabsFit` and `useDisplayLocale` run in between). They now run after it, inside the new hook. Both are independent refs initialised to `undefined`; nothing between their old and new positions reads them. The seat's ruling allows this shift when stated. G-columns are plain values, not hooks. The module also takes the lazy `ObjectChart` declaration (`lazy(() => import('@object-ui/plugin-charts')…)`), verbatim. Its only mount is the chart face of `renderListView`, so the chart chunk stays lazy and is split exactly as before. The `ImportWizard` lazy declaration stays in `ObjectView.tsx`. ### Imports Each import moves with its only readers: `DENSITY_MODE_TO_ROW_HEIGHT`, `normalizeListViewSchema`, `buildListFilterKey`, `writeListFilterState`, `ListViewPreference`, `ListView`, `ListViewSchema`, `TreeViewConfig`, `useRef`, `ReactNode`, `resolveManagedByEmptyState`, `warnSuppressedListNav`, `substituteFilterTokens`, `keepInlineEditModeForTheSession`, and the view-options family entries (`timelineViewOptions`, `calendarViewOptions`, `ganttViewOptions`, `galleryViewOptions`, `kanbanViewOptions`, `storedLegacyOptions`, `isSameOptionsValue`). The view-options names are still re-exported from `ObjectView.tsx`, lines unchanged. Names the component still reads elsewhere (`useCallback`, `Suspense`, `lazy`, `isSavedViewId`, `FilterTokenScope`) are imported by the module as well. Nothing new is exported from `ObjectView.tsx`, and the package entry is untouched. `ObjectView.tsx` goes from 2263 to 1480 lines. Move-only proof: the multiset of non-blank lines of the base `ObjectView.tsx` is contained in new `ObjectView.tsx` plus the module. The only base lines missing are six import lines or entries that lost a moved name: the `react`, `@object-ui/core`, `../context/ListPreferencesProvider.js`, `@object-ui/types` and `./objectViewChrome.js` imports, and the `substituteFilterTokens` entry of the helpers list. Each import is replaced by its trimmed form. Removed outright are the `./listFilterStorage.js`, `@object-ui/plugin-list`, `../utils/managedByEmptyState.js` and `../utils/warnSuppressedListNav.js` imports, the `ObjectChart` lazy declaration, and the view-options entries of the `./objectViewOptions.js` import list. Their lines reappear verbatim in the module. The added lines are the trimmed imports and the new import, the module header and imports, the hook signature and parameter types, the `return`, and in `ObjectView.tsx` the call with its destructuring and two-line comment. ## Test edits Six 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. `ObjectView.relayRungCensus-7559.test.ts`: `RELAY_SOURCE` reads `objectViewListRenderer.tsx` (both candidate paths), the parse name follows, and so does the path in the anchor's error message. The census anchors on `const fullSchema = { … }` in `renderListView`. 2. `packages/types/src/__tests__/tree-view-config-readers-8253.test.ts`: `CONSOLE_COMPOSITION` → `packages/app-shell/src/views/objectViewListRenderer.tsx`. It reads the `viewDef` tree `labelField` rung. 3. `packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts`: `APP_SHELL_OBJECT_VIEW` → `packages/app-shell/src/views/objectViewListRenderer.tsx`. Its three reads are the app-shell relay (`rowHeight` through the fold, `rowColor`, and the absent `allowExport` / `prefixField` / `densityMode` reads). 4. `ObjectView.titleFieldConvergence.test.tsx`: the joined `SOURCE` list `['ObjectView.tsx', 'objectViewOptions.ts']` gains `'objectViewListRenderer.tsx'`. The seams it counts span all three. 5. `ObjectView.overlayPatchOnly.test.ts`: `objectViewCode` reads `ObjectView.tsx` and `objectViewListRenderer.tsx` joined. The retired-family absence covers both, `handleViewConfigSave` stays in `ObjectView.tsx`, and the objectui#12474 doors (`onDensityChange`, `persistHiddenFields`) are in the module. 6. `view-filter-fold.ratchet.test.ts`: `objectViewSrc` reads `ObjectView.tsx` and `objectViewListRenderer.tsx` joined. The `onFilterChange` handlers are in the module, and `handleViewConfigSave` stays in `ObjectView.tsx`. Slice 11b will add its module to the joined lists in 5 and 6, and re-point the `writeRefusalCensus-11583` rows and `check-i18n-call-site-keys`. ## Seam table rows this PR changes (measured on `8cac64f2`) The full table (regions A to Q) is in #26336. | # | Concern | Lines on `8cac64f2` | Reads | Read by | Slice | |---|---|---|---|---|---| | G-columns | Column-declaration flags of the active view | 783–806 | B (`viewDraft`), F (`baseView`, `derivedColumnViewIds`, `savedViews`) | O | **11a, this PR** | | M | Held list identity refs | 1109–1119 | — | O | **11a, this PR** | | O | `renderListView` | 1145–1910 | A, B, C, D, E, F, G-columns, H, J, L, L-filter, M, N | Q | **11a, this PR** | | B | View config panel and create dialog state, `handleViewConfigSave`, `handleViewCreate` (and `handleViewUpdate`, 829–836) | 231–386 | A, C | F, G-columns, H, I, N, P, Q | 11b | | I | View switching, mobile switcher, view-tab CRUD callbacks | 837–1045 | A, B, C, F | N, Q | 11b (closes the card) | ## 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 calendar, gallery, gantt, timeline, kanban and chart bindings, relays, density, column and filter tests among them), the `AppContent*` tests, every other test that reads `ObjectView.tsx` as source, including the six re-pointed ones, eslint on the touched files, the derived dispatch gates, and a Console build. ## Acceptance notes - eslint: 0 errors, no new `no-explicit-any`. The two files report 108 warnings against 107 for the base `ObjectView.tsx` alone. The one added warning is `react-refresh/only-export-components` on `objectViewListRenderer.tsx`: the module declares the lazy `ObjectChart` component and exports a hook. The declaration is kept verbatim with its only mount rather than reshaped. The two `exhaustive-deps` warnings on `handleViewChange` only moved lines. The six test edits add no warning. Changeset: `.changeset/25992-objectview-list-renderer-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 f9ecf95 commit 73683ee

9 files changed

Lines changed: 975 additions & 831 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 `renderListView` moves into a sibling hook
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`renderListView`, the callback the plugin `ObjectView` calls to draw a view through `ListView` (and a chart view through `ObjectChart`), moves verbatim out of `ObjectViewInner` in `views/ObjectView.tsx` into `useObjectViewListRenderer` in `objectViewListRenderer.tsx`. The column-declaration flags and the held list identity refs that only it reads move with it. The component passes every input in and reads the returned callback. 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(readFileSync(path.join(here, 'ObjectView.tsx'), 'utf8'));
311+
const objectViewCode = stripComments(['ObjectView.tsx', 'objectViewListRenderer.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.relayRungCensus-7559.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const sourceOf = (fromRoot: string, fromPackage: string): string => {
131131
return readFileSync(path, 'utf8');
132132
};
133133

134-
const RELAY_SOURCE = sourceOf('packages/app-shell/src/views/ObjectView.tsx', 'src/views/ObjectView.tsx');
134+
const RELAY_SOURCE = sourceOf('packages/app-shell/src/views/objectViewListRenderer.tsx', 'src/views/objectViewListRenderer.tsx');
135135
const UPSTREAM_SOURCE = sourceOf('packages/plugin-view/src/ObjectView.tsx', '../plugin-view/src/ObjectView.tsx');
136136
const TYPES_SOURCE = sourceOf('packages/types/src/objectql.ts', '../types/src/objectql.ts');
137137
const LIST_VIEW_SOURCE = sourceOf('packages/plugin-list/src/ListView.tsx', '../plugin-list/src/ListView.tsx');
@@ -225,7 +225,7 @@ const collectLiteral = (literal: ts.ObjectLiteralExpression, prefix: string, out
225225
};
226226

227227
const relayLiteral = ((): ts.ObjectLiteralExpression => {
228-
const sf = parse('ObjectView.tsx', RELAY_SOURCE);
228+
const sf = parse('objectViewListRenderer.tsx', RELAY_SOURCE);
229229
let found: ts.ObjectLiteralExpression | null = null;
230230
const visit = (node: ts.Node): void => {
231231
if (
@@ -244,7 +244,7 @@ const relayLiteral = ((): ts.ObjectLiteralExpression => {
244244
if (!found) {
245245
throw new Error(
246246
'objectui#7559 census cannot find the relay literal `const fullSchema = { … }` in\n'
247-
+ 'app-shell/src/views/ObjectView.tsx. The census anchors on that name. If the literal was\n'
247+
+ 'app-shell/src/views/objectViewListRenderer.tsx. The census anchors on that name. If the literal was\n'
248248
+ 'renamed or moved, re-point this anchor — ⛔ do not delete the census, which is the only\n'
249249
+ 'thing standing between a dropped rung and silence (objectui#7199, #7218, #7516).',
250250
);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ describe('ObjectView view-config `titleField` — the middle rung is gone (objec
314314
describe('the seven seams share ONE expression shape (objectui#6557)', () => {
315315
// This face spans `ObjectView.tsx` and the view-options family it imports
316316
// from `objectViewOptions.ts` (objectstack#25992 slice 2), so the scan reads both.
317-
const SOURCE = ['ObjectView.tsx', 'objectViewOptions.ts']
317+
const SOURCE = ['ObjectView.tsx', 'objectViewOptions.ts', 'objectViewListRenderer.tsx']
318318
.map((file) => readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), file), 'utf8'))
319319
.join('\n');
320320

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

Lines changed: 40 additions & 823 deletions
Large diffs are not rendered by default.

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

Lines changed: 915 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 = readFileSync(objectViewPath, 'utf8');
46+
const objectViewSrc = [objectViewPath, path.join(here, 'objectViewListRenderer.tsx')].map((file) => readFileSync(file, 'utf8')).join('\n');
4747
const widgetsSrc = readFileSync(widgetsPath, 'utf8');
4848

4949
/**

‎ui/packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2050,7 +2050,7 @@ describe('objectui#7924 — the unread members, in the FOUR buckets the ruling w
20502050
*/
20512051

20522052
const LISTVIEW = 'packages/plugin-list/src/ListView.tsx';
2053-
const APP_SHELL_OBJECT_VIEW = 'packages/app-shell/src/views/ObjectView.tsx';
2053+
const APP_SHELL_OBJECT_VIEW = 'packages/app-shell/src/views/objectViewListRenderer.tsx';
20542054

20552055
/** A named view's one required member; every literal below is a delta on it. */
20562056
const BASE_VIEW = { label: 'All' } as const;

‎ui/packages/types/src/__tests__/tree-view-config-readers-8253.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const read = (rel: string) => readFileSync(join(REPO_ROOT, rel), 'utf8');
7777
const RESOLVER = 'packages/plugin-tree/src/ObjectTree.tsx';
7878
const VIEW_BRANCH = 'packages/plugin-view/src/ObjectView.tsx';
7979
const LIST_BRANCH = 'packages/plugin-list/src/ListView.tsx';
80-
const CONSOLE_COMPOSITION = 'packages/app-shell/src/views/ObjectView.tsx';
80+
const CONSOLE_COMPOSITION = 'packages/app-shell/src/views/objectViewListRenderer.tsx';
8181

8282
/**
8383
* The declared keys, as a value — `it.each` needs one, and a failure named

0 commit comments

Comments
 (0)