docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs: teach content as text's expression channel, not value (29 occurrences, not 23) - #7114

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content
Sep 1, 2026
Merged

docs: teach content as text's expression channel, not value (29 occurrences, not 23)#7114
os-warren merged 3 commits into
mainfrom
claude/issue-7015-expressions-docs-content

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7015

Docs-only. Switches authored examples that carry an expression in a text node's
value over to content, the ruled sole evaluation channel (maintainer ruling
2026-08-31, decision batch #17, option 2 on objectstack#13670).

Why the spelling matters (measured, not assumed)

packages/components/src/renderers/basic/text.tsx:51 renders
{schema.content || schema.value} — so valueis read back. What it never
gets is evaluated: SchemaRenderer evaluates the spec-declared carriage keys via
expressionBindableTextKeysFor(type), and text has no value row (the map's
answer for an unlisted type is the empty set). Net effect: the reader sees the
literal ${...} on screen. content is evaluated unconditionally on every
component type
— its leg in SchemaRenderer carries 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 files
it names. "Concentrated in" was not "only in": three more files carry six more.

FileIn scopeFence
content/docs/guide/expressions.md20json
content/docs/guide/architecture.md3json x2, tsx x1
content/docs/guide/schema-rendering.md3json
content/docs/blocks/block-schema.mdx2ts x2
content/docs/guide/dashboard-filters.md1json
Total29

Before: 29 text-node value-with-expression. After: 0.
Control for that zero — the identical query in the same run still returns the two
non-text hits 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:388type: "progress", and :453type: "input". Not
    text nodes; retargeting their value would be an unruled behaviour edit.
    Both are also outside the carriage map, so they likely render literals too —
    reported rather than touched.
  • The published skills surface — 7 hits, all correct as written, and changing
    them would be destructive.
    schema-expressions.md and page-builder.md use
    text + value as deliberate counter-examples teaching this exact rule, each
    paired with its ✅ content fix; the rest are statistic, which genuinely
    declares a value row. Zero files under the skills directory are touched here.
  • packages/plugin-dashboard/README.md (metric-card), packages/react/README.md
    (input) — non-text nodes. Root README.md teaches stat-card, which nothing
    registers — a different defect class, reported separately.
  • schema-rendering.md:34text + value with no expression; renders fine
    through the fallback. Out of scope by the card's own rule.
  • One false positive: an examples/ editor fixture whose ${name} is JavaScript
    template-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 in expressions.md
"Best Practices". They declare no type, so they sit outside the ruling's literal
wording, but both halves of both ✅/❌ pairs used value — and since the carriage
map answers the empty set for an absent type, the ✅ halves were teaching a
spelling that evaluates for nothing. content is right whatever type the reader
substitutes. To keep strict ruling scope: git revert 39d3eb151.

Gates — all run at final HEAD 3476c2c25

GateExitVerdict
check-doc-snippet-types0green — "Every covered documentation snippet compiles against the built types"; 272/272 blocks judged, 0 failed
check-doc-component-types0green — every documented component type is registered
check-doc-fence-languages0green
check-doc-links0green — valid across 17 scan roots
check-control-bytes0green — 5893 files scanned
check-changeset-presence0green — "no changeset is owed"
check-changeset-no-major0green
vitest x6 gate suites0266/266 passed

check-doc-snippet-types needed the built closure and first returned
PRECONDITION 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]: any index signature accepts both spellings identically.
That is reasoning, and check-doc-snippet-types green 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 not major (the guard refuses that outright).


Generated by Claude Code

… `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-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT — PM seat (domain:ui), reviewer of record

Landing armed. CI is 28/28 completed at 3476c2c2525 success, 3 skipped, 0 failure — including Doc Snippet Type Check (the gate that actually judges the three compiled snippets) and Changeset Declaration.

I re-measured the census independently, with a different query than yours

Not because the report was doubted — because a census is the whole deliverable here, and a number nobody re-derived is a number on trust. Using a JSON-key regex rather than your node-aware sweep, on landed main vs. your head:

main5f4514f7bbranch 3476c2c25
"value" holding an expression, content/docs32 (arch 2 · dashboard-filters 1 · expressions 26 · schema-rendering 3)2
"content" holding an expression ⚠️control030

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.

  1. 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.
  2. "Docs-only, therefore prose." Three edits live in compiled snippets under check-doc-snippet-types. You resolved the risk to nil via BaseSchema's [key: string]: any — and then, correctly, did not stop at the reasoning: the gate green is the measurement.
  3. 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:

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

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

Labels

None yet

Projects

None yet

2 participants

@os-warren@claude