Skip to content

docs(guide): stop authoring ${…} in six expression keys with no carriage row (objectui#7418) - #7847

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7418-expressions-doc-uncarried-keys
Sep 6, 2026
Merged

docs(guide): stop authoring ${…} in six expression keys with no carriage row (objectui#7418)#7847
os-sam merged 1 commit into
mainfrom
claude/issue-7418-expressions-doc-uncarried-keys

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7418

The maintainer's 2026-09-01 ruling chose fork (B): non-text types get no new carriage rows, and the docs stop authoring ${…} in keys that have none — 「文档教现实」. It named progress.value and input.value; both landed with objectui#7115 and are untouched here. This carries the same ruling to the six remaining sites in the same file. No carriage rows are added, and nothing outside content/docs/guide/expressions.md is touched.

The measurement — from the artifact, on this tree

Not from the card's table and not by line number. expressionBindableTextKeysFor read out of the built dist/ui/index.mjs of @objectstack/spec@17.2.0 (this repo's lockfile still resolves that version), which is the same lookup SchemaRenderer.tsx consumes:

text -> []        statistic -> [label, value, description]
card -> [title, description]                button -> [label]
badge -> []       progress -> []            input -> []

SchemaRenderer evaluates exactly four top-level channels — content (no type gate), the carriage keys for the node's type, the properties bag and the props bag — plus the condition keys. Anything else at a node's top level reaches the renderer as the characters the author typed.

A mechanical sweep of all 39 json fences in the page against that lookup reproduced the card's six independently, and found no seventh:

before (39 fences parsed, 0 unparsed) after
8 uncarried sites 2

The 2 that remain are the two list.items occurrences the card deliberately did not claimitems is a data key, not one of the four text keys the carriage map governs, and whether the list renderer resolves it is a separate unmeasured question. Left deliberate, as recorded.

Per site: which repair, and why

Each is one of two repairs, decided by what the passage teaches — not one blanket rule.

section key repair why
Comparison Operators badge.variant moved onto a carrying channel The subject is the operator set; the badge was incidental. Now a text node's content, evaluated on every type, so it still demonstrates binding.
Scoped Data card.body moved onto a carrying key Demonstrating scoped item data. body became description, a real card carriage key on the same node.
Nested Ternary badge.variant moved onto a carrying channel The subject is the nested ternary chain. Chain preserved verbatim, now on text/content.
Status Badge badge.text, badge.variant literal + prose Here the badge IS the subject, so under (B) binding is not available for it. Matches how objectui#7115 rewrote progress and input: name the missing row, author resolved values, keep the condition key as an expression.
Conditional Styling card.className literal + prose Same shape — className carries on no type at all. Prose names it and points at the two working routes; the example gates an authored variant with visibleOn.

So: three sites were demonstrating binding and keep demonstrating it; three could not, and say why in prose rather than being silently downgraded.

Two further defects found in the same passages

Measured through the real production renderers, not read off prose:

  • text is not a badge key at all. BadgeSchema declares label / variant / icon / children, and renderers/data-display/badge.tsx renders schema.label. So "text": "${status}" rendered an empty badge with text="${status}" leaked as a DOM attribute — worse than the literal characters the card predicted. Repaired to label.
  • success / warning / info are not badge variants. The closed set is default / secondary / destructive / outline. All three invalid values appeared in the sites being repaired; authoring a literal forced picking a valid one, so the page no longer teaches any of them.

card.className was the most visible of the six: the expression's own source text landed inside the rendered class attribute.

Is this page gated? Yes — but not where the defect lives

Worth stating plainly, because it is the same shape as objectui#7488.

  • check:doc-types does scan this page and judged its type literals. Its own header states the limit: "NOT in scope, deliberately: whether the snippet's OTHER keys are read by the renderer the type resolves to." Every type here was always valid, so it was always green.
  • check:doc-snippets does cover this page (confirmed by asking the gate's own analyze()), but only its ts/tsx blocks. All six defects live in json fences, which it does not type-check.

⇒ The page is gated, and no gate reads the json fences' non-type keys. All six sat under green gates. objectui#4795's second half (build-time rejection of ${…} outside the carriage map) is what would redden them; this PR clears that path ahead of it.

Verification

Union re-run after the final commit, at 586e637f9:

check:doc-types             EXIT=0   ✅ Every documented component type is registered.
check:doc-snippets          EXIT=0   Semantic phase: 456 of 456 block(s) judged, 0 failed.
check:doc-fences            EXIT=0   ✅ every TypeScript block in 227 document(s) is fenced ts/tsx/typescript
check:docs-route-closure    EXIT=0
check:doc-example-readers   EXIT=0   OK  80 documented symbol(s), 3944 call site(s)
check-doc-links.mjs         EXIT=0   Links are valid across 17 scan roots.
check-control-bytes.mjs     EXIT=0   ✅ OK (scanned 6367 tracked text file(s))

node scripts/check-changeset-presence.mjs — EXIT=0, verbatim:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

Acted on: no changeset added.

Not run, said plainly: the full pnpm lint repo-wide scan and the repo-wide test suite — this diff is one markdown file with no ts/tsx block touched, and CI runs the farm regardless.


Generated by Claude Code

…riage row

The 2026-09-01 ruling chose fork (B): non-`text` types get no new carriage
rows, and the docs stop authoring `${…}` in keys that have none. It named
`progress.value` and `input.value`, both already landed. Six more sites in the
same file are the same class, found by re-reading the file against the carriage
map rather than against the `type: "text"` census.

Measured on this tree from the built artifact — `expressionBindableTextKeysFor`
out of `@objectstack/spec@17.2.0`'s `dist/ui/index.mjs`, the same lookup
`SchemaRenderer.tsx` consumes: `badge` and `progress` and `input` carry nothing,
`card` carries `title`/`description` only.

Each site is repaired by what its passage teaches, not by one blanket rule:

- Comparison Operators, Nested Ternary — the subject is the operator, the badge
  was incidental. Moved onto `text`/`content`, which is evaluated on every type,
  so both keep demonstrating binding.
- Scoped Data — demonstrating scoped `item` data. `card.body` moved to
  `card.description`, a real carriage key on the same node, which also removes
  the half-working node whose `title` evaluated while its sibling did not.
- Status Badge, Conditional Styling — the subject IS the non-carrying component,
  so binding is not available under (B). Literal values plus prose naming the
  missing row and the working alternative, matching how `progress` and `input`
  were rewritten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@os-sam
os-sam marked this pull request as ready for review September 6, 2026 00:15
@os-sam
os-sam added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 0840abb Sep 6, 2026
30 checks passed
@os-sam
os-sam deleted the claude/issue-7418-expressions-doc-uncarried-keys branch September 6, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants