Repository navigation
docs(react-pages,skills,showcase): teach the data-invalidation nonce in a react page's live-data effect, opt the two showcase react pages in (#20395) - #21045
Conversation
…in a react page's live-data effect The kind:'react' author scope injects useDataInvalidation (objectui deca847a8, in the pinned console db11afd49). The react-pages guide's scope table and Live data example, the objectstack-ui skill's react scope list, the generator's injected-scope line and Live-data note (react-blocks.md regenerated through gen:react-blocks), and the two showcase react pages that read through useAdapter in effects now carry the nonce, so a page action elsewhere re-runs the read in place with no remount. Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg Co-authored-by: Claude <noreply@anthropic.com>
…wo bus nonces The rollup effect's dependency list now names projectChanged and invoiceChanged (the injected useDataInvalidation nonces), so the harness that evaluates the lifted call verbatim supplies both as numbers that never move; the four executed-contract assertions read the same body once, as before. Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewServed-tier: Inputs: card #20395 (body; comments 5865123040, 5870067887, 5923248286, 5923384068, 5924107460 as stored after its one edit); PR #21045 (body read twice — 13228 bytes at creation, 15213 bytes after the seat's append; file list, 7 files; the API diff, byte-identical to ① Derived judgments(a) Every sentence about the hook, read at (b) The Live data example. (c) (d) The generator. The diff to (e) The two showcase pages. A The dev's double-run note, judged: for CRM Workbench it is a correct upper bound — a save in the page's own (f) The test harness, outside the claim's file surface. (g) Scope. Seven files; under (h) The PR body. At creation (13228 bytes): line 1 ② Semver levelNothing published changes. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读改了什么:objectui 那一半(PR objectui#10973,已在本仓钉住的控制台 为什么改:裁决 A(objectui#10887 member 3,批次 #233 第 2 项,你 9 月 28 日「同意」)。PageView 页面动作后的整页 remount 已移除,读数据的块都走失效总线;react 页面自己的 风险与代价(含回滚):只改文档、技能、示例与一个示例测试,不碰协议键、不发包( 席位意见:建议批准。三条观察留给后续(技能示例里对 ListView 的 key-remount 已属冗余; 你要做的:在本 PR 上留一条 APPROVED review(os-zhuang 或 hotlong)。本席本班收班,落地(清 Generated by Claude Code |
Fixes #20395
Clause-②: no — docs, skill prose, two strings in a generator and two example pages; no protocol key moves.
The objectstack half of ruling A on objectui#10887 member 3 (ruling
5864668610, maintainer 「同意」 2026-09-28). The objectui half landed in PR objectui#10973 (mergedeca847a8):ReactKindPageinjectsuseDataInvalidationinto thekind: 'react'author scope. This repo's pinned console (.objectui-sha=db11afd4967cd9d39381c5e21dc2deec9d706204, 78 commits ahead of that merge, behind 0 — the seat's REST compare at unlock) carries it, so the six sites that still taught or ran the pre-hook pattern are brought to it.The hook, read at the pinned sha
Read with
git show db11afd49:PATHin the objectui checkout aftergit fetch --depth=1 origin db11afd49…, not at objectui HEAD:packages/components/src/renderers/layout/react-page.tsx— the scope memo spreadsbuildComponentScope(adapter)and addsuseAdapter,useDataInvalidation,data,variables,page; the memo still depends on[schema, adapter]only, so the scope identity, and with it the page's own state, is unchanged by the new member.packages/react/src/data-invalidation.ts:106—export function useDataInvalidation(objectName?: string, recordId?: string): number. The nonce is a NUMBER:useState(0), incremented once per bus change thatdataChangeMatches(sameobjectName, or a change reported on'*'; a record-scoped change also stales a list reader that passed norecordId). With noobjectNamethe hook subscribes to nothing and returns 0 forever. Its docblock prescribes the use this PR teaches: put the nonce in the fetch effect's dependency array; the refetch happens in place, no remount.useMutationInvalidationBridge(dataSource), mounted once inpackages/app-shell/src/console/AppContent.tsx:449, so every create/update/delete through the console's data source reaches the readers.ListView(renderers/basic/data-list.tsx:137),ObjectView,elements.tsxandrecord-picker.tsxread it at that sha.Every sentence the six files now carry about the hook is true of that sha.
What changed (7 files, +33 / -15)
content/docs/ui/react-pages.mdx— the scope table gains theuseDataInvalidationrow; the Live data example readsconst changed = useDataInvalidation('showcase_invoice')and nameschangedin the effect's dependencies; one paragraph after the block says why: a page action elsewhere writes the object, the bus reports it, the nonce moves, the effect re-runs in place — no remount, the page's state survives. The in-page link#live-dataresolves (check:doc-anchorsgreen; github-slugger).skills/objectstack-ui/rules/pages.md(Tier H) — one bullet in the react scope list, in the file's voice. Paid for by deleting the blockquote's two sentences that restated the bullet above them (the contract covers the curated core set; runtime-injected blocks read props from the registry;record:*rejected) — the bullet still says all three. No other line re-wrapped.packages/spec/scripts/build-react-blocks-contract.ts— the## Injected scopeline gainsuseDataInvalidation; the Live-data note gains the nonce sentence.skills/objectstack-ui/references/react-blocks.mdregenerated throughpnpm --filter @objectstack/spec gen:react-blocks, never hand-edited: exactly those two lines moved (the note paragraph and the scope line), 115 → 115 lines;check:react-blocksgreen.references/_index.mduntouched (not this generator's output).examples/app-showcase/src/ui/pages/crm-workbench.page.ts—const projectChanged = useDataInvalidation('showcase_project'); the KPI effect's dependencies[refreshStats, reloadKey]→[refreshStats, reloadKey, projectChanged], plus a two-line comment.examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts— two nonces,showcase_projectandshowcase_invoice(the two objects the rollup reads; the hook takes one object, and a reader on'*'matches only wildcard changes); dependencies[adapter, sel, reload]→[adapter, sel, reload, projectChanged, invoiceChanged]; the comment's "reload bumps re-run it" now reads "reload bumps and bus writes re-run it".examples/app-showcase/test/react-page-adapter-query-contract.test.ts— NOT in the claim's file surface; a bounded in-place fix, see Deviations.What a page action now does on the two showcase pages
The example app is rendered by the pinned console, so the new dependency is a runtime behaviour change there:
showcase_projectthe bus reports — a save in the page's own ObjectForm, a row action on its ListView, a record action from another block or page — movesprojectChanged, and the KPI strip (Total / Active) re-reads in place. Before, it re-read only when the page's ownafterSavebumpedreloadKey, so a write from anywhere else left the strip stale until PageView's remount (which PR objectui#10777 removes).showcase_projectorshowcase_invoice(an invoice marked paid from the Invoices ListView beside it, say) re-runs the Account 360 rollup — Projects / Invoices / Open AR — in place, withseland the drawer state kept. Before, only the drawer's own save (reload) did.reloadKey/reloadstay in the dependency lists because they also key the ListView and ObjectForm remounts those pages do (key={reloadKey}). After this change a save in the page's own form runs the effect twice — the key bump and the bus event — one redundant read, same answer. Retiring the key-remount pattern is a separate decision (Acceptance notes).skills/**readingsLines and tokens; tokens are the ratchet's
ceil(utf8 bytes / 4), read fromcheck-skills-token-ratchetat2f2fa11d7(before) andc1770a387(after).skills/objectstack-ui/rules/pages.md(touched, authored)skills/objectstack-ui/references/react-blocks.md(touched, generator-owned: measured, not ratcheted)skills/objectstack-ui/**(11 files)skills/**, ratcheted (authored) totalskills/**, bundle total (shipped tree)skills/*/SKILL.mdsummedNet: the authored skill prose shrinks by 5 tokens; the +69 in the bundle is the generator's output. No ceiling moved, no re-wrap bought a line.
Changeset
None;
skip-changesetis the seat's to apply.skills/**andcontent/docs/**appear in no package'sfiles[](everypackage.jsonunderpackages/andexamples/walked: zero nameskills,content/docsor a parent path);@objectstack/example-showcaseisprivate: true;packages/spec/scripts/is outside@objectstack/spec'sfiles[](dist,json-schema,liveness,prompts,llms.txt,README.md,src/**/*.zod.ts,CHANGELOG.md,api-surface,spec-changes.json). Noprivate: falsepackage is touched.Tests and gates (head
c1770a387)Heavy runs went through
scripts/pm/os-verify-lock.sh(slotissue-20395; one 540 s queue-timeout behind an objectui full build, read as NOT MEASURED and resumed on the same slot).pnpm --filter @objectstack/spec build— exit 0 (held 72 s).pnpm turbo run build --filter="@objectstack/example-showcase^..." --concurrency=2— 60/60 tasks, exit 0 (held 318 s); thenpnpm turbo run build --filter="!@objectstack/docs" --concurrency=2— 72/72, 60 cached, exit 0 (held 42 s), so the whole-workspace gates could measure.typecheck(tsc --noEmit) exit 0;validate(objectstack validate) exit 0 with advisory warnings only, all pre-existing and none on the two pages;vitest run --maxWorkers=2at2ecc5d7ff, before the harness fix: 28 files passed, 1 failed —react-page-adapter-query-contract.test.ts× 4,ReferenceError: projectChanged is not defined; atc1770a387, clean tree:Test Files 29 passed (29),Tests 385 passed (385)(held 46 s).dispatch-gates --commandsat2f2fa11d7re-derived on this branch with--repo objectstack-ai/objectstack— identical list.--ranatc1770a387derives 94 families (the test file addscheck:engine-double-contract,check:query-options-erasure,check:type-check-coverage,check:type-check-debt,check:where-matcher, all run green):94 derived famil(ies) accounted for — 94 run, 0 NOT-MEASURED (a DERIVED zero). Also run, outside the derivation:pnpm --filter @objectstack/spec run check:generatedexit 0 andcheck:skill-docsexit 0. Eight gates first answered exit 3 (prerequisite: nodist/) and were re-run green after the builds —docs-transcript-drift,dts-closure,dual-build-cjs-loads,lean-entry-closure,sourcemap-no-sources-content, speccheck:skill-examples(259 prose examples type-check), lintcheck:doc-formula-expressionsandcheck:doc-security-posture; speccheck:docswas re-run after the spec build and that run is the one cited.check-skills-token-ratchetand its--self-test,check:skill-refs,check:skill-docs,check:react-blocks,check:doc-authoring,check-doc-frontmatter,check-docs-section-name,check-doc-route-spelling --advisory,check:doc-anchors,check:skill-frame-sync,check:skill-identifier-liveness,check:role-word,check:pm-governed-merges,check-adr-0087-registration --base origin/main,check:react-page-adapter-contract,check:page-declaration-shape,check:examples-live-imports,check:nul-bytes.check-governed-merges --branch: GOVERNED, landing tier H viaskills/**× 2; 48 changed lines, under the 5000 threshold. Draft, four-piece; no seat readies, queues or arms auto-merge on it.pnpm lint(repo-wide eslint, CI's run); the showcase Playwright smoke (test:smoke, a browser lane); and no browser run of the two pages — the runtime behaviour above is derived from the pinned-sha readings, and objectui's pin (react-page-invalidation.test.tsxin PR objectui#10973) is the one that drives the real renderer with a page in the taught shape.Deviations
examples/app-showcase/test/react-page-adapter-query-contract.test.ts(6 insertions, 3 deletions). The test lifts the renewals page'sReact.useEffect(...)call verbatim out ofsourceand evaluates it throughnew Function('React', 'adapter', 'sel', 'reload', 'setRelated', …); the dependency list now namesprojectChangedandinvoiceChanged, so the lifted call threw four times. The harness gains the two parameters, bound to0, and a three-line comment. Bounded in-place fix, four conditions: ① the same change this card makes — the effect's dependency list is what the card edits, and the harness enumerates exactly that list; ② mechanical, the shape pinned by the existingnew Functionsignature; ③ the file's last touches onorigin/mainare 2026-08-24 (eb10fcfd4) and 2026-08-21 (6cca75c3a) and no claim on it is visible from this worktree — the seat's open-PR file-list read at unlock covered the six named files, not this one, so the seat confirms; ④ inside the showcasepnpm testfamily already in this PR's verification set, no new verification surface. The claim's file surface wants this path appended in the same round (the seat's write).check:commit-card-trailers) refused it before anything left the worktree; amended to the model-free pair and pushed. Nothing else deviated.Acceptance notes (observed, not filed)
skills/objectstack-ui/rules/pages.md's Master/detail example andcrm-workbench.page.tskey the ListView on a counter (key={reload}/key={reloadKey}) to refresh it after a save. At the pinned shaListViewreads the bus itself (data-list.tsx:137), so that remount is redundant for the refresh; it still works and the runtime honours it, so this is a boundary note and not a card — it is also the patterndata-invalidation.ts's header says the bus "deliberately replaces". Retiring it changes what the example teaches. carrier: this lane's seat, when it chooses; noted, not filed.kind:'react'paragraph: "os builddoes NOT lint react source" stands a few lines under "os validaterejects them here (react-block-needs-record-context)". Two different commands, andos validatedoes read reactsource(that rule andpage-source-className-tailwind), so the sentence holds foros buildonly as far as it goes; not re-worded here (not this card, and the file has 16 tokens of headroom). carrier: none; noted, not filed.Generated by Claude Code
维护者速读(草稿)
改了什么。 objectui 已把
useDataInvalidation(数据失效总线的读端,返回一个数字 nonce)注入kind: react页面的作者作用域(objectui PR 10973,已在本仓钉住的控制台db11afd49内)。本 PR 是 objectstack 这一半:react-pages 文档的作用域表与 Live data 示例、objectstack-uiskill 的 react 作用域规则(一条)、生成器build-react-blocks-contract.ts的两处字符串及其产物react-blocks.md(经脚本重新生成)、以及 showcase 的两个 react 页面(crm-workbench、renewals-pipeline)在useAdapter读数据的 effect 依赖里加入 nonce。附带一处测试 harness 的机械修正(见 Deviations)。为什么改。 裁决 A(批次 #233 第 2 项,2026-09-28「同意」):PageView 在页面动作后的整页 remount 将被移除(objectui 10777),所有读数据的块都改走失效总线;react 页面自己的
useAdapter读取若不接总线,会成为唯一在页面动作后留下陈旧数据的面。文档与 skill 教的示例若不带 nonce,AI 照抄出来的页面默认就是陈旧的。风险与代价(含回滚)。 纯文档 / skill / 示例改动,无协议键变动(
Clause-②: no),不发布任何 npm 包(skills、content/docs 不在任何包的files[],showcase 为 private)。skill 棘轮:pages.md5681 → 5676 tokens(上限 5692 不动),新增那条规则靠删掉同文件里重复的两句付账;生成文件 +69 tokens 不受棘轮约束。运行时影响仅限 showcase 示例应用的两个页面:页面内自己保存后 effect 会跑两次(key 自增 + 总线事件),多一次读、结果不变。回滚:revert 本 PR 即可,无迁移。席位意见。 (留空,席位定稿)
你要做的。 这是 Tier H(
skills/**)四件套草稿 PR:需要os-zhuang或hotlong的 APPROVED review,然后由 domain:skills 席位落地。(本节由席位在 PR 创建后补贴:dev 报告 5924107460 申报的遗漏。)