docs(guide): author the button keys the schema actually declares — six text to label, three top-level onClick to action:button - #7931
Merged
Conversation
…ectui#7898)
Nine JSON fences under content/docs/guide/ authored `button` keys that do not
exist on `ButtonSchema`, and no instrument saw them because they all hold
LITERALS: the expression-carriage census only judges sites that author `${...}`,
and check:doc-types only judges the `type` literal.
Six sites spelled the button's text as `text`. Measured from the built
`@object-ui/types` dist: `ButtonSchema.shape` has `label` and has no `text`, and
`button.tsx` renders `schema.label || renderChildren(...)`. Nothing refuses the
misspelling either — `BaseSchema` is `.passthrough()`, so the validator KEEPS the
unknown key. Rendered through the real SchemaRenderer, the node with `text`
produces a button whose textContent is the empty string.
Three sites authored a top-level `onClick`. `ButtonSchema.onClick` is
`handlerKeyRefusal('onClick', 'runtime-slot', ...)`, a `z.custom(() => false)`
that refuses every authored value by name — and because `onClick` is on
SDUI_DOM_PASS_THROUGH_KEYS, an authored string or object is forwarded to the real
DOM listener slot, where React throws on the first click. Repaired onto the
channel the refusal message itself names and the channel measured to dispatch:
an `action:button` node carrying `actionType`.
Judged per passage, never one blanket rule: five sites are teaching how a button
shows text and take `label`; four are teaching how a button binds an action and
become `action:button`. `quick-start.md`'s `events`-bag site is deliberately
untouched.
The census figure is unchanged by this PR, as predicted — 65 expression sites, 61
carried, 4 uncarried in 3 pages, byte-identical before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7898
Nine JSON fences under
content/docs/guide/authoredbuttonkeys thatButtonSchemadoes not declare. All nine are now the keys the renderer actually reads. Every reading below is measured on this branch; the union at the bottom was re-run on the final commit9cd6c894fwithgit diff HEADempty.⭐ Why no instrument saw these nine — the blind spot is the most valuable sentence here
They all hold LITERALS. That places them outside every gate this repo owns:
check-doc-expression-carriage(#7851 / PR #7868)${…}"Submit","Edit","editRecord"— not in its population at allcheck:doc-types(check-doc-component-types)typeliteralcheck:doc-snippetsts/tsxblocksjsonfencesAnd nothing catches them at parse time either:
BaseSchemais.passthrough(), so an undeclared key is not refused, it is KEPT. Measured from the built@object-ui/typesdist:A green parse, the phantom key carried through, and then dropped by the renderer. This is the same geometry as #7896 (a package README's
typeliterals judged by nothing): the defect falls between all the instruments. Per the dispatch, this PR builds no gate — that would introduce a new scan population and trigger this lane's report-only-first rule. #5250 is the instrument-level card for the class, and it carries the.passthrough()half that makes a naive gate vacuous.Finding 1 — six sites spelled the button's text as
textMeasured from the built artifact, not the card:
packages/components/src/renderers/form/button.tsxrendersschema.label || renderChildren(schema.body || schema.children).textis on no read path and is not onSDUI_DOM_PASS_THROUGH_KEYS, so it is not even leaked — it is silently dropped.Render evidence (#6318's standard: it renders differently)
Driven through the real
SchemaRendererand the real registered renderers:Same for
layout.md's "New Order": before,textContent: ""; after,"New Order". The button had no text at all — it rendered as a blank rectangle.Finding 2 — three sites authored a top-level
onClickButtonSchema.onClickishandlerKeyRefusal('onClick', 'runtime-slot', 'Click handler')(packages/types/src/zod/tombstone.zod.ts) — az.custom(() => false)that refuses every authored value. Measured:The refusal is not the whole cost.
onClickis onSDUI_DOM_PASS_THROUGH_KEYS(React synthetic handlers never become attributes, so the whitelist admits it), which means an authored string or object is forwarded bytoFormControlDomPropsinto the real DOM listener slot. Measured — this throws when a user clicks, it is not merely inert:Which channel is actually read — measured, not assumed
The dispatch asked me to measure whether the remedy is the
eventsbag or something else. It is not theeventsbag.eventsis declared onEventableSchema(packages/types/src/api-types.ts) and re-exported, but a repo-wide search for.events/?.eventsacrosspackages/**/srcandapps/**/srcfinds zero SDUI read points — every hit is audit events, validation-rule events, or calendar/timelineevents.EventableSchemahas no mirror, no extender and no reader; that is #6497, whose status note records the 2026-08-25 ruling on #6182 (Option A): the authorable form for actions is the declarative action object, andEventableSchemare-prices to an ADR-0049 retirement rather than becoming the seat of any wiring.So the repair uses the channel the refusal message itself names and that I measured to dispatch — an
action:buttonnode carryingactionType:This is the spelling
content/docs/guide/record-edit-modes.mdalready teaches ("The handler name goes inactionType") and thatcontent/docs/core/app-schema.mdxalready points at ("the handler-expression string is refused by name — author anaction:buttonnode instead").Repaired per passage, never one blanket rule
architecture.mdtexttolabellayout.mdtexttolabellayout.mdbuttontoaction:button,texttolabel,onClicktoactionTypelayout.mdtexttolabelschema-rendering.mdbuttontoaction:button;onClickobject toactionType+endpoint+methodThe
schema-rendering.mdsite had three defects in one node, all now stated in prose: the refused top-levelonClick;ajax, which is not an action type (the built-in vocabulary isscript/url/modal/flow/api/form, plus objectui'snavigationalias, plus host-registered handler names); andapi, which is not a key — the endpoint key isendpoint, withmethod.✅ Deliberately untouched
content/docs/guide/quick-start.md:149— recorded by #7872's seat as measured-and-NOT-a-defect, and out of this PR's surface. Not touched. (My own measurement of theeventsbag disagrees with that note; see the paragraph above and #6497. That page's repair belongs to #6497's retirement chain, not to this card — reported, not acted on.)Seven of the nine sites sit inside
page.actions, andpage.actionshas no reader on thepagenode.PageNodeSchemadoes not declare it (it survives a parse only via.passthrough()),PageRendererhas zero read points for it, and the array is stringified onto the wrapper element asactions="[object Object],[object Object]". Measured: that page renders 0 buttons; the same two buttons moved intopage.bodyrender 2.That is a capability decision (should
pagegrow anactionsreader, or should the pages move ontobody/regions+page:header?), not a documentation fix, so this PR repairs the button keys and stops. Filed as #7926. The button-key repair is correct independently of how #7926 is ruled —labelis the button's text key either way.The census is unchanged, as predicted
check-doc-expression-carriagebefore and after this PR is byte-identical:Confirming the blind-spot claim: these nine hold literals and are entirely outside its population.
Gate union — re-run on
9cd6c894f,git diff HEADemptyExit codes captured by redirecting first, never through a pipe. Verdict lines are the gates' own:
check-doc-expression-carriage✅ Blind spot: none — every fence above was parsed and judged.(4 pre-existing uncarried sites, unchanged)check:doc-types✅ Every documented component type is registered.(889 type literals, 772 registered, 117 exempted)check:doc-fences✅ check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript…check-doc-linksLinks are valid across 17 scan roots.check:doc-example-readersOK 80 documented symbol(s), 3946 call site(s)… no @example hand-spells one.check:doc-snippetsEvery covered documentation snippet compiles against the built types.(456/456 blocks judged, 0 failed — after building the gate's own 26-package scoped closure; the first run was EXIT=2 PRECONDITION NOT MET, not a red gate)check:control-bytes✅ check-control-bytes: OK (scanned 6400 tracked text file(s); skipped 85 binary).check:handler-key-readsOK 106 arm(s)… every judged read is a declared member of its arm.check-governed-queue-guard --test✅ NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched.Changeset — the checker's verdict, quoted verbatim, not guessed:
Clause-② is
noas dispatched: doc examples only — no key added, no zod touched, no gate predicate or scan population moved. Nothing outsidecontent/docs/guide/.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code