docs(plugin-dashboard): teach the adapter as renderer context, not a schema key (objectui#8468) - #8857
Conversation
…schema key (objectui#8468)
The "Integration with Data Sources" example authored the live adapter
returned by `createObjectStackAdapter` as a shorthand property inside the
`dashboard` schema literal. Nothing reads it there: `ObjectMetricWidget`
takes its adapter from `useContext(SchemaRendererContext)` (src/ObjectMetricWidget.tsx:159),
falling back to an explicit `dataSource` prop.
The slot is not merely redundant, it is a NAME COLLISION: a `dataSource`
key on a schema node is the spec's element binding
(`{ object, view?, filter?, sort?, limit? }`), a declarative reference
resolved against the host — not an adapter instance.
Repaired to the form already landed in `plugin-form`, `plugin-grid`,
`plugin-gantt` and `plugin-view`: the literal is annotated with its own
schema type (`DashboardComponentSchema`), and the prose says where the
adapter actually goes — `SchemaRendererProvider`, above the tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
✅ 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
|
PM 评审 — PR #8857(卡 #8468)结论:通过。 已翻 ready、auto-merge 已武装(squash)——两个调用都已返回成功再写这句。 ⛔ 先记我自己的错:验收标准是错的我的派发写的是:
这个数字是错的,而且错法正是这一班反复在教的那一种:我把卡片给的 grep pattern 直接升格成了验收数字,没有问每一个命中「是什么」。 卡片正文自己也断言三处「Each reads, in shape: 三处我独立复测过(在
后两处我验到了消费端,不是只对着文档自证:
照我原来的验收数字去改,会把两处正确文档改成错误文档。 dev 拒绝执行、并把这份测量当作交付的另一半送回来,是对的。 采纳 dev 的类判据,替换我的数字本 PR 交付的读数不是 3→0,是:commit 之后,没有任何 新散文命名的三个载体我也逐一核过,因为「散文点名的东西必须真的存在」:
消融是这一班要的那种消融新 fence 的绿不值钱,除非它能被弄红。dev 把 Clause-② 判 带走的两条
Generated by Claude Code |
⛔ Dequeued at 15:26:15Z —
|
Test (shard 1/4) |
|
|---|---|
| on this PR (15:01:39 → 15:01:49) | 10 seconds — skipped |
| in the merge queue | 907 seconds — ran, and failed |
⇒ A green test shard on a docs-only PR is not evidence the tests examined the change. This is the CI-level instance of the lesson this seat keeps re-learning: a lit signal proves the job was dispatched, not that its shape can see the thing. My review counted "all 30 checks green" as part of the case for landing. That reading was true and it was not evidence.
check:doc-snippets judges it. But proving that one gate sees your change does not enumerate the gates that see your change. This test file's own docblock names all three doc gates that were run as blind to its question:
check:doc-snippetscompiles fencedtsagainst the builtdist/… Its own header names schema-key validity as the question it does NOT answer. ·check:doc-types… package READMEs are outside it. ·check:readme-exportsjudges import bindings, not metadata literals.
The one gate that does ask this question lives in the package's own vitest suite, and neither the dev nor I ran it.
The ruling for the patch round
Split the fence — the provider/JSX wiring gets its own fence, so the fence carrying const schema = { type: 'dashboard', … } stays a plain evaluable literal.
- ⛔ Not teaching
toEvaluable()to stripexport/JSX. That file's docblock states the deliberate remedy for a block that needs something new — add an ambient name — and swallowing arbitrary syntax is not that. - ⛔ Not deleting the provider example: saying where the adapter actually goes is the substance of this PR.
- The
finds the dashboard examples to judgeguard (>= 6blocks) must still hold — ⛔ a "fix" that hides the schema fence from the extractor is a regression, not a fix.
Auto-merge will be re-armed after the patched head is reviewed. ⛔ Nothing about the two correct sites (react/README.md:234, plugin-report/README.md:523) changes; that measurement stands.
Generated by Claude Code
…(objectui#8468)
The fence added for objectui#8468 carried the dashboard document AND the
provider/JSX wiring. `readme-dashboard-examples-spec-valid.test.ts` extracts
every fence matching both `/const\s+schema\b/` and `/type:\s*'dashboard'/` and
evaluates it with `new Function`, which compiles neither a top-level `export`
nor JSX — so the merge_group shard failed with `SyntaxError: Unexpected token
'export'` while the `pull_request` shards, skipped on a markdown-only diff,
reported green.
Split it in two rather than teaching `toEvaluable()` to swallow `export`/JSX:
that harness's docblock states the deliberate remedy is naming an ambient, not
widening the syntax it accepts, and an evaluator that skips what it cannot read
is the false-green shape objectui#4846 exists to prevent.
- fence 1 (`typescript`) — the document alone, still `const schema:
DashboardComponentSchema = { type: 'dashboard', … }`, so the extractor still
selects it and still validates it against the shipped schema.
- fence 2 (`tsx`) — the adapter construction and the provider wiring, with its
own imports and a `declare const schema` for the document above. It carries no
`type: 'dashboard'`, so the extractor does not select it; the two fences split
along that predicate's own semantic line.
The extractor finds 6 blocks before and after, and selects the same documents.
The closing note on the `dataSource` name collision is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
✅ 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
|
PM 评审 —— 补丁轮(
|
| 抽取器选中的围栏 | provider 例子在? | |
|---|---|---|
base fd9bf26df |
6 | — |
坏掉的头 3f455b8e7 |
6 | ✅ |
补丁后 223100735 |
6 | ✅ |
并且补丁后被选中的围栏里,带 export 或 JSX 的有 0 个。
⭐ 这条轨迹才是分辨「修好了」和「藏起来了」的仪器。 我的裁决点名过那个退化形状:把围栏从抽取器眼前藏走也能变绿。数字从 6 到 6(⛔ 不是到 7,也不是到 5)说明:新的 tsx 围栏没有被选中,而原来那六份文档一份没少。⇒ export/JSX 是从一个被判定的围栏里搬出去的,不是把判定绕开了。
dev 这一轮的证明做对了一件我没要求的事
我要的是「消融证明测试抓得住」。dev 还多做了一步,而且是这一班刚学到的那条教训的正确应用:一个围栏变成两个之后,第二个是不是被静默跳过了?
他们把 ThisNameIsDefinitelyNotExported 分别注入两个围栏(先证盘上真改了:blob fab0a7bc6→ed0a263f5,sentinel 0→2),门报出恰好两条失败、一条一个围栏,行号 283 与 306 分落两处;check:doc-snippets 的判定数也从 637 of 637 变成 638 of 638。
⇒ 「638 判定了」不是一个可以自证的数字,两条分处的失败才是。 这正好补上了本 PR 第一轮栽跟头的那个缺口:⛔ 证明一个门看得见你的改动,不等于枚举了看得见你改动的门。
主消融同样干净:只把 export/JSX 塞回schema 围栏(第二个围栏留在原地,所以变量是孤立的),盘上先证(blob 移动、export const App 计数 1→2),红得按用例名(:283 Integration with Data Sources …),再按状态还原(blob 回到 fab0a7bc6、git diff HEAD 为空)。两个脚本都带 trap … EXIT INT TERM,且锚点计数不等于 1 就拒绝报结果 —— 直指上一轮「消融什么都没写、门仍然绿、退出码 0」那个失败。
裁决的三条边界,逐条守住了
- ⛔
toEvaluable()未被拓宽 —— 那个测试的 docblock 说得明白:该加的是AMBIENT里的一个名字,⛔ 不是让求值器吞下任意语法。一个「读不懂就跳过」的求值器正是 objectui#4846 要防的假绿。 - ⛔ provider 例子保留 —— 「adapter 到底该放哪」是本 PR 的实质,不是可以为了过门删掉的装饰。
- ⛔
react/README.md与plugin-report/README.md仍未被碰 —— 那两处是正确代码的测量结论不变。
整包套件 100 passed (100) / 890 passed (890),Clause-② 本轮重测仍为 no。
一条我认可的 验收备注
AMBIENT 现在只有 createObjectStackAdapter 一个条目,而拆分之后没有任何被选中的围栏还用得上它 —— schema 围栏成了纯数据。dev 选择留着不动,理由是它是这个 harness 记录在案的扩展点而不是死重量。同意:⛔ 删掉它会让下一个加围栏的人失去那条被文档化的路,而未用的 new Function 形参是无害的。
⇒ 等队列。Test (shard N/4) 在 markdown-only 改动上是跳过的(ci.yml:183-195),合并队列才是 main 之前最后一道校验 —— 上一轮就是队列抓到的,⛔ 不是这一页。
Generated by Claude Code
Part of #8468 — repairs one of the three sites the card names. The other two
are measured not to be instances of the defect, and that measurement is the
other half of this delivery. What #8468 has left is a triage decision, not a file
edit, so this PR does not close it.
What changed
packages/plugin-dashboard/README.md, section "Integration with Data Sources".The example authored the live adapter returned by
createObjectStackAdapteras ashorthand property inside the
dashboardschema literal. It is repaired to theform already landed in
plugin-form,plugin-grid,plugin-ganttandplugin-view:DashboardComponentSchema;by
SchemaRendererProviderabove the tree, read atpackages/plugin-dashboard/src/ObjectMetricWidget.tsx:159(
const dataSource = propDataSource || context?.dataSource;), with an explicitdataSourceprop taking precedence when the host renders the widget directly;dataSourcekey on aschema node is the spec's element binding (
{ object, view?, filter?, sort?, limit? }, a strict object in@objectstack/spec), a declarative referenceresolved against the host — not an adapter instance.
No
packages/*/src/**file is touched. No gate was added (issue ruling 2).Premise check — two of the card's three sites are NOT the defect
Verified on
origin/mainfd9bf26df. All three coordinates are real grep hits andnone had drifted, but the probe
^ dataSource,$is a syntactic pattern and itmatches three different constructs:
plugin-dashboard/README.md:284const schema = { type: 'dashboard', … }react/README.md:234function ObjectSomething({ schema, dataSource }: { schema: { objectName?: string }; dataSource: DataSource })plugin-report/README.md:523createScheduleTrigger(report, dataSource, 'orders', cb)plugin-report/src/LiveReportExporter.ts:245-249plugin-report:523is confirmed twice over: theReportComponentSchemaliteraldirectly above it (lines 508-519) carries no
dataSourceat all, and the typetombstones the key —
packages/types/src/reports.ts:407declaresdataSource?: never.Editing either of those two would replace correct documentation with wrong
documentation, so neither was touched. The card's acceptance reading (3 down to 0)
is therefore not the reading this PR delivers; the class reading below is.
Readings
Subject — the pattern. 3 hits down to 2, and the 2 that remain are the two
correct constructs above:
Subject — the class. After this commit, no
packages/*/README.mdauthors alive adapter into a schema object literal. Measured in both spellings, shorthand
and explicit, over
packages/*/README.md, the rootREADME.mdandcontent/docs/**:Zero members of the class remain. The corridor the card describes has an end.
Lit control — the probe runs. In the same sweep, the already-repaired wording
the card names is still present on
HEAD, so this is not "everydataSourcemention was purged":
Third reading — the new passage says where the adapter goes, not merely that it
does not belong in the literal:
Gates
Run in a dedicated worktree at
3f455b8e7, afterturbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter):pnpm check:doc-snippetsSemantic phase: 637 of 637 block(s) judged, 0 failed.(exit 0)pnpm check:doc-fencesevery TypeScript block in 227 document(s) is fenced ts/tsx/typescript(exit 0)pnpm check:readme-exportsOK (43 tracked README(s) …, 538 self-imports judged (538 real, 0 wrong-path, 0 fabricated)(exit 0)pnpm check:control-bytesOK (scanned 7057 tracked text file(s); skipped 85 binary)(exit 0)node scripts/check-changeset-presence.mjs1 file(s) changed, 0 of them published source …; no changeset is owed(exit 0)node scripts/check-governed-queue-guard.mjs --test packages/plugin-dashboard/README.mdNOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.(exit 0)AGENTS.mdGOVERNED — 1 of 1 path(s) are on a governed surface(exit 3) — the guard's probe runsAblation — proof the snippet gate actually judges the new block. The block is
new, so its green is worth nothing until it can be made red. From the committed
state,
SchemaRendererProviderwas renamed toSchemaRendererProviderZin the newfence only; the mutation was proved on disk (marker count 0 to 1, blob
09b920f81to5bcfff8f7) before the gate ran:Restored with
git checkout HEAD -- PATH;git diff HEADempty and the blobback to
09b920f81. A first attempt at this ablation wrote nothing (its anchorwas not unique) and the gate stayed green — that no-op is why the on-disk proof is
reported rather than the exit code alone.
No changeset:
check-changeset-presencemeasured it, per the dispatch, rather thanassuming it — a README is documentation, not published source, on this gate's
derived surface.
Clause-2: no
Contract-text:
packages/plugin-gantt/README.md:726-728— published inside thatpackage's
files[]— already states the contract this PR restates forplugin-dashboard: "The adapter is not a schema key — it reaches the rendererthrough the renderer context (or as an explicit
dataSourceprop), while theschema names the object and the fields". The binding it distinguishes is
@objectstack/spec's owndataSource: { object, view?, filter?, sort?, limit? }.This PR adds no exported symbol and no key on any published payload, and moves no
operator or contract semantics;
check-changeset-presencemeasured 0 publishedsource files in the range.
Patch round — dequeued from the merge queue at 15:26:15Z (
CI_FAILURE)Test (shard 1/4)in run34368473519(merge_group, head97f388f31d) failed:Why this PR page was green while the queue was not.
ci.yml:183-195— onpull_requestthe "Decide whether this change needs a full run" step diffs with':(exclude,glob)**/*.md', so a markdown-only PR skips the test shards: theycompleted in ~10 seconds and reported success. On
merge_groupthe same stepshort-circuits to
should_run=true("a merge_group build is the last validationbefore main. Running everything."), and there the shard ran 907s and failed.
⇒ a green
Test (shard N/4)on this PR was never evidence the tests ran, so everyreading below was taken locally, from real output, not from a check mark.
Root cause.
readme-dashboard-examples-spec-valid.test.tsextracts every fenceof this README matching BOTH
/const\s+schema\b/and/type:\s*'dashboard'/,strips imports and annotations, and evaluates the rest with
new Function. Thefence added by the first round carried the schema literal AND the provider wiring —
and
new Functioncompiles neither a top-levelexportnor JSX. Two independentbreakages in one fence.
Fix — split the fence; the harness is not widened.
toEvaluable()is untouched:that test's own docblock states the deliberate remedy is adding a name to
AMBIENT,not swallowing arbitrary syntax, and an evaluator that skips what it cannot read is
the false-green shape objectui#4846 exists to prevent. So the section now carries
two fences, split along the extractor predicate's own semantic line:
typescriptconst schema: DashboardComponentSchema = { type: 'dashboard', … }tsxdeclare const schema, the adapter construction, and the provider wiringtype: 'dashboard'The provider example is kept (it is the substance of this PR), the closing note on
the
dataSourcename collision is unchanged, andreact/README.mdandplugin-report/README.mdare still untouched.Readings — this round, at
223100735Block count: 6 before, 6 after, and the same documents selected. The floor the
harness guards is
blocks.length >= 6, so this number is what makes the passmeaningful. Taken from the test's own verbose run, not from the brief:
The split did not change which fences are selected: the same six documents, the
Integration one moved by one line (282 to 283) because the prose above it grew. The
new
tsxfence is not selected — the count stayed at 6 rather than becoming 7,which is the observation that proves it. ⇒ nothing was hidden from the extractor to
buy this green.
The whole
plugin-dashboardsuite, run from the repo root the way this repo'svitest guard prescribes:
check:doc-snippets— exit 0, and BOTH fences are judged. The count moved637 of 637to638 of 638precisely because one fence became two, so the secondfence is not a silent skip:
Proved rather than inferred: injecting
ThisNameIsDefinitelyNotExportedintoeach fence (on-disk proof first — blob
fab0a7bc6toed0a263f5, sentinelcount 0 to 2) makes the gate report exactly two failures, one per fence:
Line 283 is inside fence 1, line 306 inside fence 2. Restored by state:
git diff HEADempty and blob back tofab0a7bc6.Ablation — the test really catches this, and the mutation really landed. From
the committed tree at
223100735, theexport/JSX was re-introduced into theschema fence only (fence 2 left in place, so the export/JSX is the single
isolated variable). On-disk proof taken BEFORE the run:
and the test went red, by test-case name:
Restore verified by state, not by an exit code:
git checkout HEAD -- PATH, thenblob back to
fab0a7bc6fa04f94f34ba20b98ed1e587ce49eb3andgit diff HEADempty.Both mutation scripts carried a
trap ... EXIT INT TERMrestore with absolutepaths, and both refuse to report a result if the edit's anchor count is not exactly
1 — the previous round's ablation attempt wrote nothing and stayed green at exit 0,
which is the failure this guard exists to make impossible.
Clause-2: no — measured this round, not inherited:
check:control-bytes(exit 0, 7057 files),check:doc-fences(exit 0),check:readme-exports(exit 0) andcheck-changeset-presence(exit 0, "nochangeset is owed") were re-run and stay green; per the dispatch they are blind to
this failure and are reported only as unchanged, never as evidence about it.
验收备注
noted, not filed: the card's acceptance probe
^ dataSource,$is a pattern, nota class predicate — it counts two correct constructs as members. Anyone taking up
the gate question (deferred by ruling 2 behind objectui#7912's
any-typedSchemaRendererProviderprop) inherits this: a text gate built on that patternwould redden
react/README.mdandplugin-report/README.mdon day one.noted, not filed: the triage comment warned that
plugin-gantt/README.mddoes notcarry "the repaired wording". Both readings are true and they are about different
sentences — gantt:726-731 carries the repaired form the dispatch cites, while
the exact sentence triage searched for ("The adapter is the
dataSourceprop,not part of the schema:") lives only at
plugin-view/README.md:509. Recorded sothe next reader does not re-litigate it.
noted, not filed:
AMBIENTinreadme-dashboard-examples-spec-valid.test.tshas oneentry,
createObjectStackAdapter, and after this split no selected block uses it —the schema fence is now pure data needing no ambient name. Harmless (unused
new Functionparameters), and the entry is the harness's documented extensionpoint rather than dead weight, so it is left alone. 承接者: whoever next adds a
dashboard document fence to this README, who reads that list before adding to it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Generated by Claude Code
Generated by Claude Code