Repository navigation
Commit 63d1a7c
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 (merge `deca847a8`): `ReactKindPage` injects
`useDataInvalidation` into the `kind: '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:PATH` in the objectui checkout after `git
fetch --depth=1 origin db11afd49…`, not at objectui HEAD:
- `packages/components/src/renderers/layout/react-page.tsx` — the scope
memo spreads `buildComponentScope(adapter)` and adds `useAdapter`,
`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
that `dataChangeMatches` (same `objectName`, or a change reported on
`'*'`; a record-scoped change also stales a list reader that passed no
`recordId`). With no `objectName` the 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.
- The bus is fed by `useMutationInvalidationBridge(dataSource)`, mounted
once in `packages/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.tsx` and `record-picker.tsx` read 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)
1. `content/docs/ui/react-pages.mdx` — the scope table gains the
`useDataInvalidation` row; the Live data example reads `const changed =
useDataInvalidation('showcase_invoice')` and names `changed` in 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-data` resolves (`check:doc-anchors` green;
github-slugger).
2. `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.
3. `packages/spec/scripts/build-react-blocks-contract.ts` — the `##
Injected scope` line gains `useDataInvalidation`; the Live-data note
gains the nonce sentence.
`skills/objectstack-ui/references/react-blocks.md` regenerated through
`pnpm --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-blocks` green. `references/_index.md`
untouched (not this generator's output).
4. `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.
5. `examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts` — two
nonces, `showcase_project` and `showcase_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".
6.
`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:
- CRM Workbench: any write to `showcase_project` the bus reports — a
save in the page's own ObjectForm, a row action on its ListView, a
record action from another block or page — moves `projectChanged`, and
the KPI strip (Total / Active) re-reads in place. Before, it re-read
only when the page's own `afterSave` bumped `reloadKey`, so a write from
anywhere else left the strip stale until PageView's remount (which PR
objectui#10777 removes).
- Renewals Pipeline: any reported write to `showcase_project` or
`showcase_invoice` (an invoice marked paid from the Invoices ListView
beside it, say) re-runs the Account 360 rollup — Projects / Invoices /
Open AR — in place, with `sel` and the drawer state kept. Before, only
the drawer's own save (`reload`) did.
- Kept on purpose, outside this card: `reloadKey` / `reload` stay 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/**` readings
Lines and tokens; tokens are the ratchet's `ceil(utf8 bytes / 4)`, read
from `check-skills-token-ratchet` at `2f2fa11d7` (before) and
`c1770a387` (after).
| surface | lines before → after | tokens before → after | ceiling |
| --- | --- | --- | --- |
| `skills/objectstack-ui/rules/pages.md` (touched, authored) | 453 → 453
| 5681 → 5676 (headroom 11 → 16) | 5692, not moved |
| `skills/objectstack-ui/references/react-blocks.md` (touched,
generator-owned: measured, not ratcheted) | 115 → 115 | 3340 → 3409 | —
|
| whole package `skills/objectstack-ui/**` (11 files) | 2231 → 2231 |
30770 → 30834 | — |
| whole catalog `skills/**`, ratcheted (authored) total | — | 143505 →
143500 of 154938 | — |
| whole catalog `skills/**`, bundle total (shipped tree) | — | 154058 →
154122 | — |
| all `skills/*/SKILL.md` summed | 4395 → 4395 | — | — |
Net: 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-changeset` is the seat's to apply. `skills/**` and
`content/docs/**` appear in no package's `files[]` (every `package.json`
under `packages/` and `examples/` walked: zero name `skills`,
`content/docs` or a parent path); `@objectstack/example-showcase` is
`private: true`; `packages/spec/scripts/` is outside
`@objectstack/spec`'s `files[]` (`dist`, `json-schema`, `liveness`,
`prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`,
`api-surface`, `spec-changes.json`). No `private: false` package is
touched.
## Tests and gates (head `c1770a387`)
Heavy runs went through `scripts/pm/os-verify-lock.sh` (slot
`issue-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); then `pnpm turbo
run build --filter="!@objectstack/docs" --concurrency=2` — 72/72, 60
cached, exit 0 (held 42 s), so the whole-workspace gates could measure.
- Showcase, under the lock: `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=2`
at `2ecc5d7ff`, before the harness fix: 28 files passed, 1 failed —
`react-page-adapter-query-contract.test.ts` × 4, `ReferenceError:
projectChanged is not defined`; at `c1770a387`, clean tree: `Test Files
29 passed (29)`, `Tests 385 passed (385)` (held 46 s).
- Dispatch gates: the 89 commands of `dispatch-gates --commands` at
`2f2fa11d7` re-derived on this branch with `--repo
objectstack-ai/objectstack` — identical list. `--ran` at `c1770a387`
derives 94 families (the test file adds `check: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:generated` exit 0 and `check:skill-docs`
exit 0. Eight gates first answered exit 3 (prerequisite: no `dist/`) and
were re-run green after the builds — `docs-transcript-drift`,
`dts-closure`, `dual-build-cjs-loads`, `lean-entry-closure`,
`sourcemap-no-sources-content`, spec `check:skill-examples` (259 prose
examples type-check), lint `check:doc-formula-expressions` and
`check:doc-security-posture`; spec `check:docs` was re-run after the
spec build and that run is the one cited.
- By name, each exit 0: `check-skills-token-ratchet` and 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`.
- Governed: `check-governed-merges --branch`: GOVERNED, landing tier H
via `skills/**` × 2; 48 changed lines, under the 5000 threshold. Draft,
four-piece; no seat readies, queues or arms auto-merge on it.
- Not run locally: `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.tsx` in PR objectui#10973) is the one
that drives the real renderer with a page in the taught shape.
## Deviations
- File surface +1:
`examples/app-showcase/test/react-page-adapter-query-contract.test.ts`
(6 insertions, 3 deletions). The test lifts the renewals page's
`React.useEffect(...)` call verbatim out of `source` and evaluates it
through `new Function('React', 'adapter', 'sel', 'reload', 'setRelated',
…)`; the dependency list now names `projectChanged` and
`invoiceChanged`, so the lifted call threw four times. The harness gains
the two parameters, bound to `0`, 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
existing `new Function` signature; ③ the file's last touches on
`origin/main` are 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 showcase `pnpm test` family 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).
- The first commit's co-author trailer carried a model name and the
pre-push hook (`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 and
`crm-workbench.page.ts` key the ListView on a counter (`key={reload}` /
`key={reloadKey}`) to refresh it after a save. At the pinned sha
`ListView` reads 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 pattern
`data-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.
- Same file, the `kind:'react'` paragraph: "`os build` does NOT lint
react source" stands a few lines under "`os validate` rejects them here
(`react-block-needs-record-context`)". Two different commands, and `os
validate` does read react `source` (that rule and
`page-source-className-tailwind`), so the sentence holds for `os build`
only 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](https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg)_
## 维护者速读(草稿)
**改了什么。** objectui 已把 `useDataInvalidation`(数据失效总线的读端,返回一个数字 nonce)注入
`kind: react` 页面的作者作用域(objectui PR 10973,已在本仓钉住的控制台 `db11afd49` 内)。本 PR
是 objectstack 这一半:react-pages 文档的作用域表与 Live data 示例、`objectstack-ui`
skill 的 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.md` 5681 → 5676 tokens(上限 5692 不动),新增那条规则靠删掉同文件里重复的两句付账;生成文件
+69 tokens 不受棘轮约束。运行时影响仅限 showcase 示例应用的两个页面:页面内自己保存后 effect 会跑两次(key 自增
+ 总线事件),多一次读、结果不变。回滚:revert 本 PR 即可,无迁移。
**席位意见。** (留空,席位定稿)
**你要做的。** 这是 Tier H(`skills/**`)四件套草稿 PR:需要 `os-zhuang` 或 `hotlong` 的
APPROVED review,然后由 domain:skills 席位落地。
_(本节由席位在 PR 创建后补贴:dev 报告 5924107460 申报的遗漏。)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6fc22b7 commit 63d1a7c
7 files changed
Lines changed: 33 additions & 15 deletions
File tree
- content/docs/ui
- examples/app-showcase
- src/ui/pages
- test
- packages/spec/scripts
- skills/objectstack-ui
- references
- rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| |||
175 | 176 | | |
176 | 177 | | |
177 | 178 | | |
| 179 | + | |
178 | 180 | | |
179 | 181 | | |
180 | 182 | | |
| |||
187 | 189 | | |
188 | 190 | | |
189 | 191 | | |
190 | | - | |
| 192 | + | |
191 | 193 | | |
192 | 194 | | |
193 | 195 | | |
194 | 196 | | |
195 | 197 | | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
196 | 203 | | |
197 | 204 | | |
198 | 205 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
| |||
56 | 59 | | |
57 | 60 | | |
58 | 61 | | |
59 | | - | |
| 62 | + | |
60 | 63 | | |
61 | 64 | | |
62 | 65 | | |
| |||
Lines changed: 7 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
57 | 62 | | |
58 | 63 | | |
59 | 64 | | |
60 | 65 | | |
61 | 66 | | |
62 | 67 | | |
63 | 68 | | |
64 | | - | |
| 69 | + | |
65 | 70 | | |
66 | 71 | | |
67 | 72 | | |
| |||
99 | 104 | | |
100 | 105 | | |
101 | 106 | | |
102 | | - | |
| 107 | + | |
103 | 108 | | |
104 | 109 | | |
105 | 110 | | |
| |||
Lines changed: 6 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
119 | 122 | | |
120 | | - | |
121 | | - | |
| 123 | + | |
| 124 | + | |
122 | 125 | | |
123 | | - | |
| 126 | + | |
124 | 127 | | |
125 | 128 | | |
126 | 129 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
139 | | - | |
| 139 | + | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
| |||
169 | 169 | | |
170 | 170 | | |
171 | 171 | | |
172 | | - | |
| 172 | + | |
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
115 | | - | |
| 115 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
169 | 169 | | |
170 | 170 | | |
171 | 171 | | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
172 | 175 | | |
173 | 176 | | |
174 | 177 | | |
| |||
200 | 203 | | |
201 | 204 | | |
202 | 205 | | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
| 206 | + | |
207 | 207 | | |
208 | 208 | | |
209 | 209 | | |
| |||
0 commit comments