Repository navigation
fix(plugin-grid,plugin-gantt,plugin-charts,plugin-view,app-shell): grid, gantt and chart refetch in place on the invalidation bus; no view is remounted to show a write (objectui#10035) - #10494
Conversation
…n for its object (objectui#10035) ObjectGrid read no refresh input: its load effect moved only on its own internal counter, so a host could show it a write only by remounting it. The load effect now names useDataInvalidation(objectName)'s nonce, so a write reported on the bus re-reads the rows into the same table. Rows a host handed down are not subscribed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…ion for its object (objectui#10035) The registered object-gantt renderer hands ObjectGantt only schema and dataSource, and its own query named no refresh input, so a host could show it a write only by remounting it. It now reads useDataInvalidation(resource) and answers a nonce move with the SILENT reload the toolbar refresh and every write-readback already use, so GanttView stays mounted and keeps its scroll, collapse and zoom. Host rows and inline value sets are not subscribed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…tion, on both fetch paths (objectui#10035) ObjectChart's fetch effect named no refresh input, so a host could show it a write only by remounting it. The effect now names useDataInvalidation's nonce: the object-bound aggregate keys on schema.objectName, the dataset-bound queryDataset on the base object the query's answer names (the node carries no objectName). Bound and inline rows are not subscribed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
… writes on the bus (objectui#10035) With ObjectGrid, ObjectGantt and ObjectChart now refetching in place on the data-invalidation bus, both ObjectView layers drop their remount sets (REMOUNT_TO_REFRESH_VIEW_TYPES, REMOUNT_TO_REFRESH_VISUALIZATIONS) and the grid / chart remountKey: every view is keyed on its identity alone. The bump sites those renderers no longer hear through a key now declare the change on the bus: plugin-view's own saves and deletes (announceOwnWrite) and each onMutation write it hears; app-shell's server actions, flows, imports, realtime events and refresh action (refreshData). View CRUD keeps bumping the counter alone - it is metadata, delivered as props. The kept-remount pins from the first half flip to in-place pins. The pending #10035 changeset's closing paragraph, which this change makes false, is corrected. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…the bus (objectui#10035) app-shell does not depend on plugin-gantt, so the side-effect import of the real renderer did not type-check. The pin registers an object-gantt stand-in that reads the real useDataInvalidation hook, as the stableFilterIdentity-7237 pin does, and additionally asserts the visualization itself keeps its instance. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…ng render (objectui#10035) The gate on the object-schema read was carried into the invalidation effect through a ref written during render (react-hooks/refs). The effect now names the gate's inputs and records the last nonce it answered: a nonce that lands while the gate is closed is marked handled and dropped, so the gate opening later cannot add a query beside the gated first load. The app-shell pin's harness hands out its controls from an effect instead of assigning them during render (react-hooks/globals). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
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: Branch check: ① Derived judgmentsMechanism — grid ( Mechanism — gantt ( Mechanism — chart ( Double-fetch summary. No path fetches three times. plugin-view's Hosts — plugin-view ( Hosts — app-shell ( In-list gantt. ListView hands §5 #8 corollary. Nothing in either Pins. Read, not run. Renderer pins: grid (table node same, "Written later" present, Deviation. Editing ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10035
Clause-②: no — renderers refetch in place instead of remounting. No schema, export or accept set moves.
The remainder of objectui#10035, after objectui#10378 (
Part of #10035, merged8e49a9981). Governing text: AGENTS.md §5 commandment #8's corollary, "refresh data, don't rebuild UI — after a save/action, invalidate the affected data (notifyDataChangedfrom@object-ui/react) so consumers refetch in place; never bump akey=to remount a subtree". Dispatched by thedomain:uiseat 2 PM, sessionsession_014mXUNuFomfj24w7s1pZzhN(mode:subagent, so that id is also this run's).What this changes
The three renderers that read no refresh input now read the data-invalidation bus, so neither
ObjectViewlayer remounts anything to show a write.@object-ui/plugin-gridObjectGrid: the load effect namesuseDataInvalidation(objectName)'s nonce and re-reads into the same table (theRefreshIndicatorpath, not the skeleton). Not subscribed when a host hands the rows down (data,bind, inlinevalue). Only the fetch effect region and the import line are touched; the page-size region objectui#10278 edits is not.@object-ui/plugin-ganttObjectGantt: a nonce move triggersreload({ silent: true }), the path the toolbar refresh and every write-readback already use, soGanttViewstays mounted (scroll, collapsed groups, zoom). The main fetch effect's reload flipsloadingand swapsGanttViewfor the placeholder, which is a remount one level down, so the bus does not go through it. Each nonce is answered once; one that lands while the object-schema gate is closed is dropped, because the gated first load reads rows written before it.@object-ui/plugin-chartsObjectChart: the fetch effect names the nonce, on both binding shapes. Object-bound (aggregate/find) keys onschema.objectName. Dataset-bound (queryDataset) keys on the dataset's base object as the query answer names it (object, the field the dashboardDatasetWidgetreads for drill-through), because a dataset node carries noobjectName.@object-ui/plugin-viewObjectView:REMOUNT_TO_REFRESH_VIEW_TYPESand the grid'sremountKeyare gone; every view is keyed on its identity. The sites that moverefreshKeyalso notify the bus: the host's own form save and delete (announceOwnWrite), and eachonMutationwrite it hears (republished with the event'srecordId).@object-ui/app-shellObjectView:REMOUNT_TO_REFRESH_VISUALIZATIONS, the whitelist scan and the chart branch'sremountKeyare gone;ListViewand both chart branches are keyed on identity. The bump sites that change DATA without a data-source write now go throughrefreshData(counter plusnotifyDataChanged): the action runtime's refresh (server, api and flow actions),useObjectActions' refresh (delete, therefreshaction), realtime events, and import completion. View CRUD keeps bumping the counter alone: it changes view metadata, which reaches every renderer as props..changeset/10035-renderers-read-invalidation.md(patchon the five packages). The pending.changeset/10035-objectview-refresh-in-place.mdfrom the first half is corrected in prose only (frontmatter untouched): its closing paragraph said gantt, chart and standalone-grid views "are still remounted after a write", which this change makes false before it could publish.Measurement (dispatch zone 2)
ab50ccc12: both remount sets and the gridremountKeypresent (REMOUNT_TO_REFRESH_VIEW_TYPES4 occurrences,REMOUNT_TO_REFRESH_VISUALIZATIONS3;useDataInvalidation0 in all three renderer files).useMutationInvalidationBridge, which the console mounts once inAppContent; nothing else bridges. So the bus hears a data-source write in the console, and hears nothing in an embed that does not mount the bridge.handleFormSuccess, the delete'sonRefresh, theonMutationsubscription) are data-source writes, so they reach the bus through the bridge in the console and never outside it. All three now notify it themselves.importRecordsemits noonMutation, read indata-objectstack), the action runtime (raw HTTP), and therefreshaction (no write) would stop. The delete throughuseObjectActionsreaches the bus through the bridge anyway, and now also notifies.externalRefreshKeyneeds nothing:AppContentbumps it for a form save (a data-source write, which the bridge announces) and for undo / redo (which already callnotifyDataChanged). View CRUD is metadata.find/aggregate/queryDatasetcalls; host pins: the self-fetching view's own query).Why the bus, not a host counter prop or a per-renderer
onMutationsubscription: a prop is one more relay per host (and per renderer), the silent-gap class objectui#7559's relay census exists for, and anonMutationsubscription misses every change that is not a data-source write (realtime, import, server actions).Pins: red on base, green on head
Reverse run, all five source files checked out at BASE
ab50ccc12with the head pins kept (on-disk blob == base blob for all five; restore proven bygit diff HEADempty, status clean, blob == HEAD):Test Files 5 failed (5),Tests 12 failed | 15 passed (27). Every failing in-place case failed on the half the defect predicts:ObjectGrid.invalidationRefetch-10035.test.tsx(new)ObjectGantt.invalidationRefetch-10035.test.tsx(new)ObjectChart.invalidationRefetch-10035.test.tsx(new)ObjectView.refreshInPlace-10035.test.tsx(flipped)ObjectView.refreshInPlace-10035.test.tsx(flipped)The negative cases (another object, host rows, inline rows) and the six data-fed plugin-view cases stay green on base, as they should. The gantt file was reworked after that run; its reverse was re-run on the final head: red on (b), restore proven (blob == HEAD).
Ablations (each through
ablation-replace.mjs: anchor hit exactly once, blob changed, restored to the HEAD blob)onMutationstops notifying the busannounceOwnWritestops notifyingrefreshDatastops notifyingGanttViewunmounted through the placeholder (final head)Each ablation's counterpart halves stayed green, so each pin fails for the reason it names.
Local verification (all at HEAD
af9e92846unless noted)Test Files 5 passed (5),Tests 27 passed (27).73 passed (73)/561 passed(final head); plugin-view48/443, plugin-charts70/812, plugin-grid154/1474(at904a6e061; those packages' files did not change after it).ObjectVieworAppContent(the only app-shell source file touched isviews/ObjectView.tsx):92 passed (92),892 passedat904a6e061; the app-shell pin re-ran green on the final head. The full app-shell package runs in CI.tsc --noEmit && tsc -p tsconfig.test.json), afterturbo run buildof the dependency closure: plugin-grid, plugin-charts, plugin-view Done atea9d7bce9(unchanged since); plugin-gantt and app-shell Done at the final head.eslint --format jsonover the 10 touched ts/tsx files reads 10 files, 0 errors, 579 warnings. The rules come fromeslint.config.js(--print-configresolves 118 rules forObjectView.tsx). Untouched files cannot change verdict: noparserOptions.project/projectService, so linting is not type-aware, and noeslint-rules/rule reads the disk. One finding on an added line: theexhaustive-depsnote on the grid load effect's dependency line, which namesperms/schema(not the nonce). BASE has the same note on the same effect (a lint probe of the BASE file reads it;ObjectGrid.tsxhas 208 warnings on both sides).check-changeset-presence(1 changeset, 5 released packages),check-changeset-no-major,check:new-line-citations(0 new),check:control-bytes,check-vi-mock-override-shape,check-test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:phantom-deps,check:unreferenced-sources;check-governed-queue-guard --testNOT GOVERNED. At904a6e061(no import or changeset changed after it):check-changeset-fixed,check:self-import,check:esm-specifiers(--specifiers-only),check:handler-key-reads,check:pending-changeset-literals. Report-only:check-changeset-overwritereports the pending 10035 changeset correction (its case 2);check:changeset-claimslists 16 pending changesets naming touched files, and none makes a claim about refresh, remount, keys or invalidation (each body scanned).origin/mainatb274b6103touches none of these files;git merge-treeagainst it is clean.Acceptance notes
ListViewdraws its loading skeleton in place of the visualization while it refetches an empty list (loading && data.length === 0), so a gantt or chart there is unmounted and remounted by the list's own refetch when the list has no rows. With no rows the gantt draws nothing either, so no state is lost in practice.ListView.tsxis outside this claim (objectui#10327 is in flight on it).object, the chart has no object to follow and refreshes on its own inputs only. The in-tree adapter passes the field through from the server envelope.onMutationwrite reaches the bus twice in one synchronous dispatch (the bridge, then the host), and React batches them into one render. A form save may then be announced a second time byannounceOwnWriteafter the save resolves. That is at most the two refetches the two key bumps it replaces cost, since each remount fetched.object-viewcovers its own writes and republishesonMutation.Deviations
.changeset/10035-objectview-refresh-in-place.mdfrom the first half is corrected (prose only), as above. It is not in the claim's surface. Leaving it would have published a false paragraph.refreshDatawraps the bump sites named in H2(b). The ablations show which pins depend on each.Generated by Claude Code