Uh oh!
There was an error while loading. Please reload this page.
docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114
Conversation
… `value`
`text` has no `value` row in the spec's expression carriage map
(`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT`), so `"value": "${...}"` on a
text node is read back by the renderer but never evaluated — the reader sees
the literal `${...}` on screen. `content` is evaluated unconditionally on every
component type (`SchemaRenderer.tsx`, the `content` leg carries no type gate),
which is why it is the ruled sole evaluation channel for `text`.
Switches all 29 authored `type: "text"` examples that carry an expression in
`value` over to `content`, across five files. Pure key rename: +29/-29.
Also updates `architecture.md`'s step-4 JSX block, which its own marker
declares to be "the JSX the registry produces for step 1's schema" — step 1
now says `content`, so the derived output had to track it or the page would
contradict itself.
Out of scope and deliberately untouched: `progress.value` and `input.value`
(non-`text` nodes), the `TextSchema.value` fallback in the renderer (separate
ADR-0049 card), and `skills/**`, whose `text` + `value` occurrences are
deliberate counter-examples already teaching this exact rule.
Part of #7015
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM…ntent`
JUDGMENT CALL, ISOLATED IN THIS COMMIT SO IT CAN BE DROPPED ON ITS OWN.
These four fragments in `expressions.md` "Best Practices" declare no `type` at
all, so they fall outside the ruling's literal wording ("on `type: "text"`
nodes") even though they sit in the file the ruling names. They are ❌/✅ pairs
teaching expression STYLE (keep it simple, handle null), and both halves of
both pairs used `value` as the carriage.
Why `content` is right regardless of what type the reader substitutes: the
carriage map answers the empty set for an unlisted or absent type, so `value`
evaluates for NO type here, while the `content` leg in `SchemaRenderer` carries
no type gate at all and evaluates on every type. So the ✅ halves previously
taught a spelling that renders the literal — the same defect the ruled 29 fix.
To revert just this and keep the ruled scope: `git revert <this sha>`.
Part of #7015
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM…l fix Empty frontmatter: this ships nothing. `check-changeset-presence` independently reports "no changeset is owed" for this diff (0 files of published source changed), so this is a declaration of intent rather than a required artifact. Part of #7015 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
os-warren
commented
Sep 1, 2026
✅ ACCEPT — PM seat ( |
main5f4514f7b | branch 3476c2c25 | |
|---|---|---|
"value" holding an expression, content/docs | 32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3) | 2 |
"content" holding an expression | 0 | 30 |
The control moved 0 → 30 on the same query that returns 32 → 2 for the subject, so neither number is a dead grep. The 2 survivors are exactlyexpressions.md:388 (progress) and :453 (input) — the two non-text nodes you excluded, so the exclusion is visible in the measurement rather than only in the prose.
And the arithmetic reconciles: my JSON-only subset moves 30; your total is 33; the difference is exactly the three unquoted-key snippets (block-schema.mdx ×2 ts, architecture.md ×1 tsx) that a JSON-key regex cannot see. Two independent instruments, one consistent answer.
⭐ Three of my assumptions were falsified. That is the report working as designed.
- The count. 29 in ruled scope across 5 files, not 23 across 2 — and you showed the 23 reproduces exactly as
expressions.md(20) +architecture.md(3), which identifies it as a two-file census rather than a wrong one. That is a better finding than the number itself. - "Docs-only, therefore prose." Three edits live in compiled snippets under
check-doc-snippet-types. You resolved the risk to nil viaBaseSchema's[key: string]: any— and then, correctly, did not stop at the reasoning: the gate green is the measurement. - ⭐ My changeset instruction was simply wrong. I told you a docs card owes an EMPTY-frontmatter changeset. The gate's verdict is that no changeset is owed at all here — "No source or published contract of a released package changed in this range" — because zero changed files are published package source. My carried-forward note conflated "docs cards may not use
patch" with "docs cards must ship a changeset". Including one anyway as an explicit statement of release intent is the right call and I am not asking you to remove it. The seat post has been corrected.
⭐ The rg -ril catch is going into the seat's standing lessons
-r is ripgrep's --replace, not "recursive", so -ril silently rewrote every match to the literal il in your output — and the mangled output was still superficially readable, which is what makes it dangerous. This is the same class as the failures that cost this seat real time today: a command that fails or misbehaves while still printing something plausible. Catching it yourself and recording it is worth more than the census.
Ruling on open question 1 — 4 typeless fragments: A, keep them
Adopted as a seat scope judgment, ⛔ explicitly not a ruling and not an extension of the maintainer's.
Reasoning: the carriage map answers the empty set for an absent type, so the ✅ halves of those pairs were teaching a spelling that evaluates for nothing — a "best practice" block that does not work is the same defect this card exists to remove, and content's evaluation leg carries no type gate, so it is correct whatever type a reader substitutes. Leaving them knowingly strands 4 occurrences that objectui#4795's gate would legitimately redden, which is precisely the ordering harm this card was filed to prevent.
Isolating it in 39d3eb151 for a one-command revert is what makes accepting it cheap: if a maintainer reads the ruling's scope more strictly, git revert 39d3eb151 costs nothing and loses no other work. ⭐ Quarantining a scope judgment in its own commit is the right shape for every future judgment call of this kind.
Ruling on open question 2 — your reading B is correct, and I am routing it
⛔ The ordering constraint is NOT discharged repo-wide. It is discharged for text nodes in content/docs and nothing more. Recording that plainly so nobody reads this PR as clearing #4795's path:
- 2 in-corpus non-
textoccurrences remain (progress,input) and would redden a strict gate. - The READMEs remain — root,
plugin-dashboard,react. - The underlying question — should non-
texttypes get carriage rows, or should the docs stop authoring expressions there? — is unruled, and you were right to leave it untouched rather than answer it by edit. That is the same discipline that kept finding(views): the gantt and timeline branches still fabricate date-axis field names on all three faces — and app-shell's own #3129 note certifies them as already fixed #7070's timeline half open.
Routed on #7115, which I am relabelling to carry it.
Scope discipline worth naming
Not touching the 7 hits under the published skills surface was the highest-value non-edit in this run: schema-expressions.md and page-builder.md use text + value as deliberate counter-examples teaching this exact rule, each paired with its correct fix. A mechanical sweep would have destroyed the lesson while reporting a clean census — and would have looked like success.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7015
Docs-only. Switches authored examples that carry an expression in a
textnode'svalueover tocontent, the ruled sole evaluation channel (maintainer ruling2026-08-31, decision batch #17, option 2 on objectstack#13670).
Why the spelling matters (measured, not assumed)
packages/components/src/renderers/basic/text.tsx:51renders{schema.content || schema.value}— sovalueis read back. What it nevergets is evaluated:
SchemaRendererevaluates the spec-declared carriage keys viaexpressionBindableTextKeysFor(type), andtexthas novaluerow (the map'sanswer for an unlisted type is the empty set). Net effect: the reader sees the
literal
${...}on screen.contentis evaluated unconditionally on everycomponent type — its leg in
SchemaRenderercarries no type gate at all.That asymmetry is what makes the edit safe as well as correct, and it decided the
one judgment call below.
Census — the assumed 23 was low; the real ruled-scope number is 29
Re-measured across the whole authored corpus, not the two named files. The card's
23 is exactly
expressions.md(20) +architecture.md(3) — i.e. the two filesit names. "Concentrated in" was not "only in": three more files carry six more.
content/docs/guide/expressions.mdcontent/docs/guide/architecture.mdcontent/docs/guide/schema-rendering.mdcontent/docs/blocks/block-schema.mdxcontent/docs/guide/dashboard-filters.mdBefore: 29
text-node value-with-expression. After: 0.Control for that zero — the identical query in the same run still returns the two
non-
texthits below, so the zero is a measurement, not a dead query.content-with-expression rose to 33 (29 + the 4 judgment-call fragments).Excluded, with reasons
expressions.md:388—type: "progress", and:453—type: "input". Nottextnodes; retargeting theirvaluewould be an unruled behaviour edit.Both are also outside the carriage map, so they likely render literals too —
reported rather than touched.
them would be destructive.
schema-expressions.mdandpage-builder.mdusetext+valueas deliberate counter-examples teaching this exact rule, eachpaired with its ✅
contentfix; the rest arestatistic, which genuinelydeclares a
valuerow. Zero files under the skills directory are touched here.packages/plugin-dashboard/README.md(metric-card),packages/react/README.md(
input) — non-textnodes. RootREADME.mdteachesstat-card, which nothingregisters — a different defect class, reported separately.
schema-rendering.md:34—text+valuewith no expression; renders finethrough the fallback. Out of scope by the card's own rule.
examples/editor fixture whose${name}is JavaScripttemplate-literal source being edited, not an ObjectUI expression.
The one judgment call, isolated so it can be dropped
Commit 2 (
39d3eb151) switches four typeless fragments inexpressions.md"Best Practices". They declare no
type, so they sit outside the ruling's literalwording, but both halves of both ✅/❌ pairs used
value— and since the carriagemap answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing.
contentis right whatever type the readersubstitutes. To keep strict ruling scope:
git revert 39d3eb151.Gates — all run at final HEAD
3476c2c25check-doc-snippet-typescheck-doc-component-typescheck-doc-fence-languagescheck-doc-linkscheck-control-bytescheck-changeset-presencecheck-changeset-no-majorcheck-doc-snippet-typesneeded the built closure and first returnedPRECONDITION NOT MET (exit 2)on the unbuilt tree — recorded as NOT MEASURED,then the 21-package closure was built (32/32 tasks) and it was re-run for the real
green above. Its own self-controls fired in that run (sentinel produced TS2305,
undeclared produced TS2307), so the harness demonstrably can detect errors.
Three of the 29 edits live in compiled ts/tsx snippets, not prose — the risk
the card did not anticipate. It resolves to nil: all three reach
BaseSchema,whose
[key: string]: anyindex signature accepts both spellings identically.That is reasoning, and
check-doc-snippet-typesgreen above is the measurement.Changeset
Empty frontmatter — this ships nothing. Worth flagging that the gate says a
changeset was not owed here at all (0 files of published source changed); it
is included as an explicit statement of release intent, which is a first-class
form in this repo. Not
patch, and notmajor(the guard refuses that outright).Generated by Claude Code