Repository navigation
feat(components): make ui:text honour the published variant and align enums - #7445
Merged
Merged
Conversation
…align` enums
`TextSchema.variant` is a nine-value enum (`h1`…`h6`, `body`, `caption`,
`overline`) and `TextSchema.align` a four-value one, and the `ui:text` renderer
read neither: `variant: 'small'`, `variant: 'body'`, `variant: 'h1'` and the key
absent all produced byte-identical DOM, while the enum still refused five
`examples/schema-catalog` entries written in the shadcn scale. Declared,
published, validated against — and unread.
The maintainer ruling of 2026-09-02 took ENFORCE over retiring the key, over
replacing the enum with the shadcn scale, and over widening it without
implementing it. So the enum does not move; the renderer catches up to it:
- every published variant maps to a typographic class no other value carries,
modelled on `ElementTextRenderer`'s existing map (`h3` is its `heading`
verbatim, `body` and `caption` are its own entries);
- `h1`…`h6` render the heading element they name; `body`, `caption` and
`overline` keep the inline span this renderer has always wrapped with;
- all four alignments reach the DOM, each paired with `block` so alignment is
visible on the inline arm rather than declared and inert;
- the registration's `inputs` declare both keys, with the published enums;
- a node authoring neither key renders exactly as before — the Zod mirror's
`.default('body')` materialises on a PARSED document, and synthesising it
here would restyle every corpus text node that never asked for a variant.
Corpus: `lead` → `heading-4` (`h4`), `large` → `heading-5` (`h5`), `small` →
`caption` (`caption`), `paragraph` → `body`, and `muted` deleted — it names a
colour, not a typographic step, and `text-with-colors` already demonstrates it.
A sixth node in the same shadcn scale, `components-feedback-loading/with-text`,
is repaired with them and pinned by a corpus-wide sweep.
Fixes #6942
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-project-manager
marked this pull request as ready for review
September 3, 2026 05:28
os-project-manager
deleted the
claude/issue-6942-text-variant-enforce
branch
September 3, 2026 05:42
This was referenced Sep 3, 2026
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 #6942
The
ui:textrenderer now readsTextSchema.variantandTextSchema.align. The published enums do not move —packages/types/src/zod/layout.zod.tsis untouched — the renderer catches up to them. Authority: the director-seat ruling in comment 5509027843 (summon #8, 2026-09-02, maintainer verbatim 「7189 A 其他同意」), option B1, ENFORCE. Clause-② no.The class map, and what it was modelled on
ElementTextRendereralready carries this concept for theelement:textnamespace:packages/components/src/renderers/basic/elements.tsx,VARIANT_CLASSandALIGN_CLASS(lines 100-112), consumed byElementTextRendererat line 114. The new map follows it as closely as the two vocabularies allow — three entries are its entries verbatim:ui:textvalueh1text-4xl font-semibold tracking-tighth1element:textheading idiom, one Tailwind step uph2text-3xl font-semibold tracking-tighth2h3text-2xl font-semibold tracking-tighth3element:text'sheadingh4text-xl font-semibold tracking-tighth4h5text-lg font-semibold tracking-tighth5subheadingsize, heading weight)h6text-base font-semibold tracking-tighth6bodytext-sm text-foregroundelement:text'sbodycaptiontext-xs text-muted-foregroundelement:text'scaptionoverlinetext-xs font-medium uppercase tracking-widest text-muted-foregroundelement:textsibling — conventional small-caps label treatmentalign:left/center/right/justifymap toblock text-left…block text-justify. Four values, notElementTextRenderer's three, becauseTextSchemapublishesjustifyas well.⛔ The two vocabularies are not reconciled here and
element:textis not touched — that drift is the follow-up card the ruling reserves.Four judgement calls a reviewer should look at directly
body. The Zod mirror declares.default('body'), which materialises on a parsed document; the renderer is handed the authored node. Synthesising the default here would puttext-sm text-foregroundon the ~690 corpus text nodes that never asked for a variant — a corpus-wide restyle, not the repair that was scoped. A node authoring neither key renders byte-identically to before.blockis paired with every align class. Three of the nine variants render an inline span, andtext-centeron an inline box centres nothing — declaringalignand shipping it inert would re-enter this card's own failure mode one layer down. On the heading elementsblockis a no-op.h1-h6render the heading element they name. A value that saysh1and paints a span would deliver the size and withhold the semantics.body/caption/overlinekeep the span this renderer has always wrapped with, so nothing about inline usage changes for them.inputsdeclaresdefaultValue: 'body'forvariant. That restates the published default for a designer seeding a new node; it is not a claim about the absence path (1).The five catalog entries — per-entry decision
components-basic-text/paragraphpbody(file kept)pis body copy andbodyis the published spelling of exactly that. The only exact match of the five.components-basic-text/smallsmallcaption, renamedcaption.jsonsmallis the small de-emphasised step;captionis the published value for that role. Content re-authored "Caption text" so the tile names what it demonstrates.components-basic-text/leadleadh4, renamedheading-4.jsonleadis the larger of the two intermediate shadcn steps (text-xlagainstlarge'stext-lg), so it takes the higher remaining published step.components-basic-text/largelargeh5, renamedheading-5.jsonlargeistext-lg font-semibold— the next step down, and what the publishedh5step renders here.components-basic-text/mutedmutedmutednames a COLOUR, not a typographic step, so no published value expresses what it meant. Re-spelling itcaptionwould demonstrate the same rendering as the re-authoredsmall.json— the duplicate this must not produce — andcomponents-basic-text/text-with-colorsalready demonstratestext-muted-foregroundon a text node (its third child is literally "Muted Text").No published value is demonstrated twice. The category now authors
h1,h2,h3,h4,h5,bodyandcaptionacross seven tiles, plussimple-text(no variant) andtext-alignment(three alignments, which now paint). Two published values,h6andoverline, are demonstrated by no catalog entry — closing that would mean inventing two new entries rather than re-authoring one of the five, which is outside this card's fence. Both are covered by the renderer pin, and they are the obvious content for a follow-up if the seat wants full catalog coverage.Repairs beyond the five, each forced by the change and named here
examples/schema-catalog/src/schemas/components-feedback-loading/with-text.json— a sixth node in the same shadcn scale (variant: "muted"), sitting outside the category anyone was counting. Same defect class, one published value available for a spinner's small de-emphasised label (caption), no other claim on the file, same test suite. Repaired with the five and pinned by a corpus-wide sweep, so a seventh cannot arrive unnoticed.content/docs/components/basic/text.mdx— forced twice over: the page named the deleted and renamed ids (getExamplethrows on an unknown id, so a stale id is a build failure, not a cosmetic one), and its schema block taught a fifth vocabulary —h1-h4,p,lead,large,small,muted— that the validator refuses. Now the published nine.examples/schema-catalog/test/layout-dom-leak-5574.test.tsx—NODE_CENSUSfortextmoves702 -> 701rendered and176 -> 162no-element. Arithmetic, in the comment: one node fewer (muted.jsondeleted) and 13 nodes leaving the no-element class because a node that authorsvariantoralignnow needs an element to carry the class — the 7 variant-carryingcomponents-basic-textentries, the 3text-alignmentchildren, thewith-textlabel, and theh3headings inbasic-containerandbasic-stack. The census moves in the SAFE direction: fewer phantom-clean nodes, more elements actually read.examples/schema-catalog/test/catalog-gallery-render.test.tsx— a comment whose example ("components-basic-text/*render a bare text node … 'Heading 1' is a direct child of the wrapper") is now false. Re-pointed atsimple-text, which still takes that path. Comment only; the helper's logic is unchanged.Tests
New pins, both derive the vocabulary from the Zod mirror at runtime rather than restating it:
packages/components/src/__tests__/text-variant-align-6942.test.tsx(57 tests) — one rendered element per published value; the nine class strings asserted pairwise distinct; the heading elements;alignmapped for all four values and the four distinct; variant+align composition; authoredclassNamesurvives; the 53 in-repo schema files carry a registered ObjectUI type but failsafeValidateSchema#6318 triage table now discriminating (absent,body,h1are three different renderings, where the finding measured them byte-identical); and the not-C control — the five shadcn spellings are still refused bysafeValidateSchema, every published value validates, and an off-enum value is not quietly styled either.examples/schema-catalog/test/text-variant-corpus-6942.test.tsx(24 tests) — the corpus half: a sweep over everytextnode in all 432 catalog entries asserting each authoredvariant/alignis a published value (named, not counted, on failure), the per-entry re-authoring, everycomponents-basic-textentry validating, and the seven variant-carrying tiles rendering seven classes no sibling renders.Red-then-green ablation
Each mutation proved on disk before its run (anchored
grep -cFof both the removed and the injected text, plus a blob hash differing from HEAD's); each restore proved by state —git diff HEADempty andgit hash-objectequal to the HEAD blob — never by an exit code;trap ... EXIT INT TERMwith absolute paths throughout. No rebuild leg: vitest aliases@object-ui/componentstopackages/components/src(vitest.config.mts:415) and the component pin imports../renderersrelatively, so neither pin resolves throughdist.variantread replaced withundefined(the pre-card state)2a6bcd0evs HEAD1a66d9e7alignread replaced withundefined1eaeee82h5's class set equal toh4's6941a340git diff HEADempty, blob1a66d9e7= HEADThe union, on
56b3edd15The tree these ran on is byte-identical to the commit (
git status --porcelainempty; the ablation's green leg re-ran both pins after the commit).pnpm exec vitest run --maxWorkers=2 packages/components/— 226 files, 2129 tests passedpnpm exec vitest run --maxWorkers=2 examples/schema-catalog/— 20 files, 1945 tests passedturbo run lint --filter=@object-ui/components --filter=@object-ui/example-schema-catalog— 3 tasks successful, 0 errors (950 pre-existing warnings, none in a file this PR adds or edits)turbo run type-checkfor both packages — 31 tasks successful. Verified it actually covers the new files:tsc -p tsconfig.test.json --listFilesliststext-variant-align-6942.test.tsx,text-variant-corpus-6942.test.tsxand the editedrenderers/basic/text.tsx.check:control-bytes,check:doc-types,check:doc-fences,check:doc-snippets(after the build its own preflight names — its first run reported PRECONDITION NOT MET, which is not a verdict),regenerate-catalog-index.py --check(index up to date, 432 entries),check-changeset-presence/-no-major/-fixed/-overwrite— all green.pnpm check(the CLI's own corpus scan): the "carries a registered ObjectUI component type but did not validate" bucket now lists 23 files and nocomponents-basic-textorcomponents-feedback-loadingentry is among them. Thesafe-validate-corpus-6318docblock recorded 28 in that bucket when it was written; the five entries this PR re-authors are the difference.Changeset:
@object-ui/components, patch. Catalog entries and docs carry none.Handed on, not fixed here
colorkey three catalogtextnodes author — and the docs page teaches — is declared by nothing and read by nothing #7444 (filed by this seat) — the same catalog category authorscoloron three text nodes and the docs page teaches it, whileTextSchemadeclares no such key and nothing reads it. Different key, not published at all, so honouring it would be a vocabulary change rather than a renderer repair.packages/cli/src/__tests__/check-validity-recogniser.test.ts:157carries a comment describing its inline fixture as "copied verbatim from…/components-basic-text/small.json: real corpus content". The assertion is unaffected —variant: 'small'is still refused, deliberately — but that provenance is now historical rather than current. Left untouched: editing it would pullpackages/cliinto this PR's verification surface for a comment.Behaviour note, so it is not read as a regression
An external author who wrote
variant: 'h1'and saw nothing will now see a heading. That is the declared behaviour arriving, as the ruling states. A node that never authoredvariantoralignrenders exactly as it did before.🤖 Generated with Claude Code
https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Generated by Claude Code