Repository navigation
fix(plugin-map,components,plugin-dashboard,plugin-view): each data renderer's fetch keys on the query it issues, and the map reads once, expanded (objectui#10664) - #10688
Conversation
…mount reads once and expanded (objectui#10664) The fetch effect listed the object definition among its dependencies while a separate effect loaded it into a local useState. The definition lands after the first query, so every mount issued two find calls, the first without the lookup $expand, and switching the bound object sent the new object's query with the previous object's expansion. The definition now comes from the shared useSettledSchema, and the object branch waits on it, the objectui#7895 / objectui#7903 shape. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…hen the sort they send changes (objectui#10664) Both fetch effects put the sort on $orderby but listed only filterKey in their dependencies, so a changed sort kept the old order until the object, filter or limit moved. Each now keys on a sortKey, by content the way filterKey keys the filter, so an equal sort in a fresh array is not a change. The repeater row was folded here from objectui#10665; the record picker is this card's census row for the same shape. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
… definition and keys on its expansion (objectui#10664) The census row for object-data-table carried the map's shape: the definition sat in a local useState fed by its own effect and was listed in the fetch effect's dependencies, so every object-bound mount issued two find calls, the first without $expand. The query now waits on the shared useSettledSchema. Its expansion also reads schema.columns, which the dependency list did not name, so it now keys on the expansion the columns produce, by content. The FLS pin's helper waited for a second find, which pinned the double read; it now reads the one post-definition find and asserts there is only one. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…ptionsFrom.filter changes (objectui#10664) The options effect sends optionsFrom.filter on both of its reads (the dataset query's runtimeFilter and the fallback's $filter), but its dependency list did not name it, so a changed option filter kept the previous options. It now keys on the filter by content. Census row of this card. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…t changes (objectui#10664) The non-grid fetch falls back to schema.table.sort for its $orderby, and its dependency list named the named-view and active-view sort sources but not this one, so a changed table sort kept the old order. It now keys on the table sort by content. Census row of this card. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
One patch changeset per touched package: plugin-map, components, plugin-dashboard and plugin-view, each naming the renderers it changes. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…64 table-sort pin Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Read: card objectui#10664 (body + 4 comments) and PR objectui#10688 (body, 18-file list, check-runs) via REST; the head fetched into Binding text. Triage ① Derived judgmentsA.
B. C. Census row D. Census row E. Census row: dashboard filter bar options — same mismatch, RIGHT. F. Census row: G. Stranding under the two new gates — covered. H. No identity key per render (AGENTS.md #10). Every new key is a string ( I. Files that must not be touched — untouched. The 18-file list contains no J. Existing pins edited.
K. New pins can fail — lit control and a negative leg, per file.
L. Asked and missing: none. Map gated; repeater keyed; both triage pins present (1 M. Done and not asked: nothing beyond the surface. The four extra census rows are what the triage's 「census the other data renderers for the same mismatch as part of the claim」 and the claim's file surface explicitly cover. The two test edits are consequences of the gate. No spec, types, or export moved. N. Observations (not defects). (1) ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10664
Clause-②: no
Each data renderer's fetch effect now keys on the inputs of the query it issues. The map's query waits for a settled definition, so a mount reads once and that read is expanded. The repeater and four other census rows re-read when a query input they send changes. None of them re-reads when an equal value arrives in a new object. Folds objectui#10665 (the repeater row), per the triage.
Scope, per the rulings: fetch gating and dependency lists only. No view's
errorlifecycle is touched; that is objectui#10663, dispatched in parallel. Every new key is a string compared by value (AGENTS.md #10).What changed, per renderer
object-map/ObjectMap(plugin-map)useStatefed by its own effect and was listed in the fetch effect's dependencies. Measured: 2findper mount, first without$expand; switching the bound object sent the new object's query with the previous object's expansion ([['owner'], ['manager']]fordepot).useSettledSchema, keyed onrecordSourceObjectName. The object branch waits onready(the objectui#7895 / objectui#7903 gate) and holds the loading placeholder while it waits. Host rows and inlinevaluesets are not held.element:repeater/RepeaterRenderer(components)properties.sortgoes onto$orderby; the dependency list had no sort.sortKey, by content, the wayfilterKeykeys the filter.element:record_picker(components)sort(flat, or thedataSourcebinding's) goes onto$orderby, not keyed.sortKey, by content.object-data-table/ObjectDataTable(plugin-dashboard)[null, ['account', 'owner_dept']]per mount. Under-key: the expansion readsschema.columns, which was not keyed.useSettledSchemagate on the fetching branch.lookupExpandKey(the expansion the columns produce, by content), so a relabelled column does not re-read.optionsFrom.filtergoes onto both option reads (runtimeFilter,$filter), not keyed.optionsFilterKey, by content.object-view/ObjectViewnon-grid read (plugin-view)schema.table.sortfor$orderby; the named-view and active-view sort sources are keyed, this one was not.tableSortKey, by content.Fixture re-judged: the
expandForhelper inObjectDataTable.expandFls-7230.test.tsxwaited for a secondfind, so it pinned the double read. It now reads the single post-definitionfindand asserts there is exactly one, so an ungated regression reddens instead of reading an unexpanded first call. The header ofObjectMap.invalidationRefetch-10623.test.tsxsaid the map issues twofindcalls on mount. It now points at the new pin.Census: every registered data renderer's fetch effect
Population: renderers registered through
ComponentRegistryinpackages/componentsandpackages/plugin-*whoseuseEffectissues a record read (find,aggregate,queryDataset). Enumerated by a scratch script overorigin/main4758b33, comparing what each query reads with the effect's dependency list. The script is not committed; this table is a reading taken once, not a live count (AGENTS.md #9). Field widgets (LookupFieldand siblings), app-shell pages and theuseViewDatahook (no in-tree renderer consumes it) are outside the population.object-grid/ObjectGrid(plugin-grid)$selectharvest readsconditionalFormatting,rowActionDefsandbulkActionDefs(predicate operands), and$searchFieldsreadssearchableFields. None is keyed. Measured by an uncommitted probe: adding aconditionalFormattingrule referencingindustryto a mounted grid issued no read ($selectstayed['id','name']); a fresh mount with the rule selected['id','name','industry'].list-view/ListView(plugin-list)conditionalFormatting,rowActionDefs,bulkActionDefs). Same probe, same reading.RelatedList(plugin-detail)expandKey,selectKeyand the arity flag move when the child definition lands, so a child with expandable columns or a multi-valued relationship reads twice.DECLARED COSTinRelatedList.multiValueParentScope-7299.test.tsx. Raised as an open question in the report, not overridden here.ObjectTimeline,ObjectGallery,ObjectKanban,ObjectCalendar,ObjectGantt(main read and quick-filter options),ObjectTreeObjectChart,ObjectMetricWidgetuseCallbackidentity (fetchData,fetchMetric)ObjectPivotTable,element:number, tab-count probe (containers.tsx),record-activity,record-history, reference rail,DatasetWidget,DatasetReportRenderer,LineItemsPanelEvidence
Every measured leg ran through
SchemaRendererand each package's own registration, or rendered the component directly where a sibling test already does. Vitest resolves every@object-ui/*specifier to its package'ssrc(rootvitest.config.mtsalias), so no leg's resolution path goes throughdist, and no rebuild leg applies.On the base, before any source change (pins written first):
ObjectMap.fetchGate-10664: exit 1, 4 failed / 3 passed. The 4 are the SUBJECT cases; the green 3 are the two SETTLES cases and the CONTROL.data-list.sortKey-10664+record-picker.sortKey-10664: exit 1, 3 failed / 2 passed (3 SUBJECT red, 2 CONTROL green).ObjectDataTable.fetchGate-10664: exit 1, 3 failed / 3 passed.DashboardFilterBar.optionsFilterKey-10664: exit 1, 2 failed / 1 passed.ObjectView.tableSortKey-10664: exit 1, 1 failed / 1 passed.Reverse verification, fix committed first (
46c5432). Each of the six source files was set to its4758b33blob; the mutation was proven on disk (blob equals base, not head; each new anchor counted 0). The seven pin files then ran red, exit 1, 20 failed / 10 passed. The 20 are every SUBJECT case plus the seven FLS cases, which now assert one read. The 10 are every CONTROL and SETTLES case. Restore wasgit checkout HEAD -- PATHunder atrap … EXIT INT TERM, proven by each blob equalling HEAD and an emptygit diff HEAD. The restored run exited 0, 30 / 30.Targeted ablations, both through
ablation-replace.mjs, which confirms the anchor hit and verifies the restore:lookupExpandKeyremoved from the data table's dependency list, gate kept: exit 1, only the column-change SUBJECT red (1 failed / 5 passed). This isolates the columns under-key from the double read.objectSchemaremoved from the map's dependency list, gate kept: exit 1, only the CONTROL red.$expandstayed['owner']where['owner','region']was due. The control can fail for the reason it exists.Gates
The four package suites, the type-check and eslint ran on
2d48eb5, after mergingorigin/main526fc11. The branch then mergedorigin/main4df0f3dto give6451600. That merge touches onlyapp-shellandplugin-charts, no file in the four packages here. On6451600the seven pin files and the ratchet gates were re-run. The components suite's full run is from thef133b5cmerge head. On2d48eb5, where the merge brought only theRefreshIndicatorchange intocomponents, the component files this PR edits and therefresh-indicatortests were re-run. That is a declared narrowing; CI runs the full farm.6451600, the seven pin files: exit 0, 7 / 7 files, 30 / 30 tests.6451600, exit 0:check-changeset-presence(4 changesets for 4 released packages),check-changeset-overwrite,check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:new-line-citations(0 new),check:changeset-claims,check:pending-changeset-literals,check:phantom-deps.2d48eb5,pnpm exec vitest run --maxWorkers=2 packages/plugin-dashboard/: exit 0, 135 / 135 files, 1268 / 1268 tests.2d48eb5,… packages/plugin-map/ packages/plugin-view/plus the 12data-list/record-picker/refresh-indicatortest files incomponents: exit 0, 94 / 94 files, 743 / 743 tests.f133b5c,… packages/components/: exit 0, 309 files passed and 1 skipped; 3032 tests passed and 17 skipped.f133b5c, the consumer sample: exit 0, 10 / 10 files, 900 / 900 tests. The sample is the schema-catalog render tests,InterfaceListPage.mapConfig,widget-dom-leak-sweep,public-block-binding-reach, threeListView.map*and two app-shell drill tests.2d48eb5,pnpm turbo run type-check --filter @object-ui/plugin-map --filter @object-ui/components --filter @object-ui/plugin-dashboard --filter @object-ui/plugin-view --concurrency=2: exit 0, 22 / 22 tasks.2d48eb5,pnpm exec eslinton the 14 changed.ts/.tsxfiles, plain form (what each package'slintruns): exit 0, 0 errors. The warnings are the files' existingno-explicit-anyand React Compiler advisories. Each newuseMemokey draws the samepreserve-manual-memoizationadvisory thefilterKeybeside it already draws.2d48eb5, also exit 0:check-changeset-fixed,check-changeset-no-major,check-type-check-coverage,check:element-data-source-declaration,check:handler-key-reads,check:self-import,check:unreferenced-sources, andcheck-governed-queue-guard --test(NOT GOVERNED, 18 paths).check:changeset-claims(report-only): it names ten pending changesets that cite files this change edits. Each paragraph was re-read, and none describes the fetch gating or dependency lists changed here.Changesets: one
patchper touched package (plugin-map,components,plugin-dashboard,plugin-view), each naming its renderers and citing the card.Acceptance notes
ObjectChartkeys its fetch effect onfetchData, andObjectMetricWidgetkeys onfetchMetric; both areuseCallbackresults.LineItemsPanelkeys onload, auseCallbackover the memoisedlistFilterNode/orderBy.ObjectGridlists the memoiseddataConfigandschemaFilter.useViewData(@object-ui/react, exported, no in-tree renderer consumer) rebuilds its adapter on an inlinevalueset's length alone, not its content.Generated by Claude Code