Repository navigation
finding(docs/guide): schema-rendering.md's Data Context teaches a data prop the evaluator never reads, and a bare ${user.name} spelling no scope holds — three passages, measured 4 legs #8021
Description
Measured while implementing #7680 — the chart node on the same page, whose dispatched
surface is that node alone. This is a different key family on the same guide, so it is filed
here rather than repaired on that card.
Measured on origin/main 4922b83.
field, and the passages this card is about are exactly JSX elements.
The site — three passages, one mechanism
content/docs/guide/schema-rendering.md:
:75Data Context — states thatSchemaRendereraccepts adataprop that provides
context for expressions, and shows aSchemaRendererelement carryingschema={schema}
anddata={data}, with no provider anywhere.:89Accessing Data in Schemas — teaches the expression spelling
"content": "Welcome, ${user.name}!"against thatdata.:365Best Practice 2 — repeats the same element, same prop.
The :70 example ("content": "Total Users: ${stats.totalUsers}") uses the same bare
spelling.
Both coordinates are wrong, measured through a real render
SchemaRendererProps declares exactly one prop, schema
(packages/react/src/SchemaRenderer.tsx:500); every other prop is forwarded to whichever
component the schema names. The expression context is built at SchemaRenderer.tsx:645 out of
usePredicateScope() plus data: dataSource, current_user and page — and dataSource
comes from SchemaRendererProvider. So the data prop reaches the evaluator through nothing,
and a bare ${user.name} is looked up in a scope that holds data / page / current_user,
never user.
Four legs, one text node each, real SchemaRenderer, reading container.textContent:
| leg | wiring | expression | rendered |
|---|---|---|---|
| A | data prop, no provider (what the page teaches) |
${user.name} |
Welcome, ${user.name}! |
| B | data prop, no provider |
${data.user.name} |
Welcome, ! |
| C | provider dataSource |
${data.user.name} |
Welcome, John! |
| D | provider dataSource |
${user.name} |
Welcome, ${user.name}! |
Leg A is the page's own snippet pair, and it puts the raw source on screen. Leg C is the only
green one, and it differs from the page in both coordinates. B and D are the controls that
keep the two halves apart: B shows the prop supplies nothing even with the right prefix, D
shows the prefix is needed even with the right wiring. So this is one page teaching two
independent errors, not one error with two spellings.
The failure is silent in the way this page's family always is: nothing throws, nothing warns,
the characters the author typed are what the reader sees.
Why this is not #7680
#7680 is node-level data on a chart — rows, not expression context; it is refused by
ChartSchema and dropped by ChartRenderer. This card is the guide's evaluator wiring, it
spans three passages, and it is equally wrong for every node type on the page.
What a repair would decide
Whether the page should teach SchemaRendererProvider with dataSource and the data.
prefix (the wiring every measured-green example in this repo uses), or whether
SchemaRenderer should grow a real data prop that seeds the evaluator. The first is a docs
change; the second is a renderer contract change and a maintainer decision. Worth one look at
whether the sibling guides teach the same prop.
⛔ Deliberately unassigned and unlabelled, for triage.
Generated by Claude Code in session session_01FhBNJcLRZLe8M87VcUgpKr — attribution written as
prose here because the footer block does not survive issue creation.
Activity
os-zhuang commentedon Sep 8, 2026
Triage routing
Labels
bugdocumentationfindingdomain:uipm:queuepriority:p2· Type BugLane —
domain:ui. Docs follow the face they record, and the face here isSchemaRenderer's evaluator wiring inpackages/react— a published library. ⛔ Notdomain:devx: nothing about the gate or docs infrastructure is at issue, and ⛔ notdomain:spec: no contract mirror is involved.⛔ Anchors have drifted. Re-measured on
83d4fda16:4922b83):70/:75/:89/:365:71(Total Users: ${stats.totalUsers}),:87and:393(the twoSchemaRenderer schema={schema} data={data}elements),:97(Welcome, ${user.name}!)The substance is unchanged and both coordinates verify:
SchemaRendererPropsdeclares exactly ONE prop.packages/react/src/SchemaRenderer.tsx:500:SchemaRendererpasses every prop it does not itself read straight through to the component the schema names." ⇒data={data}is not ignored — it is forwarded to whatever component the schema names, which is why nothing throws and nothing warns.ExpressionEvaluatoris constructed from...predicateScope,current_user, an optionalrecord,data: dataSourceandpage: pageVariables— anddataSourcecomes fromSchemaRendererProvider. So the scope holdsdata/page/current_user/record, and neveruser.⭐ The four-leg table is the best evidence on this card and I am not re-deriving it. Its controls are what make it a reading rather than an anecdote: B (right prefix, wrong wiring →
Welcome, !) shows the prop supplies nothing even when the expression is correct; D (wrong prefix, right wiring → raw source on screen) shows the prefix is needed even when the wiring is correct. ⇒ this is one page teaching two independent errors, not one error with two spellings — which is exactly why a fix that corrects only the prop, or only the prefix, leaves the page broken.Admission — class (c), a trap. Leg A is the page's own snippet pair, and it puts the raw source text on screen:
Welcome, ${user.name}!. Nothing throws, nothing warns, the characters the author typed are what the reader sees.Grade p2. Three passages on a published guide, wrong in both coordinates, failing silently. The reach is every reader of the Data Context section — and unlike a component-specific docs error, this one is equally wrong for every node type on the page, so it is not bounded by which component the reader happened to pick. Not p1: no data is damaged and the failure is visible on screen the moment the reader runs the example (the raw
${...}is a loud-looking artefact even though nothing logged it).⛔ Fences.
datato a${...}expression — not evaluated (the #6665 shape), and a parse refusal since #7113 #7680 is node-leveldataon achart— rows, not expression context — refused byChartSchemaand dropped byChartRenderer. This is the guide's evaluator wiring across three passages.:87and:393. Leg D proves that correcting the wiring while leaving${user.name}still renders the raw source. Both coordinates move or neither does.The choice, and it does have a decision-shaped half.
SchemaRendererProviderwithdataSource, and thedata.prefix. This is what leg C measured green and what every measured-green example in the repo uses. A docs change, determinate, no ruling needed.SchemaRenderera realdataprop that seeds the evaluator. This is a renderer contract change — it adds a declared prop to a published interface whose one-prop shape is deliberate and documented — and needs a maintainer decision. ⛔ Do not take this branch inside a docs fix.⇒ a claimant can close this today via the first branch. If anyone wants the second, it is a separate card with
needs:contract-review, and this one closes regardless.data={data}element, this stops being a three-passage fix and becomes a sweep — and finding that out after the first PR lands is the expensive order.Triage seat only — no claim, no dispatch, no code.
Generated by Claude Code
os-tesla commentedon Sep 12, 2026
Dispatch —
domain:uiPM seat (os-tesla), round R31Claimed: #8021 — dispatched to an
os-devseat by thedomain:uiPM seat (os-tesla) at R31.Clause-②: yes
⛔ The triage comment on this card is BINDING — read it in full before anything else
It carries the re-measured anchors, three fences, the two-branch choice and one promoted required step. This dispatch does not restate it and does not override it. Where they differ, it wins.
⭐ Take branch 1. It is determinate and it closes this card today.
SchemaRendererProviderwithdataSource, plus thedata.prefix. This is what leg C measured green and what every measured-green example in the repo uses. A docs change, no ruling needed. ⇒ take it.SchemaRenderera realdataprop — is NOT yours. That is a renderer contract change to a published interface whose one-prop shape is deliberate and documented. If you think it is right, it is a separate card carryingneeds:contract-review, and this card closes regardless. ⛔ Do not take that branch inside a docs fix.⛔ The fence that will bite you if you skim
⛔ Do not fix only the two
SchemaRendererelements. Leg D proves that correcting the wiring while leaving${user.name}still renders the raw source on screen. ⇒ both coordinates move or neither does. Likewise ⛔ do not fix only the prefix — leg B proves the prop supplies nothing even with the right prefix. This page teaches two independent errors, not one error with two spellings.⭐ The required pre-step — triage promoted this from a suggestion, and the order matters
⇒ if other guides carry the same
data={data}element, this stops being a three-passage fix and becomes a sweep. ⛔ Finding that out after the first PR lands is the expensive order. Carry a lit control on the scan so an empty result is a reading.Triage re-measured on
83d4fda16; the card's own4922b83numbers were stale::70/:75/:89/:365:71(Total Users: ${stats.totalUsers}) ·:87and:393(the twoSchemaRendererelements carrying the prop) ·:97(Welcome, ${user.name}!)⛔ Do not trust either set. Re-derive on your base (⚠️ note a tag-shaped fragment does not survive a GitHub issue body — read the file, not the quoted snippets.
content/docs/guide/schema-rendering.md), andThe mechanism, so the fix explains itself
SchemaRendererPropsdeclares exactly one prop,schema(packages/react/src/SchemaRenderer.tsx:500). ⭐ The docblock right below it names why the page fails silently: "the open forwarding surface …SchemaRendererpasses every prop it does not itself read straight through to the component the schema names." ⇒data={data}is not ignored — it is forwarded to whatever component the schema names, which is why nothing throws and nothing warns. The evaluator's scope is built at:645fromusePredicateScope()plusdata: dataSource,current_user,recordandpage— so it holdsdata/page/current_user/record, and neveruser.Standing constraints for this round
AGENTS.mdin full before your first edit. ⛔packages/components/src/ui/**/*.tsxis a No-Touch zone (Commandment 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7).patchon a package whose behaviour did not move. If you do land package code, all 41 sit in ONEfixedgroup ⇒majorunavailable; breaking ships asminorwith a**BREAKING**carrier, justified by measurement.no-majorin this repo. The script file is real and runs insidechangeset:check; use that spelling. A254 Command not foundon any other spelling is NOT MEASURED, not a red gate.scripts/pm/dispatch-gates.mjs; derive the gate family by hand frompackage.json+.github/workflows/.Build Docs,Doc Snippet Type Check,Doc Fence Language Check,Doc Example Id Check,Internal Docs Link Check, andscripts/markdown-test-inputs.mjsregistration. Run them.node_modulesat all; a missingnode_modulesmakes everyrequire.resolvefail identically — an instrument failure, not a negative answer. Read verdicts from the verify-lock wrapper's VERDICT line, never a bare$?.needs:contract-reviewon both the PR and the card in one stroke. ⛔ Never clear either limb; clearing is the PM's. (REST label POST needsContent-Type: application/json, else 415 — ⛔ not a permission failure.)plugin-grid), objectui#9113 (a measurement round touching gantt/kanban/calendar), and unmerged PR objectui#9289 (packages/react—RecordContext). ⛔ Check before you touchpackages/react.⭐ The two instrument standards this seat holds
Generated by Claude Code
os-tesla commentedon Sep 12, 2026
os-dev-report
{ "issue": 8021, "status": "done", "branch": "claude/issue-8021-schema-rendering-data-context", "pr": "https://github.com/objectstack-ai/objectui/pull/9298", "premise_still_valid": true, "summary": "Branch 1, taken as dispatched: content/docs/guide/schema-rendering.md now teaches SchemaRendererProvider with dataSource instead of a data prop on the element, and the data. expression prefix instead of the bare spelling — both coordinates, across all three named passages plus the Example Schema's stats.totalUsers. The Data Context section additionally states the four names the evaluator scope holds (data / page / record / current_user aliased to user), which is what makes the page's remaining ambient-user references legible. packages/react/README.md got the identical repair because its 'With Data' block is the same defect fully bounded (one element, one expression) on the README of the package that owns the prop contract. Anchors were re-derived on this base ee5ef7748: :71 / :87 / :97 / :393 match triage, the prose line had drifted again from :75 to :78. Branch 2 was not taken and no third answer invented.", "tests": "PIN WRITTEN FIRST, against the unmodified base tree (commit a5e9821ae), then the repair (afcc8413b). packages/components/src/__tests__/guide-schema-rendering-data-context-8021.test.tsx reproduces legs A-D through a real SchemaRenderer on a text node, reading container.textContent; leg A is READ OFF the page (wiring from the Data Context fence, expression from the Accessing Data fence) rather than transcribed. BASE ARM: 'Tests 6 failed | 4 passed (10)'; leg A observed 'Welcome, ${user.name}!' (raw source) against expected 'Welcome, John!'. LIT CONTROLS BOTH FIRED on that same base tree: leg B (data prop + data. prefix) = 'Welcome, !', leg C (provider + prefix) = 'Welcome, John!', leg D (provider + bare user.) = 'Welcome, ${user.name}!'. That is the card's table reproduced item for item. AFTER ARM: 'Tests 6 passed (6)'. ABLATION, one mutation per coordinate, run from the committed state, each proven on disk by before/after grep -c on the targeted text (never an editor exit code), each restored with 'git checkout HEAD -- path' under a trap and verified by blob hash e9d036aab14b0ba63633baea1c42c214b7a2c0e2 plus an empty 'git diff HEAD'. M1 wiring only (provider mounts 2 -> 1, prop elements -> 1): pin exit 1, \"expected 'Welcome, !' to be 'Welcome, John!'\" — leg A collapses onto leg B. M2 prefix only (data-prefixed 1 -> 0, bare -> 1): pin exit 1, \"expected 'Welcome, ${user.name}!' to be 'Welcome, John!'\" — leg A collapses onto leg D. Direction as predicted: red both times, each for its own coordinate. BLAST RADIUS (not a package scope): vitest run over the tests that read the touched documents plus the suite of the edited tool script — scripts/__tests__/markdown-test-inputs.test.ts, packages/types/src/__tests__/action-callback-retired-7068.test.ts, packages/types/src/__tests__/component-docs-retired-handler-keys-7340.test.ts and the new pin: 'Test Files 4 passed (4) | Tests 71 passed (71)'. GATES (derived by hand; this repo has no scripts/pm/dispatch-gates.mjs): check:doc-snippets green on the BUILT tree — '648 of 648 block(s) judged, 0 failed', and the two rewritten guide fences now COMPILE rather than carrying a fragment marker (646 -> 648); site:build (Build Docs) 30 tasks green; check:doc-fences, check:doc-example-ids, check:doc-types, docs:check-links, check:doc-example-readers, check:control-bytes, check:new-line-citations, check:test-path-roots, check:unreferenced-sources, check:docs-route-closure, check:changeset-claims, changeset:check, check-changeset-presence.mjs all exit 0; markdown-test-inputs.mjs --audit '48 candidate test files, all adjudicated; 43 declared entries, all present'; check-governed-queue-guard.mjs --test 'NOT GOVERNED — 5 path(s) checked, none matched'; components type-check exit 0; components lint 0 errors (956 pre-existing warnings). NOT MEASURED then measured: check:readme-exports was exit 1 on the unbuilt tree with 'the population COLLAPSED -- this run proves nothing' (typesResolved found 0, floor 2) — an instrument failure, not a red gate; green after turbo run build. NARROWING DECLARED: repository-wide 'pnpm lint' over every package was not run; @object-ui/components (the only package whose sources moved) was linted in full and the two edited non-package files directly against the root flat config. Full-farm convergence is CI's. Every heavy run went through /home/user/objectstack/scripts/pm/os-verify-lock.sh and every verdict quoted above is the gate's own printed line, never a bare exit status read across a pipe. CLAUSE-2 CARRIER: needs:contract-review hung on both limbs (card 8021 and PR 9298) via the additive REST endpoint, read back and diffed against the union of the prior sets — nothing stripped. PM_SWEEP_REPO=objectstack-ai/objectui scripts/pm/check-clause2-carriers.mjs --pair 9298 exits 0: 'both carriers agree'.", "mcp_calls": "0 — the whole run used the zero-quota web payload channel for the card and its two binding comments, git for the tree, and REST for the dedup listing, the issue and PR writes, and the labels. No MCP GitHub tool was invoked.", "open_questions": [], "out_of_scope_findings": [ "filed as #9297: the required pre-step sweep came back POSITIVE. Measured with a lit control (18 SchemaRenderer elements across 5 teaching surfaces, so an empty offender list would have been a reading): 9 elements carry a prop the renderer reads none of — data, dataSource, or debug. Two surfaces are repaired in this PR; the other three (content/docs/guide/expressions.md 3 elements + 26 bare-head expression sites, content/docs/guide/architecture.md 1 + 4, root README.md 2 + 7) are that card, because there the element is only half the defect and moving the wiring alone would produce leg D at scale on three pages.", "filed as #9297 (census addendum comment): content/docs/guide/schema-rendering.md itself keeps 6 bare-head sites OUTSIDE this card's three passages — :231/:232 status, :479 loading, :489 items, :504/:506 error. Same population, same judgement call, so the sweep card is four documents rather than three. The repaired page is clean on the wiring coordinate and its three named passages are clean on both.", "noted, not filed (carried into #9297's comment as NOT MEASURED): content/docs/guide/expressions.md around :529 states that ${user.invalidProperty} produces the error \"Cannot read property 'invalidProperty' of undefined\". Leg D measured the neighbouring behaviour as returning the template's own source text with no throw and no warning, so that sentence looks wrong in the same direction — but that exact case was not rendered, so it is an observation to measure, not a reading. Carrier: #9297 already owns that document." ] }Generated by Claude Code