fix(app-shell): the Studio grid's columns keep a stable identity — no duplicate find() per render (#4567) - #4573
Merged
Conversation
… duplicate find() per render (#4567) The Data pillar built its object-view `table.fields` inline, allocating a fresh columns array on every render. That array's IDENTITY is a data-fetch input downstream: plugin-view's ObjectView forwards it to the `renderListView` slot as `columns` by reference, and ListView derives its `$expand` fields from `schema.columns` with the array in that memo's dependency array by identity, which is itself in the fetch effect's dependency array. Each render of the pillar therefore issued another list query — measured 1 to 4 across three re-renders that changed nothing — invisible in the UI while multiplying backend load. The array is now memoized on the draft's `fields`, so it changes identity only when its contents change. Keyed on `objDraft.fields` rather than `objDraft`: `onPatch` replaces the draft object while keeping `fields` identical, so the looser key would refetch on every unrelated draft edit. The fix is at the PRODUCER. ListView's by-identity dependency is correct for a genuine column change and is untouched (plugin-list is read-only here), as is the refresh channel pinned by #4549. Tests drive the real producer (`DataPillar`), not a reconstruction: three no-op re-renders issue no extra find(), and a real column change still refetches through a path that does not remount the grid, so the dependency is proven live rather than defeated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM step-7 复核 — ACCEPT (session_017Qqyix2QcnpUC9XeYVDzx3)
Auto-merge armed (squash) — landing verified per the merge-queue discipline. Generated by Claude Code Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4567
The chain — all three legs verified on
origin/mainat ffca56fStudioDesignSurfacebuilt the object-viewtable.fieldswithreadFields(objDraft.fields).entries.map(...).filter(...)inline, allocating a fresh array on every render of the pillar.renderListViewslot ascolumnsby reference (packages/plugin-view/src/ObjectView.tsx:997— no map/filter of its own). In design mode there is no saved view, so it falls through toschema.table?.fields.ListViewderivesexpandFieldsfromschema.columnswith that array in the memo's dependency array by identity (packages/plugin-list/src/ListView.tsx:1245-1294), andexpandFieldsis itself in the fetch effect's dependency array (:1609). A fresh array therefore refetches.The fix — producer side
The columns array is memoized on the draft's
fields, so it changes identity only when its contents change.ListView's by-identity dependency is untouched — it is correct for a genuine column change, andplugin-listis read-only for this card.Memo inputs, measured rather than guessed
objDraftisReact.useState(StudioDesignSurface.tsx:2002), so it is stable across renders and a sound key.objDraft.fields, notobjDraft:onPatchdoes a spread that replaces the draft object while keepingfieldsidentical, so the looser key would churn the columns — and refetch — on every unrelated draft edit such as an icon or label change. The neighbouringfieldCountmemo uses the looser key; this one deliberately takes the tighter one.STUDIO_SYSTEM_FIELD_NAMESis a module-scopeSet, so it is stable and not a dependency.renderStudioGridListadds only primitives plusaddRecord, which is not in ListView's fetch dependency array, andschema.data/schema.filter/schema.searchableFieldsare all undefined here.expandFieldswas the only per-render identity churn, which is why the producer memo alone reaches exactly zero — matching the filer's hoisted-columns measurement.Red-first, with the filer's measurement shape
New suite
StudioDesignSurface.gridColumns.test.tsxdrives the real producer (DataPillar), not a reconstruction — the defect is a property of how that component builds its schema, so a harness building its own array would measure the harness.Predicted before running: three no-op re-renders grow the find() count by exactly 3. Actual, verbatim, before the fix:
Exactly +3, one per render, reproducing the filer's 1 to 4 shape (this harness's mount settles at 2 rather than 1; the delta is the measurement). After the fix: 2 tests passed.
Reverse verification
Fix removed via a patch file plus
git checkout --(nevergit stash), restore sha256-verified:expected 5 to be 2Must-not-change pins
objDraft.fieldswithout remounting the grid (its key is unchanged), so the refetch it produces can only have arrived through the column-identity dependency chain. A memo that froze the columns or returned a constant turns it red.renderListViewslot'srefreshKey— the prop was forwarded to a component that never declared it #4549 / PR chore(app-shell): the dead slot refreshKey parameter is removed (#4549) #4571 untouched. ItsgridRefreshsuite stays exactly as it is, including its ownSTABLE_COLUMNShoisting — that workaround is what this fix makes unnecessary in production, but removing it from that suite would change what those tests measure. Both its bump-refetch and no-storm cases stay green.renderStudioGridListregion is not disturbed beyond the memo.Verification
vitest run --maxWorkers=2 packages/app-shell/src/views/studio-design/— 26 files, 146 tests passedpnpm --filter @object-ui/app-shell type-check— both tsc passes green (tsc --noEmitandtsc -p tsconfig.test.json)origin/main: the touched source file reports 15 warnings / 0 errors both before and after; the new test file contributes 0check:control-bytes,check:phantom-depsgreen; control-character self-scan over every touched file clean.d.tsbyte-identical both ways —dist/andtsconfig.tsbuildinfocleared between builds, same sha25672075d2c..., empty diff. Module-local memoization, hencepatch.Generated by Claude Code