Skip to content

docs(skills): testing.md Pattern 5 publishes userRole through the scope channel the renderer reads (objectui#9380) - #10941

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-9380-testing-pattern5-data-root
Sep 28, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-9380-testing-pattern5-data-root

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #9380

Clause-②: no — a published guide's example is corrected to the scope the renderer already publishes; no accept set or public contract surface moves

What changed

skills/objectui/guides/testing.md, «Pattern 5: Component rendering with SchemaRenderer» — one fence and two prose lines above it.

  • The fence handed a plain bag (users, userRole) to SchemaRendererProvider's dataSource and gated hidden: '${userRole !== "admin"}' on a bare userRole root. Nothing publishes that root: dataSource carries the CRUD adapter and, since objectui#9308 landed (852437297b), the renderer binds no expression root from it at all. The predicate faults (userRole is not defined), evaluateCondition fails soft to true, the node is hidden, and getByText('Secret') throws for a reader who copies the example.
  • The fence now publishes the same keys through PredicateScopeProvider — the channel auth-permissions.md, schema-expressions.md, data-integration.md, page-builder.md and rules/protocol.md already teach — so ${userRole !== "admin"} is a real gate. The hidden expression is unchanged: a bare userRole is the right spelling once the host publishes it under that name.
  • The admin assertion is kept as it was (// admin should see it). A viewer leg is added with rerender, so the test named "hides component when hidden expression is true" exercises its hidden branch once. Not an inversion: the admin leg still asserts the node is on screen.
  • Two prose lines above the fence name the channel and the fail-soft trap and point at guides/auth-permissions.md.

Measured, not inferred

Scratch vitest files under packages/components/src/__tests__/ — root config, dom project (happy-dom), @object-ui/react aliased to packages/react/src, so the path exercised is the CURRENT SchemaRenderer evaluation memo (configEvaluationScope: the usePredicateScope() spread plus current_user / record / page) driving @object-ui/core's ExpressionEvaluator.evaluateCondition from source. The real text renderer was registered at module scope (import '../renderers'), which is the reader's own setup per the guide's "Common testing mistakes" section and not this card. All three scratch files were deleted before the commit; nothing permanent was added because no existing test pins this guide's examples (node scripts/markdown-test-inputs.mjs --list names only tree-wide gates as readers of testing.md).

RED — the fence as written, text registered (verbatim):

❯ |dom| packages/components/src/__tests__/scratch-9380-registered.test.tsx (2 tests | 1 failed) 47ms
    × hides component when hidden expression is true 12ms
TestingLibraryElementError: Unable to find an element with the text: Secret.
  Reason: Failed to evaluate expression "userRole !== "admin"": userRole is not defined

The DOM dump under that error is an empty body holding one empty div — the node is hidden, not unknown. renders component from schema passed in the same file, so the only variable left is the root.

GREEN — the corrected fence (byte-identical to the published fence except the describe name and the renderer registration import; diff printed exactly those two hunks). Verbatim except the verbose reporter's angle-bracket separator, spelled › here because GitHub's body sanitizer eats tag-shaped fragments:

✓ |dom| packages/components/src/__tests__/scratch-9380-corrected.test.tsx › SchemaRenderer (corrected) › renders component from schema 27ms
✓ |dom| packages/components/src/__tests__/scratch-9380-corrected.test.tsx › SchemaRenderer (corrected) › hides component when hidden expression is true 8ms
 Test Files  1 passed (1)
      Tests  2 passed (2)

Copied verbatim with only the imports the fence itself carries (no renderer registered), both tests fail on Unknown component type: text — the registration point the guide's own "Common testing mistakes" section already states; unchanged here.

Line readings (published skill, net budget +6)

reading before after delta
skills/objectui/guides/testing.md, whole file 364 369 +5
package: every skills/**/SKILL.md (one — skills/objectui/SKILL.md, 137 lines) plus the guide touched 501 506 +5

No re-wrap bought a line: the fence is +2 (the it block that gained a leg), the prose is +2, plus one blank line.

objectui#9308 — does it change the correct spelling?

The card's «Suggested shape» offered two routes and objectui#9308 (option B, landed as 852437297b, card state completed) removes one of them: data: dataSource is gone from the evaluator scope, so spelling the gate ${data.userRole} against the injected bag would be exactly as wrong as the bare root is today — schema-expressions.md now warns that re-publishing data only restores an always-false verdict. The route that stands is publishing the name through PredicateScopeProvider, which is what this PR does; the bare spelling inside the expression stays because that is what a scope-published key looks like.

Gates (run locally at d1fda66e1a)

  • pnpm check:skills-paths → exit 0 — 88/89 stated path(s) resolve across 20 guide file(s); 1 baselined
  • pnpm check:skill-eval-tokens → exit 0 — Every must_contain token is taught by its own skill bundle.
  • pnpm check:control-bytes → exit 0 — scanned 9180 tracked text file(s)
  • pnpm check:new-line-citations → exit 0 — 0 new citation(s) (re-run after the commit)
  • node scripts/check-changeset-presence.mjs → exit 0 — No source or published contract of a released package changed in this range, so no changeset is owed. (skills/** sits in no package's files[]; no changeset and no skip-changeset label to write)
  • node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/testing.md → exit 3 — GOVERNED — 1 of 1 path(s) (expected: Tier H, draft only)
  • the ledger's readers of skills/objectui/** — check-skill-examples, check-skills-paths, check-skill-eval-tokens, doc-version-claims, layered-read-declared-path-4016, check-shell-escape-residue, check-installed-spec-pin-claims, dollar-dialect-alias-census, markdown-test-inputs, skill-guide-provider-envelope → Test Files 10 passed (10), Tests 365 passed (365)
  • pnpm check:skill-examples → NOT MEASURED locally: exit 2 PRECONDITION NOT MET, no dist/ in this worktree and the scoped build the gate names spans 30 packages (--dry=text), beyond the foreground budget. Declared to CI: skill-examples.yml runs on every PR with no path filter. This diff adds no marker and touches no marked fence, so the marked population and its floors are unchanged.
  • check:doc-snippets / check:doc-examples / check:doc-fences — not owed: their DOCS_ROOT is content/docs, and check-doc-snippet-types.mjs states that skills/objectui/** is claimed by no gate there.

Acceptance notes

  • Renderer registration (the verbatim variant above) is the reader's setup and already documented by the guide; noted, not filed.
  • The old fence passed a plain bag as dataSource, which objectui#7912's DataSource typing rejects for a reader running tsc (the same TS2740 shape objectui#9346 measured on the docs pages). The corrected fence passes nothing to SchemaRendererProvider, so that latent error leaves with it.

维护者速读(草稿)

改了什么 — 发布技能包 skills/objectui/guides/testing.md 的「Pattern 5」示例:把示例里当作表达式变量来源的 SchemaRendererProvider 的 dataSource 换成 PredicateScopeProvider 的 scope,hidden 表达式本身不变;补一条「非管理员看不到」的断言;围栏上方加两行说明。净增 5 行,预算 +6。

为什么改 — 读者照抄原示例,第二个测试的断言直接抛错:userRole 没有被任何通道发布为表达式变量,hidden 表达式求值失败后按安全默认判为「隐藏」,getByText('Secret') 找不到节点。objectui#9308 之后所有姊妹指南都已改成本 PR 的写法,只有这一处漏了。已按当前渲染器实测:改前红、改后绿。

风险与代价(含回滚) — 只动一份 markdown,不改代码、不发版、不欠 changeset。风险在文字层面:示例若再次与渲染器脱节会再次误导读者,本次以实测为据。回滚 = revert 这一个 commit。

席位意见 —

你要做的 — 作为 GOVERNED_APPROVERS 之一在本 PR 留下一条 APPROVED review;之后由认领席翻 ready 并入队。

Session: https://claude.ai/code/session_01EBx9rvB7dufCz4at53x35U


Generated by Claude Code

…cope channel the renderer reads (objectui#9380)

The Pattern 5 fence built a plain bag, handed it to `SchemaRendererProvider`'s
`dataSource`, and gated `hidden: '${userRole !== "admin"}'` on a bare
`userRole` root. Nothing publishes that root: `dataSource` is the CRUD adapter
and, since objectui#9308, no expression root at all. The predicate faults
(`userRole is not defined`), `evaluateCondition` fails soft to `true`, the node
is hidden, and `getByText('Secret')` throws for a reader who copies the example.

The fence now publishes the same keys through `PredicateScopeProvider` — the
channel every sibling guide already teaches — so `${userRole !== "admin"}` is a
real gate. The admin leg is unchanged; a viewer leg is added so the test named
"hides component when hidden expression is true" exercises the hidden branch
once. Two prose lines above the fence name the channel and the fail-soft trap.

Measured on the current renderer (vitest `dom` project, aliased `packages/react/src`)
with the `text` renderer registered: the fence as written failed the admin
assertion (`Unable to find an element with the text: Secret`, body `<div />`);
the corrected fence passes both tests. Scratch tests deleted; no permanent test
added because no existing test pins this guide's examples.

Line readings: `testing.md` 364 -> 369 (+5); `skills/**/SKILL.md` + this guide
501 -> 506 (+5), inside the +6 budget.

Claude-Session: https://claude.ai/code/session_01EBx9rvB7dufCz4at53x35U
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d1fda66e1a89350fb837204d3af833e328921006
Local-runs: none

① Derived judgments

Diff: one file, skills/objectui/guides/testing.md (+19/−14, whole file 364 → 369), byte-identical to the net diff against main at the merge-base 733fd5ac65 (main has not moved that file since). No accept set moves: packages/**, apps/** and .changeset/** are untouched. The changed surface is a published agent-facing guide (npx skills add objectstack-ai/objectui reads this tree), so the judgments below ask whether its new text is true on this tree and whether the fence passes.

Prose claim 1 — "Expression roots are the keys of the scope a host publishes through PredicateScopeProvider": RIGHT for the fence's setup. configEvaluationScope in packages/react/src/SchemaRenderer.tsx spreads usePredicateScope() and adds only current_user (aliased from scope.user), record (only under a RecordContext) and page; PredicateScopeProvider (packages/react/src/hooks/useExpression.ts) publishes its scope prop verbatim as the context value. The fuller root table lives in the guide the prose links.

Prose claim 2 — "SchemaRendererProvider's dataSource is the CRUD adapter and publishes none": RIGHT. SchemaRendererContext types it DataSource | null | undefined; configEvaluationScope binds no data root from it (the retired data: dataSource binding is recorded in the comment block above the evaluation memo), and the only data reads left in that file are predicateScope.data — a scope key like any other.

Prose claim 3 — "a hidden gate on a name nothing published fails soft to true, hiding the node for every user": RIGHT, and the mechanism is the template path, not the bare-string catch. evaluateCondition routes a ${…} string to evaluate, whose single-template branch calls evaluateExpression, which throws userRole is not defined; the outer catch returns defaultValue ?? expression, i.e. the source string, Boolean of a non-empty string is true, _hidden is set and SchemaRenderer returns null. Same verdict the card measured on the built evaluator.

Fence — imports: PredicateScopeProvider and SchemaRenderer are both exported by @object-ui/react (index → hooks/index.js → useExpression.js; SchemaRenderer.js). RIGHT.
Fence — the admin leg: userRole: 'admin' in scope makes userRole !== "admin" false, the node is shown and getByText('Secret') resolves; the comment // admin should see it is kept, not inverted, as the card demanded. RIGHT.
Fence — the viewer leg via rerender: the evaluation memo's dependency list names predicateScope ([schema, dataSource, predicateScope, pageVariables, boundRecord]), so a new scope object re-evaluates; 'viewer' !== "admin" is true, the node is hidden, queryByText('Secret') is null. RIGHT. Dropping the SchemaRendererProvider element is safe: SchemaRenderer reads that context optionally (context?.dataSource || NO_DATA_SOURCE).
Fence — type: 'text' with content: the winning registration is ElementTextRenderer (renderers/basic/elements.tsx, registered after text.tsx; Registry.register overwrites on Map.set), which renders pickLocalized(props.content, language), and pickLocalized returns a string as-is. RIGHT.

Does an agent copying the fence get a passing test? With the text renderer registered — YES, both tests; the dev measured red → green on the current renderer (RED: userRole is not defined, empty body; GREEN: 2 passed), and every step of that measurement checks out against the source above. Copied with ONLY the fence's own imports — NO: text is registered by @object-ui/components (its index side-effect-imports ./renderers), which the fence never imports, so SchemaRenderer paints its Unknown component type: text alert in BOTH tests, before and after this diff alike. That gap is pre-existing, outside card #9380 (whose defect is the root) and outside the dispatched file surface; the guide's own "Common testing mistakes" first bullet names the class, imprecisely ("Not importing plugin packages in test setup" — @object-ui/components is not a plugin). Escalated in ③; not a defect of this diff.

Citations: the new prose and the PR body cite by symbol and quoted string, no line addresses; Line Citation Gate is green (0 new). The relative link ./auth-permissions.md resolves (Skill Guide Path Check and Internal Docs Link Check green), and that guide's "Schema-level visibility with expressions" section says exactly what the prose attributes to it, including the fail-soft table.

Gate verdicts — the check-runs on this head, read at 2026-09-28T09:14Z: 39 check-runs, 36 success, 3 skipped (dependabot and the two coverage legs — path-filter skips), 0 failure, 0 in progress. Governed Surface Queue Guard, Skill Example Check (the gate the dev could not run locally), Skill Eval Token Check, Skill Guide Path Check, Changeset Declaration, Changeset Fixed Group Check, Changeset Claim Re-read, Line Citation Gate, Lint, Type Check, Build & E2E, Test and all eight test shards are success. Every check is green.

② Semver level

None — nothing publishes. skills/** sits in no workspace package's files[] (measured over every packages/*/package.json and apps/*/package.json at this head), so no npm tarball carries the changed file; the skill ships to agents through npx skills add, which reads this tree and has no version or changeset surface. No changeset and no skip-changeset label is the correct declaration, and the three changeset checks agree. Clause-②: no is RIGHT: the diff moves no accept set and no public contract surface; it corrects a guide to the scope channel the renderer already publishes.

③ Boundary flags

Dev deviations (six) — each answered:

  1. PM-suggested data.userRole route not taken — RIGHT to refuse: configEvaluationScope binds no data, and schema-expressions.md on this tree warns that re-publishing data only restores an always-false verdict. The card's first route (PredicateScopeProvider) is the one taken; the expression text is unchanged.
  2. Viewer leg added beyond the minimal text — accepted: it makes the test named "hides component when hidden expression is true" exercise its hidden branch; the admin assertion is unchanged; +5 of the +6 budget used (364 → 369; SKILL.md 137, unchanged).
  3. check:skill-examples declared to CI — closed: Skill Example Check is success on this head.
  4. objectstack's dispatch-gates.mjs not used — RIGHT: objectui's scripts/ has no such file at this head; the gate set was derived from the repo's own workflows, and all of them ran.
  5. PR body spells the reporter's angle-bracket separator as › — cosmetic, correct under the body-sanitizer rule.
  6. Model-free commit trailer pair — verified on the head commit (Claude-Session: URL plus Co-authored-by: Claude).

open_questions: none declared; none found.

Out-of-scope findings — answered or escalated:

  • (dev, "carrier: none · noted, not filed") The fence copied with only its own imports fails on Unknown component type: text. ESCALATED to the adopting session: a reproducible copy-and-fails defect in a published guide is card-worthy (Prime Directive [WIP] Enhance every detail of the designer #10 class), and the existing "Common testing mistakes" bullet does not name @object-ui/components as the package that registers the base renderers — it owes a card (or a one-line import '@object-ui/components'; in this fence under a fresh review). Not blocking this diff: pre-existing on both sides, and the card's own defect is fully closed.
  • (dev, "carrier: this PR") The old fence's plain bag in the DataSource slot (TS2740 for a tsc reader) leaves with this diff — agreed, no action.
  • (dev, observation) i18n.md's "Language-aware + responsive page" fence hands an undeclared identifier named data to dataSource — agreed it is naming only (nothing gates on it; a fragment); repair opportunistically when that file is next touched, no card.
  • (reviewer, new) skills/objectui/evals/testing.json eval 1 still expects a "rendering test with SchemaRendererProvider and mock dataSource, and expression-based visibility test" and requires the token SchemaRendererProvider — the wiring this card retired for a visibility gate. Skill Eval Token Check is green only because SKILL.md still teaches that token for its real job (adapter injection). ESCALATED: the eval's expected output should name PredicateScopeProvider for the visibility leg; it is outside the dispatched file surface (fence and prose only), so a follow-up card, not this PR.

Governed-surface note: skills/** is Tier H in both registers (skills-catalog). This record is the contract-tier review the claim promised; landing still needs an authorized APPROVED review from GOVERNED_APPROVERS and is then the owning seat's act — no AI seat readies, queues or arms this PR on this record.

Implemented-by: claude/issue-9380-testing-pattern5-data-root
Reviewed-by: session_01EBx9rvB7dufCz4at53x35U

VERDICT: PASS


Generated by Claude Code

…enders (objectui#9380, patch round 1)

Copied verbatim, the corrected fence still threw for a reader whose setup
registers nothing: `Unknown component type: text` on both tests. The fence now
carries the one side-effect import that registers the renderers, spelled the
way page-builder.md spells its plugin imports. Nothing else in the fence moves.

Measured on the current renderer (vitest `dom` project, light setup that
registers nothing): the fence byte-identical to the published one passes both
tests; the same file minus this import fails both with `Unknown component
type:` — the control. Scratch files deleted before commit.

Line readings: `testing.md` 364 -> 370 (+6); `skills/**/SKILL.md` + this guide
501 -> 507 (+6), exactly the PR budget.

Claude-Session: https://claude.ai/code/session_01EBx9rvB7dufCz4at53x35U
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fd764036b1385120ee2d8b9c01a51c86935e7c07
Local-runs: none

① Derived judgments

Diff: one file, skills/objectui/guides/testing.md, +20/−14 against main at the merge-base 733fd5ac65; main has not moved that file, skills/objectui/SKILL.md or skills/objectui/evals/testing.json since, so the PR diff and the net diff are the same bytes. Whole file 364 → 370 (+6): fence +3 (two lines for the viewer leg, one import), prose +2, one blank line. SKILL.md 137 unchanged, package sum 501 → 507 — exactly the +6 budget the claim set, nothing left. No accept set moves: packages/**, apps/** and .changeset/** are untouched. The surface is a published agent-facing guide (npx skills add reads this tree), so the judgments below ask whether its new text is true on this tree and whether a verbatim copy of the fence passes.

Delta since the earlier record's head d1fda66e1a: exactly one line, import '@object-ui/components';, inserted after the @object-ui/react import (file line 137). RIGHT, and it closes the gap the earlier record escalated in its ③ ("copied with ONLY the fence's own imports — NO"). packages/components/src/index.ts side-effect-imports ./renderers, whose basic/index.ts imports ./text and ./elements; the manifest says sideEffects: true, so a bundler keeps the bare import as well; the import export is dist/index.js, whose CSS is extracted at lib build (the index's own note), so the import drags no stylesheet into a test. The spelling is the one page-builder.md uses for its plugin registrations (import '@object-ui/plugin-grid';).

Which renderer answers type: 'text' — a correction to the earlier record. elements.tsx registers ElementTextRenderer as element:text with skipFallback: true, so it never claims the bare text key; text.tsx registers under namespace: 'ui' without skipFallback, so Registry.register's fallback branch writes the bare text key to the ui:text renderer. SchemaRenderer resolves ComponentRegistry.get(evaluatedSchema.type) as a plain Map.get, so text reaches ui:text, which reads schema.content FLAT and returns a fragment holding the string when no className or data-obj-id is present. This matters: the fence's content is flat, not under props, and ElementTextRenderer reads only readProps(schema) (the props and properties bags), so had element:text won the bare key Hello World would have rendered as an empty paragraph. The earlier record's mechanism ("the winning registration is ElementTextRenderer … pickLocalized") was WRONG; its conclusion — both tests pass — stands, and the dev's green measurement is the ui:text path. ui:text imports no i18n hook, so the fence needs no i18n provider either.

Prose claim 1 — "Expression roots are the keys of the scope a host publishes through PredicateScopeProvider": RIGHT for the fence's setup. configEvaluationScope in packages/react/src/SchemaRenderer.tsx spreads usePredicateScope() and adds only current_user (aliased from scope.user), record (only under a bound row) and page; PredicateScopeProvider (packages/react/src/hooks/useExpression.ts) publishes its scope prop verbatim as the context value. The fuller root table is in the guide the prose links.

Prose claim 2 — "SchemaRendererProvider's dataSource is the CRUD adapter and publishes none": RIGHT. SchemaRenderer reads it as context?.dataSource || NO_DATA_SOURCE and binds no data root from it; the retired data: dataSource binding is recorded in the evaluation memo's comment block, and the commit that retired it, 852437297b (objectui#9308), is an ancestor of main.

Prose claim 3 — "a hidden gate on a name nothing published fails soft to true, hiding the node for every user": RIGHT, on the template path. evaluateCondition sends a string containing ${ to Boolean(this.evaluate(trimmed, options)); evaluate's single-template branch calls evaluateExpression, which throws userRole is not defined; the outer catch returns defaultValue ?? expression, the non-empty source string, so the condition is true. SchemaRenderer's evaluateVisibilityPredicate returns that verdict on the production branch and true from its own catch on the dev branch, _hidden is set and the component returns null. The linked auth-permissions.md section ("Schema-level visibility with expressions") carries the same verdict table, "fails soft to true / hidden for every user", so the attribution is exact.

Fence — imports: PredicateScopeProvider (hooks/useExpression.ts → hooks/index.ts → the package index) and SchemaRenderer are both exported by @object-ui/react. RIGHT.
Fence — the admin leg: userRole: 'admin' makes userRole !== "admin" false, the node is shown and getByText('Secret') resolves; // admin should see it is kept, not inverted, as the card demanded. RIGHT.
Fence — the viewer leg via rerender: the evaluation memo's dependency list names predicateScope, so a new scope object re-evaluates; 'viewer' !== "admin" is true, the node is hidden and queryByText('Secret') is null. RIGHT.
Fence — types, for a reader who runs tsc: SchemaRendererProps.schema is BaseSchema | string | null | undefined; BaseSchema declares hidden?: boolean | ExpressionWire (a string is one) and an open [key: string]: any, so the flat content and the string hidden type-check without as const; PredicateScopeProvider's scope is a Record of string to any, which the bag satisfies. RIGHT — and the old fence's plain bag in the DataSource slot (TS2740) leaves with the diff.

Does an agent copying the fence VERBATIM get two passing tests? YES on this tree. The fence now carries every import it needs: vitest, @testing-library/react, the two @object-ui/react names and the @object-ui/components side effect that registers ui:text. The dev's control is the right shape — same file minus the one import fails both tests with Unknown component type:, the published fence passes both — and it was run in the dom project whose light setup (vitest.setup.dom-light.tsx: base setup, jest-dom matchers, locale catalogues, cleanup, a scrollIntoView stub) registers no renderer, so nothing in that setup did the fence's work for it. Module top of @object-ui/components asks for none of the setup's polyfills: no zero-indent window, document or ResizeObserver use exists under packages/components/src. What a reader supplies is what the guide already names — a DOM environment (happy-dom, per "Common testing mistakes") and one installed copy of @object-ui/core, so both packages share the registry singleton, the ordinary install shape.

Citations: the new prose cites by symbol and quoted string, no line addresses; the relative link ./auth-permissions.md resolves.

Gate verdicts — the check-runs on this head, read at 2026-09-28T09:32Z after the last one completed at 09:31:50Z: 39 check-runs, 36 success, 3 skipped (dependabot and the two coverage legs — path-filter skips), 0 failure, 0 in progress. Governed Surface Queue Guard, Skill Example Check (the gate the dev could not run locally, now judged on the head that adds the import), Skill Eval Token Check, Skill Guide Path Check, Internal Docs Link Check, Changeset Declaration, Changeset Fixed Group Check, Changeset Claim Re-read, Line Citation Gate, Doc Snippet Type Check, Lint, Type Check, Build & E2E, Test, Test (dist pins) and all eight test shards are success. Every check is green.

② Semver level

None — nothing publishes. skills/** sits in no workspace package's files[] (no packages/*/package.json or apps/*/package.json at this head names it; the root manifest is private), so no npm tarball carries the changed file; the skill ships to agents through npx skills add, which reads this tree and has no version or changeset surface. No changeset and no skip-changeset label is the correct declaration, and Changeset Declaration, Changeset Fixed Group Check and Changeset Claim Re-read agree. Clause-②: no is RIGHT: the diff moves no accept set and no public contract surface; it corrects a guide's example to the scope channel the renderer already publishes and registers the renderer the example renders.

③ Boundary flags

Round-2 deviations (two) — answered:

  1. Import spelled as the bare side-effect form page-builder.md uses — RIGHT; the manifest's sideEffects: true is what makes that form safe under a bundler too, and the guide's own "Common testing mistakes" bullet names the mistake in prose only, so a sibling guide's form was the right source.
  2. PR body not edited after round 2 (it still describes the verbatim variant as failing on registration and reads +5 / 369 in its line table and 净增 5 行 in the maintainer digest) — accepted under the write-once rule; the round-2 report on card bug(skills): testing.md Pattern 5 gates on a bare userRole root nothing publishes — copy the example and the assertion throws #9380 is the corrected record, and the true readings are +6 / 370 / 507 as measured here. The adopting seat should read the card comment, not the body, for the line count.

Round-1 deviations (six) — re-answered on this head: (1) the data.userRole route was RIGHT to refuse, configEvaluationScope binds no data; (2) the viewer leg is accepted, admin assertion unchanged; (3) check:skill-examples declared to CI — closed by the Skill Example Check run on this head (see gates); (4) no dispatch-gates.mjs exists in objectui's scripts/ at this head, RIGHT; (5) the › separator in the PR body is cosmetic and correct under the sanitizer rule; (6) the head commit carries the model-free trailer pair (Claude-Session: URL plus Co-authored-by: Claude) — verified.

open_questions: none declared in either round; none found.

Out-of-scope findings — answered or escalated:

  • (round 1, "carrier: none") The fence copied with only its own imports failed on Unknown component type: text — CLOSED by this head's one-line delta; the escalation in the earlier record is discharged.
  • (round 1, "carrier: this PR") The old fence's plain bag in the DataSource slot leaves with the diff — agreed, no action.
  • (round 1, observation) i18n.md's undeclared data identifier handed to dataSource — naming only, nothing gates on it; repair when that file is next touched, no card.
  • (round 2, "carrier: seat") skills/objectui/evals/testing.json untouched — ESCALATED to the adopting session, unchanged from the earlier record: eval 1's expected_output still says "rendering test with SchemaRendererProvider and mock dataSource, and expression-based visibility test" and its must_contain still requires SchemaRendererProvider, the wiring this card retired for a visibility gate; Skill Eval Token Check is green only because SKILL.md teaches that token for adapter injection. Outside the dispatched file surface and the exhausted line budget — a follow-up card, not this PR.
  • (reviewer, new) The "Common testing mistakes" first bullet still reads "Not importing plugin packages in test setup" — @object-ui/components is not a plugin, and it is now the import this very guide's fence depends on. The fence shows the form, the bullet still names the class imprecisely. Not blocking (the fence is self-sufficient), outside the dispatched surface and the budget; fold into the same follow-up card as the eval.

Governed-surface note: skills/** is Tier H (skills-catalog). This record is the contract-tier review the claim promised; landing still needs an authorized APPROVED review from GOVERNED_APPROVERS, and the landing is the owning seat's act — no AI seat readies, queues or arms this PR on this record.

Implemented-by: claude/issue-9380-testing-pattern5-data-root
Reviewed-by: session_01EBx9rvB7dufCz4at53x35U

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

席位 domain:devx @ objectui 第 2 席(session_01EBx9rvB7dufCz4at53x35U)对照本 PR 的实际 diff(head fd764036b1)校正 dev 草稿后定稿;契约复审档复核 PASS 在案(5867268801),席位复核记录在 objectui#9380。

改了什么:只改对外发布的技能指南 skills/objectui/guides/testing.md 里「Pattern 5」一个示例(净增 6 行,正好用满预算)。原示例把 userRole 塞进数据源,再用 ${userRole !== "admin"} 控制隐藏——渲染器从不把数据源的键发布成表达式变量,于是表达式出错、按「失败即隐藏」处理,管理员也看不到节点,照抄的测试必然失败。现在改为通过 PredicateScopeProvider 发布 userRole(与兄弟指南教的写法一致),补一行 import '@object-ui/components'; 注册渲染器,并加一条「普通用户看不到」的断言,让「隐藏」分支真的被测到。示例上方两行说明点破了这个坑。

为什么改:技能包是 AI 照着写代码的材料,一个照抄即失败的示例等于产品缺陷。dev 在当前渲染器上实测:原示例照抄两个测试都失败,新示例照抄两个都通过;去掉注册那一行则失败(对照)。

风险与代价(含回滚):只改一段示例文本,不碰任何包代码、不发版(skills/** 通过 npx skills add 分发)。PR 正文里写的「+5 / 369 行」是第一轮的旧数,实际是 +6 / 370。回滚:revert 本 PR。

席位意见:建议批准。席位核对了 PredicateScopeProvider 确从 @object-ui/react 入口导出;两轮达档契约复审均 PASS,第二轮专门确认了「原样照抄即通过」。两处相邻小问题(evals/testing.json 第 1 条仍以旧写法为期望、「常见错误」第一条只说 plugin 包)未改,记为 Acceptance notes,留给下一个碰这份指南的 PR。

你要做的:在本 PR 上点 Approve(os-zhuang 或 hotlong 任一账号即可);之后翻 ready、入队由席位完成。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 28, 2026 09:38
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 7e1a8d0 Sep 28, 2026
41 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-9380-testing-pattern5-data-root branch September 28, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(skills): testing.md Pattern 5 gates on a bare userRole root nothing publishes — copy the example and the assertion throws

3 participants