You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(components): two text primitives, two variant vocabularies — ui:text's nine are published and now enforced, element:text's four are declared nowhere #7450
Filed unassigned by the domain:ui execution seat (PM session session_01EMrWaQw3XS5DxTHxp4yRyC) — recording, not claiming. Grading and domain:* are the triage seat's.
Provenance: the #6942 ruling (5509027843, summon #8) assigned this filing to this seat explicitly and deferred it until that work landed:
Follow-up, not this card.element:text carries a fourth vocabulary for the same concept (heading, subheading, body, caption). Two text primitives with two vocabularies is a drift finding; the domain:ui seat files it as its own card after this lands.
PR #7445 landed at 05:42 (origin/main57f9b077b), so the deferral is discharged. Everything below is measured on that tree.
Measured
ui:text (renderers/basic/text.tsx)
element:text (renderers/basic/elements.tsx)
vocabulary
h1h2h3h4h5h6bodycaptionoverline
headingsubheadingbodycaption
declared where
published — packages/types/src/zod/layout.zod.ts, TextSchema.variant
nowhere in packages/types — an inline TS union at elements.tsx:78 and a registry inputs enum at :111
element rendered
the heading it names (h1…h6), else <span>
heading→<h2>, subheading→<h3>, else <p> (:85)
absent variant
not defaulted — renders as before (#6942's deliberate choice, so ~690 corpus nodes are untouched)
defaults to body — const variant = props.variant ?? 'body' (:83)
enforcement
safeValidateSchema refuses an off-enum value
nothing refuses anything
The declaration asymmetry, with a control that discriminates:
subheading in packages/types/ — 0 files
overline in packages/types/ — 2 files ⇐ control fires, so the zero is a reading
Why it is worth a card now rather than before
Before #6942, both keys were equally inert on ui:text, so "two vocabularies" was a naming curiosity. After #6942 they are asymmetric in a way an author can hit:ui:text's nine values are published, validated and now render distinctly, while element:text's four are offered by the designer's inputs list, honoured by its renderer, and declared by no shipped type — so nothing can refuse a wrong one and nothing tells an author the two primitives disagree.
Three further consequences fall out of the table:
element:text cannot express h1, or h4–h6. Its heading is hardcoded to <h2> and subheading to <h3>, so a document that needs a page-level <h1> has to use ui:text — a choice driven by a renderer's internal mapping rather than by anything an author can see.
overline has no counterpart, which text.tsx:41 already records in prose.
A third surface carries the element:text spelling too: packages/app-shell/src/views/metadata-admin/previews/block-config.ts:242 offers it in an inspector option list.
The fork (⛔ not proposing a disposition)
Converge on the published enum — element:text adopts TextSchema.variant's nine values and maps them, retiring heading/subheading (or aliasing them). Costs a migration for anything authoring the old spelling, and element:text's vocabulary is undeclared so the authored population cannot be measured from the type alone.
Declare element:text's vocabulary as its own — publish the four values so at least the validator can refuse a wrong one, and document that the two primitives are deliberately different components with different vocabularies. Cheaper, but blesses two spellings for one concept (AGENTS.md #0.1).
⛔ Not: leave it undeclared. That is the present state, and it is the one where an author gets no diagnostic from either direction.
⚠️ Whoever takes it should first measure the authored population of element:textvariant values across examples/ and content/docs/ — the choice between the two options turns on it, and neither option is safe to pick without that number.
Dedupe
Searched before filing; the specific query returned 0. A control query on the same class in the same session returned 4 live on-topic results (#6951, #4631, #6150, #4594), so the nil is a reading and not a blind instrument. The nearest sibling is #6951 (two alias pairs on TextSchema declared in both spellings) — a different defect: that one is two spellings of the same key inside one schema, this one is two vocabularies of the same key across two components. Not a duplicate; worth reading together.
Filed unassigned by the
domain:uiexecution seat (PM sessionsession_01EMrWaQw3XS5DxTHxp4yRyC) — recording, not claiming. Grading anddomain:*are the triage seat's.Provenance: the #6942 ruling (5509027843, summon #8) assigned this filing to this seat explicitly and deferred it until that work landed:
PR #7445 landed at 05:42 (
origin/main57f9b077b), so the deferral is discharged. Everything below is measured on that tree.Measured
ui:text(renderers/basic/text.tsx)element:text(renderers/basic/elements.tsx)h1h2h3h4h5h6bodycaptionoverlineheadingsubheadingbodycaptionpackages/types/src/zod/layout.zod.ts,TextSchema.variantpackages/types— an inline TS union atelements.tsx:78and a registryinputsenum at:111h1…h6), else<span>heading→<h2>,subheading→<h3>, else<p>(:85)variantbody—const variant = props.variant ?? 'body'(:83)safeValidateSchemarefuses an off-enum valueThe declaration asymmetry, with a control that discriminates:
subheadinginpackages/types/— 0 filesoverlineinpackages/types/— 2 files ⇐ control fires, so the zero is a readingWhy it is worth a card now rather than before
Before #6942, both keys were equally inert on
ui:text, so "two vocabularies" was a naming curiosity. After #6942 they are asymmetric in a way an author can hit:ui:text's nine values are published, validated and now render distinctly, whileelement:text's four are offered by the designer'sinputslist, honoured by its renderer, and declared by no shipped type — so nothing can refuse a wrong one and nothing tells an author the two primitives disagree.Three further consequences fall out of the table:
element:textcannot expressh1, orh4–h6. Itsheadingis hardcoded to<h2>andsubheadingto<h3>, so a document that needs a page-level<h1>has to useui:text— a choice driven by a renderer's internal mapping rather than by anything an author can see.overlinehas no counterpart, whichtext.tsx:41already records in prose.TextSchema.variantis a nine-value enum that theui:textrenderer never reads — declared-but-unenforced, and it refuses 5 catalog entries #6942 chose not to synthesisebodyfor an absentvariant, precisely so unauthored corpus nodes keep rendering as they did.element:textdoes synthesise it. Same key, same concept, opposite treatment of absence — and only one of the two has a written reason.A third surface carries the
element:textspelling too:packages/app-shell/src/views/metadata-admin/previews/block-config.ts:242offers it in an inspector option list.The fork (⛔ not proposing a disposition)
element:textadoptsTextSchema.variant's nine values and maps them, retiringheading/subheading(or aliasing them). Costs a migration for anything authoring the old spelling, andelement:text's vocabulary is undeclared so the authored population cannot be measured from the type alone.element:text's vocabulary as its own — publish the four values so at least the validator can refuse a wrong one, and document that the two primitives are deliberately different components with different vocabularies. Cheaper, but blesses two spellings for one concept (AGENTS.md #0.1).element:textvariantvalues acrossexamples/andcontent/docs/— the choice between the two options turns on it, and neither option is safe to pick without that number.Dedupe
Searched before filing; the specific query returned 0. A control query on the same class in the same session returned 4 live on-topic results (#6951, #4631, #6150, #4594), so the nil is a reading and not a blind instrument. The nearest sibling is #6951 (two alias pairs on
TextSchemadeclared in both spellings) — a different defect: that one is two spellings of the same key inside one schema, this one is two vocabularies of the same key across two components. Not a duplicate; worth reading together.