Skip to content

docs: publish each page's own values through the scope channel on the three remaining teaching surfaces - #9376

Draft
os-tesla wants to merge 2 commits into
mainfrom
claude/issue-9297-teaching-surfaces-scope-names
Draft

docs: publish each page's own values through the scope channel on the three remaining teaching surfaces#9376
os-tesla wants to merge 2 commits into
mainfrom
claude/issue-9297-teaching-surfaces-scope-names

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Part of #9297. ⚠️ Deliberately not a closing keyword: this repair covers the three surfaces that card's dispatch scoped, and the card's own addendum (comment 5646840050) measured six more sites on content/docs/guide/schema-rendering.mdstatus x2, loading, items, error x2 — which are out of scope here and are not moved by PR #9369 either (its hunks land at :68, :79-131 and :409-446; those six sit at :231, :232, :479, :489, :504, :506). That half stays open.

What was wrong, and what moved

SchemaRendererProps declares exactly one prop, schema. A data, dataSource or debug written on the element is forwarded to the component the schema names — not read, not refused. Nothing throws, nothing warns, and the expression reaches the screen as the characters the author typed.

Per objectui#8021 leg D, moving only the wiring produces a page that reads as repaired and still prints raw source. So both coordinates move together on every site: the wiring becomes the provider that actually carries the value, and every head name on the page is judged against the scope the tree builds.

Re-measured census, with the card's own lit control

Measured on this branch's base 69aa9c017 with the card's classifier — SchemaRenderer elements matched with a word boundary after the name, so SchemaRendererProvider is excluded by construction.

surface tag-shaped hits (base → head) real JSX elements forwarded look-alikes (base → head)
content/docs/guide/schema-rendering.md (out of scope) 5 → 5 5 0 → 0
packages/react/README.md (out of scope) 4 → 4 4 0 → 0
content/docs/guide/expressions.md 6 → 3 3 3 → 0
content/docs/guide/architecture.md 1 → 1 1 1 → 0
README.md (repo root) 2 → 2 2 2 → 0
lit control 18 → 15 15 6 → 0

The control reproduces the card's 18 exactly — and shows the 18 is not a count of elements. Three of them are SchemaRenderer MENTIONS inside doc-snippet: fragment HTML comments on expressions.md, which the regex cannot tell from an element. Strip HTML comments first and the base has 15 real JSX elements, not 18. The tag-shaped count falls to 15 here only because those three comments were rewritten; no renderer element was added or deleted (real elements: 15 → 15).

Per-site judgement — 37 bare head names, individually judged

The ruling on objectui#9308 (option B) changed the question. Before it, the renderer bound a closed five-name root set, so "head name not in the set" was close to a verdict. After it, the root set is open: every key the host publishes through PredicateScopeProvider is a root. ⇒ the test is no longer is this name in the set, it is does the page publish this name, and does it say so.

The rule applied, stated once so each row can be checked against it: a head name moves to record. when the passage says the value is the row the surface is bound to; it stays bare when the passage presents it as a value the host computed and published.

Moved — 5 sites

site head why it moved
expressions.md :148 disabledOn form form is not a root on any tier; nothing in ObjectUI publishes one. Rewritten to record.status.
expressions.md :439 Dependent Fields form A form's own values ARE the row under edit ⇒ record.country.
expressions.md :470 Computed Fields form Same row ⇒ record.price * record.quantity.
architecture.md :247 Conditional Rendering form visible rewritten to current_user.role — the ambient root the host's ExpressionProvider really publishes.
architecture.md :248 Conditional Rendering form disabled rewritten to record.status.

Judged legitimate, left bare — 32 sites

site(s) head why bare is correct
expressions.md x8 users The page's running collection; a root the host publishes. The Data Context section now publishes and names it.
expressions.md :172 settings Published in the same passage, alongside user, by the provider the block now mounts.
expressions.md x2 price Host-computed value in the Operators section.
expressions.md score, count, total, items Same class: values the host computed and published.
expressions.md x2 status Host-published status value on standalone nodes, not inside a record surface. ⛔ Not moved to record. — that would assert a row context the passage never establishes.
expressions.md x2 task A host-published object the card is about; same reasoning.
expressions.md :567 formatCurrency ⭐ The card named this one: it sits in "Custom Functions", where evaluateExpression is handed its own context. Legitimate by construction. Its surrounding prose was still wrong and is repaired (see below).
expressions.md :612 adultUsersScore "Pre-compute complex logic" — a pre-computed root the reader publishes. That IS the lesson.
expressions.md :621, :626 x, isAdmin "Use Meaningful Variable Names" — a naming contrast. The names are root-level by construction; changing them destroys the lesson.
architecture.md x2 orders Host-published collection in Data Transformations.
README.md x7 stats The flagship example's own bag. The bare name is right; what was wrong is that it arrived as a forwarded prop. Now published through PredicateScopeProvider.
expressions.md item, index Excluded up front by the card: list renderers publish them through a different mechanism. Untouched.
expressions.md Math, new JS built-ins, not scope roots. My scan counts 29 heads on that page where the card counted 26; the difference is exactly these three.

