feat(components): a kind:'react' page's author scope injects useDataInvalidation (objectui#10887) - #10973
Conversation
…nvalidation A react page's own useAdapter reads now have a bus reader to name: the scope injects useDataInvalidation from @object-ui/react beside useAdapter, and the react-pages guide's scope table and Live data example teach the nonce in the effect's dependency list. Pinned through a real react page: one bus event on the page's object re-runs its read once, in place, and an event on another object does not. The members 1-2 review notes that are text ride along: the plugin-view changeset and ObjectView's comments now name the zero-row gantt, which keeps its own reader, and the identityKey comment names the bus re-read. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…out any Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…rrections Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
|
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: ① Derived judgmentsRead at 2026-09-28T12:40Z. Diff base: merge-base The injection.
The collision.
The pin (
The guide (
Notes (a) and (d).
Notes (b) and (c) were correctly left out: both are true and both need behaviour.
CI at Commits: three ( ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10887
Clause-②: yes — one identifier,
useDataInvalidation, joins the published author scope ofkind: 'react'pages, andcontent/docs/guide/react-pages.mdteaches it.Why a closing keyword. The card has three members. Members 1 and 2 landed in PR #10898 (squash
9f0c84a44). This PR lands member 3, ruled A (ruling5864668610), so all three are in once it merges. The four review notes from the members 1 and 2 round ride this round: (a) and (d) are text and are corrected here; (b) and (c) need a behaviour change, so they are reported as findings and not changed here (see Acceptance notes). The objectstack half of the ruling (the docs page, the skill and the showcase pages) is carried by objectstack-ai/objectstack#20395. TheBlocked-byline on objectui#10519 is the seat's to update; this PR does not touch that card.What changed
@object-ui/components, the react-page renderer.ReactKindPageinjectsuseDataInvalidationfrom@object-ui/reactinto the author scope, besideuseAdapter. It is the same hookListViewreads to refresh its rows:useDataInvalidation('object')returns a nonce that moves when the bus reports a write to that object, or'*'. A page names it in the dependency list of the effect that reads throughuseAdapter, and that read re-runs in place. The hook is a module-level function, and the scope memo still depends on the schema and the adapter only, so the scope keeps its identity and no page recompiles or loses state. The file header's scope list gains the entry.useDataInvalidationrow. The Live data example readsconst changed = useDataInvalidation('showcase_project')and nameschangedin the effect's dependency array, followed by one sentence on why: the nonce moves each time the bus reports a write to that object, so the effect reads again in place without remounting the page.10887-react-page-data-invalidation:minorfor@object-ui/components,Clause-②: yes.useDataInvalidationis exported by@object-ui/react, which@object-ui/componentsalready depends on and already importsuseAdapterfrom.Review notes from the members 1 and 2 round (
5864212493)10887-object-view-non-grid-bus-readernow names the exception: a gantt handed zero rows still queries for itself and keeps its own bus reader (objectui#7333). Frontmatter byte-identical (the first three lines hash the same before and after); the file has not shipped (no CHANGELOG carries its text), so it is corrected in place. The same imprecision inObjectView's member 1 comment is corrected too.fetchDrawsViewis a deny-list, so a hostviewsentry the renderer does not draw adds one unused readObjectView's non-grid fetch setsloadingon every run and passes it to the view, andObjectCalendarshows it; a silent re-read changes behaviour. Reported as a finding.identityKeycomment is incompleterefreshKeymoves or when the bus reports a change to the object (objectui#10887), that a gantt handed rows draws them while a gantt handed zero rows queries for itself (objectui#7333), and thattreereads the bus itself (objectui#10778).Both comment edits sit inside
ObjectView's component body, so neither reaches a published.d.ts; no plugin-view surface moves and the plugin-view changeset keeps itspatch.Pin, through a real react page
packages/components/src/__tests__/react-page-invalidation.test.tsx. The page is compiled from source by@object-ui/react-runtimeinside the realReactKindPage, dispatched by the realPageRendererfortype: 'home'; the events go through the real bus (notifyDataChanged). Only the adapter is a stand-in, so reads can be counted. The page source is the guide's Live data pattern with the nonce.showcase_projectre-runs the page'sfindexactly once (1 to 2), the re-read rows reach the page, and a counter the user clicked before the event still reads 1, so the page was not remounted.showcase_invoicedoes not re-run the read (findstays at 1, the rows stay the first answer), while a bareuseDataInvalidation('showcase_invoice')reader mounted beside the page moves from 0 to 1, so the event demonstrably reached the bus.react-page.tsx(blobf70b8f425, restored fromHEADafterwards: blob equal,git diff HEADempty)Tests 2 failed (2): both tests, each at the page-error assertion,ReferenceError: useDataInvalidation is not definedin the page's error panelTests 2 passed (2)on the implementation's first commitf64ba6e66, and again inside the package run below ataf76d224cablation-replace, anchoruseDataInvalidation,hit once, blobab712dfacto9625f569b, restored to theHEADblob,git diff HEADempty)Tests 1 failed | 1 passed (2): only the re-read test,expected 'read 1' to be 'read 2'; the lit control stays greenThe pin imports its subject by relative path, and
@object-ui/reactand@object-ui/react-runtimethrough the root vitest alias to their sources, so nodist/sits between a mutation and the run.Tests and gates (head
af76d224c)pnpm exec vitest run packages/components/ packages/plugin-view/ --maxWorkers=2under the verify lock ataf76d224c(the run printed that sha):Test Files 379 passed | 1 skipped (380),Tests 3689 passed | 24 skipped (3713).pnpm exec vitest run scripts/ --maxWorkers=2under the verify lock ataf76d224c: the wholescripts/test project, so everyscripts/__tests__gate whose corpus reads a touched file (the changesets, the guide, the package sources) is in it:Test Files 177 passed | 2 skipped (179),Tests 5332 passed | 2 skipped (5334).type-checkfor@object-ui/componentsand@object-ui/plugin-view(echoedtsc --noEmit && tsc -p tsconfig.test.json): Done, Done, at2ca2478ee, afterturbo run buildover both packages' dependency closures (15 tasks). The one later commit reflows comment and changeset prose only.tsc -p tsconfig.test.json --listFilesinpackages/componentsnames the new pin once, so the test program compiles it.af76d224c, each exit 0:check:control-bytes,check:new-line-citations(0 new citation(s)),changeset:check,check-changeset-presence(3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)),check-changeset-overwrite(reports the plugin-view changeset edit as a correction, its case 2),check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:phantom-deps,check:unused-deps,check:shell-escape-residue,check:doc-fences,check:doc-types,check:doc-example-ids,check:doc-example-readers,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:comment-mask-corpus.--no-inline-config --format json: 3 files, 0 errors. The new pin carries 0 warnings.react-page.tsx(6) andObjectView.tsx(60) carry the same warnings per rule as their base blobs, read through--stdin-filename. This is a narrowed run, not the repo lint: the root ESLint config enables no type-aware linting (noparserOptions.project, noprojectService), so this diff cannot move a verdict on an untouched file.pnpm lintis CI's.check:doc-snippetsandcheck:doc-examples: NOT MEASURED locally, prerequisite not met (they need the whole covered package set built). The guide's changed block is fencedjsx, whichcheck:doc-snippetsdoes not compile (its compiled fence set ists,tsx,typescript). CI runs both.check-governed-queue-guard --testover the 6 changed paths: NOT GOVERNED.One case the widened scope can break
The scope's names become the parameters of the function the page source is evaluated in. A page that declares its own top-level
constorletnameduseDataInvalidationtherefore now fails with aSyntaxError(Identifier 'useDataInvalidation' has already been declared), shown in the page's error panel; afunctionorvardeclaration of that name only shadows the injected one. Measured once through the real renderer with a throwaway test (panel shown, message as quoted), not kept. Census: no page source in objectui (apps, examples, content, skills) or in objectstackorigin/main(examples, content, skills, packages) declares the name. The changeset states this case.Serial
Open PRs, read again just before this PR (12 open, every file list complete under 100 entries): none touches
react-page.tsx, thereact-page-*tests,react-pages.mdor the10887-*changesets. One touchesObjectView.tsx: PR #10967 (opened after this claim, route 2's named-view reads).git merge-tree --write-treeof this head against its head600cf8901: clean, exit 0. Its hunks and this PR's two comment hunks do not overlap. PR #10777 touchesPageView.tsx, its tests and its own changesets only.Acceptance notes
ObjectView'sfetchDrawsViewnames the types it excludes (grid,tree,chart) rather than the typesgenerateViewSchemadraws, so a view typegenerateViewSchemahas no case for (it returnsnull, andrenderContentfalls through toObjectGrid, which queries for itself) still subscribes here and re-reads once per matching event, for rows nothing renders. Pre-existing shape of member 1; carrier: none.loadingat the start of every run, including a bus re-read, andObjectCalendarswaps to its loading placeholder while it is set, the same flash as the calendar's own objectui#10572 reader. A silent re-read would change behaviour; carrier: none.skills/objectstack-ui/references/react-blocks.md, written bypackages/spec/scripts/build-react-blocks-contract.ts) lists the injected scope asReact,useAdapter,data,variables,page. After this lands that list is incomplete. docs(react-pages,skills,showcase): teach the data-invalidation nonce in a react page's live-data effect, and opt the two showcase react pages in (ruling A on objectui#10887 member 3) objectstack#20395 namesreact-pages.mdx,rules/pages.mdand the showcase pages, not this generator; it is the natural carrier.Generated by Claude Code