⭐ A site the census could not see — and it is the one the ruling breaks

content/docs/guide/architecture.md:55 authored visible: "${data.age > 18}". The census classifier held data in the accepted root set, so this site was invisible to the offender column — and data is precisely the root objectui#9308 retires.

Measured on the built evaluator (control and both legs in the "Verification" section): a missing root and a present-but-undefined root are not the same. With { data: undefined } the predicate is false; with no data key at all it is unevaluable and this surface fails soft to true. ⇒ a data.* gate authored from these pages used to hide its node on every row and now shows it. The site moves to record.age (the runtime layer's canonical root, ADR-0089 D3) and the guide states the flip in the migration note, rather than only renaming a provider.

The Debug Mode block — both wrong props, not one

expressions.md's Debug Mode block wrote data={…} and debug={true} on the element. debug is read off the provider context (context?.debug || context?.debugFlags?.enabled), so both were forwarded and neither did anything. The block is now a SchemaRendererProvider mount (with dataSource={null}, since that prop is required and this mount is about the flag), and the prose names the mechanism for both props.

Adjacent to it, the Custom Functions passage still stated the old scope verbatim — "the provider's data source (as data), the host scope and page variables". That sentence is what the ruling deletes; it is rewritten in the same commit, because leaving it would have contradicted the new Data Context table on the same page.

Shape

Matched to PR #9369, which moved the equivalent teaching on the two surfaces this card excludes: publish host values under a real name through PredicateScopeProvider, read them by that name, and state that dataSource is the adapter and not a root.

Changeset

Not owed, by measurement rather than by assumption:

$ node scripts/check-changeset-presence.mjs
Compared the working tree with 69aa9c017 (merge-base with origin/main): 3 file(s) changed,
0 of them published source of a package the release covers, 0 of them a manifest whose
published contract moved, 0 under a package changesets ignores, 0 changeset(s) added.
✅  No source or published contract of a released package changed in this range, so no changeset is owed.

Acceptance notes

Observed while judging these pages, noted, not filed:

The two behaviours a reader is now told about, measured

Both legs run against the built evaluator (packages/core/dist), the same artefact check:doc-snippets resolves against, each with a control that fires.

Leg 1 — the verdict flip. At the visibility layer (evaluateCondition, the layer a visible / visibleOn gate uses):

{ data: undefined }   data.status == 'draft'             => false
no data key at all    data.status == 'draft'             => true
  CONTROL — a published root, both polarities must differ:
{ record:{status:'draft'} } record.status=='draft'       => true
{ record:{status:'open'}  } record.status=='draft'       => false

⇒ a data.* gate authored from these pages used to hide its node on every row and now shows it. One layer down, on the raw ExpressionEvaluator, the missing-root case returns the template's own source text — which is the interpolation half of the same fact. Both halves are in the migration note.

Leg 2 — what an unresolvable expression really does (the card addendum's NOT-MEASURED item):

{ user: { name: 'Alice' } }  ${user.invalidProperty} => undefined
{} (no user root)            ${user.invalidProperty} => "${user.invalidProperty}"
evaluateExpression, no roots ${user.invalidProperty} => "${user.invalidProperty}"
  CONTROL (a resolvable one on the same instrument):
{ user: { name: 'Alice' } }  ${user.name}            => "Alice"

⇒ nothing is thrown on either path. The guide said Cannot read property 'invalidProperty' of undefined; that is false whether or not the root exists.

Leg 3 — a bare head name IS a root when the host publishes it, which is what makes 32 of the 37 sites legitimate:

{ stats: { users: 1234 } }   ${stats.users}          => 1234
{} (nothing published)       ${stats.users}          => "${stats.users}"

Verification

All runs at head 20831dc1d, heavy ones serialised through the shared verify lock.

run verdict
pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2 Tasks: 35 successful, 35 total · BUILD EXIT=0
pnpm check:doc-snippets Semantic phase: 650 of 650 block(s) judged, 3 failed. · EXIT=1 — ⭐ all three are the inherited ones, see below
pnpm check:doc-fences EXIT=0 — every TypeScript block in 227 document(s) fenced ts/tsx/typescript
node scripts/check-doc-component-types.mjs EXIT=0 — every documented component type is registered
node scripts/check-doc-example-ids.mjs EXIT=0 — 414 real reference(s) resolve
node scripts/check-doc-links.mjs EXIT=0 — links valid across 17 scan roots
node scripts/check-control-bytes.mjs EXIT=0 — 7539 tracked text file(s)
node scripts/check-changeset-presence.mjs EXIT=0 — no changeset owed
node scripts/check-changeset-claims.mjs EXIT=0 (report-only)
node scripts/check-doc-expression-carriage.mjs EXIT=0 (report-only); its two hits on expressions.md (list.items authoring ${users}) are pre-existing and unchanged
node scripts/check-governed-queue-guard.mjs --test (the three paths) ✅ NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched
named witnesses, pnpm exec vitest run (6 files) Test Files 6 passed (6) · Tests 112 passed (112) · EXIT=0

check:doc-snippets' own controls all fired in that run, so the reading is a reading and not a degraded harness: resolution resolved @object-ui/types to the built dist/index.d.ts, sentinel produced TS2305, positive produced 0.

Named witnesses. Derived by hand from scripts/markdown-test-inputs.mjs, not guessed: packages/components/src/__tests__/page-body-single-node-8310.test.tsx reads the root README.md (it asserts the Basic Usage schema literal still spells its child list children, carries type: "grid" and three type: "statistic", and the three labels) and four tests walk content/docs/**. The objectui#8021 pin was run too, even though its TEACHING_SURFACES are the two documents this PR does not touch. All green.

Inherited reds — named, not repaired.

Drafted by the os-dev seat in session session_01UzHd6hDYatoDn17BuwKxnZ — attribution repeated as prose here because the footer block below does not reliably survive a later edit.


Generated by Claude Code


Generated by Claude Code

…me the three props the renderer never reads

The three remaining teaching surfaces from the objectui#8021 sweep — the root
README, the expression guide and the architecture guide — each wrote the page's
values onto the `SchemaRenderer` element and read them back under a bare head
name. `SchemaRendererProps` declares exactly one prop, `schema`; `data`,
`dataSource` and `debug` are forwarded to the component the schema names, so
nothing throws, nothing warns, and the expression reaches the screen as the
characters the author typed.

Both coordinates move together on every site, per objectui#8021 leg D:

* the six forwarded look-alikes become the provider that actually carries the
  value — `PredicateScopeProvider` for the expression scope,
  `SchemaRendererProvider` for the adapter and for `debug`;
* the head names are judged one at a time against the scope the tree builds.
  Thirty-two stay bare, because after the objectui#9308 ruling the root set is
  open: every key the host publishes is a root. Five move to `record.*` — the
  two form-row sites and the three predicates written against a `form` root that
  nothing in ObjectUI publishes.

`content/docs/guide/architecture.md` also carried the one `data.*` gate on these
pages. The census could not see it — its classifier held `data` in the ACCEPTED
root set — and it is exactly the spelling the ruling retires, so it moves to
`record.age` and the guide states the verdict flip: a missing root and a
present-but-undefined root are not the same, so a `data.*` gate that used to hide
its node on every row now shows it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 13, 2026
…s, measured

The Expression Errors block claimed `${user.invalidProperty}` yields
`Cannot read property 'invalidProperty' of undefined`. objectui#9297's addendum
flagged that as looking wrong and explicitly left it NOT MEASURED. Measured now,
on the built evaluator, and it is wrong in both directions:

  { user: { name: 'Alice' } }  ${user.invalidProperty}  => undefined
  {} (no user root)            ${user.invalidProperty}  => "${user.invalidProperty}"
  control, same instrument:    ${user.name}             => "Alice"

Nothing is thrown on either path. A missing MEMBER of a published root renders
as nothing; a missing ROOT renders as the characters the author typed, with one
console line.

Repaired here rather than filed because the same commit series now states, two
sections up, that an unresolvable template is returned as its own source text —
leaving the old sentence would have made the page contradict itself.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@os-tesla os-tesla changed the title docs: publish the page own values through the scope channel on the three remaining teaching surfaces docs: publish each page's own values through the scope channel on the three remaining teaching surfaces Sep 13, 2026

os-sam commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Ruled: option A. The maintainer took option A on this PR's open question (Part of #9297, ⛔ no closing keyword, objectui#9297 stays open carrying the six schema-rendering.md sites). Full record with the authorization quoted verbatim: objectui#9297 comment 5652359953.

The body already implements it — verified at head 20831dc1d, first line Part of #9297 and no closing keyword anywhere. ⇒ no edit is owed here.

⛔ Binding on whoever lands this: do not add a closing keyword in the body, the squash message or a review suggestion, and do not close objectui#9297 at merge.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants