@@ -7718,6 +7718,147 @@ const translationComponentSubmitLabelRemoved: MetadataConversion = {
77187718 },
77197719};
77207720
7721+ /**
7722+ * `element:text` `variant`'s two pre-convergence spellings become the heading
7723+ * levels they always rendered — `heading` → `h2`, `subheading` → `h3`
7724+ * (protocol 18, #21015: release 2 of objectui#7450's ruling B, maintainer
7725+ * 「其他同意」 2026-09-07, split across two releases 2026-09-09).
7726+ *
7727+ * Release 1 (#17108, 17.5.0) widened the enum to the nine values `ui:text`
7728+ * publishes — `h1`-`h6`, `body`, `caption`, `overline` — and refused nothing;
7729+ * 17.6.0 was the one full release in which both vocabularies parsed. This
7730+ * release refuses the two old spellings by name (`enumWithRetiredValues`,
7731+ * ui/component.zod.ts), and this entry carries the ruled migration hint as the
7732+ * mechanical edit.
7733+ *
7734+ * **The rewrite keeps the heading element, not the look.** Measured at the
7735+ * `.objectui-sha` pin `89cad75d55` (`renderers/basic/elements.tsx`), the
7736+ * `element:text` renderer drew `heading` as an h2 element and `subheading` as an
7737+ * h3 element, so the document outline a screen reader walks is unchanged. The
7738+ * size is not: `heading` drew `h3`'s style and `subheading` a medium-weight
7739+ * `text-lg`, while `h2` and `h3` draw their own, larger ones. The level IS the
7740+ * ruled meaning ("or pick the level you mean"), so the edit follows the
7741+ * element; the D3 entry `element-text-variant-heading-subheading-retired`
7742+ * carries the judgement the chain cannot make — whether this page wanted that
7743+ * level, or another.
7744+ *
7745+ * **One reach: every page component of type `element:text`**, through
7746+ * {@link mapPageComponents} — regions, named slots and container nesting, the
7747+ * positions the component-props gate judges. `variant` on any other component
7748+ * type is that component's own vocabulary and is never read here.
7749+ *
7750+ * `retiredFromLoadPath`: an author is refused at parse with the prescription
7751+ * rather than silently rewritten; stored rows replay it at rehydration
7752+ * (`applyConversionsToStoredItem`) and `os migrate meta` lists the edit for
7753+ * existing sources. Idempotent by construction: the rewrite's output is
7754+ * outside its own input set.
7755+ */
7756+ const elementTextVariantHeadingLevels: MetadataConversion = {
7757+ id: 'element-text-variant-heading-levels',
7758+ toMajor: 18,
7759+ retiredFromLoadPath: true,
7760+ retiredAfter: '17.6.0',
7761+ surface: 'page.component.element:text.variant',
7762+ summary:
7763+ "element:text 'variant' spellings 'heading' → 'h2' and 'subheading' → 'h3' (the vocabulary converged "
7764+ + "on the nine values ui:text publishes; each old spelling already rendered that heading element, "
7765+ + 'so the outline is unchanged and the heading takes that level\'s style)',
7766+ apply(stack, emit) {
7767+ const VARIANT_REWRITE: Readonly<Record<string, string>> = { heading: 'h2', subheading: 'h3' };
7768+ return mapPageComponents(stack, (component, path) => {
7769+ if (component.type !== 'element:text') return component;
7770+ const properties = component.properties;
7771+ if (!isDict(properties)) return component;
7772+ const variant = properties.variant;
7773+ if (typeof variant !== 'string' || !Object.prototype.hasOwnProperty.call(VARIANT_REWRITE, variant)) {
7774+ return component;
7775+ }
7776+ const to = VARIANT_REWRITE[variant]!;
7777+ emit({ from: variant, to, path: `${path}.properties.variant` });
7778+ return { ...component, properties: { ...properties, variant: to } };
7779+ });
7780+ },
7781+ fixture: {
7782+ before: {
7783+ pages: [
7784+ {
7785+ name: 'text_variant_levels',
7786+ regions: [
7787+ {
7788+ name: 'main',
7789+ components: [
7790+ { type: 'element:text', properties: { content: 'Overview', variant: 'heading', align: 'center' } },
7791+ { type: 'element:text', properties: { content: 'Details', variant: 'subheading' } },
7792+ // A published level and an absent `variant` ride through.
7793+ { type: 'element:text', properties: { content: 'Body copy', variant: 'h3' } },
7794+ { type: 'element:text', properties: { content: 'Default body' } },
7795+ // `variant` on another component type is that type's own
7796+ // vocabulary, untouched here.
7797+ { type: 'element:button', properties: { label: 'Go', variant: 'heading' } },
7798+ // Nested one container down.
7799+ {
7800+ type: 'page:card',
7801+ properties: {
7802+ title: 'Card',
7803+ children: [{ type: 'element:text', properties: { content: 'In a card', variant: 'subheading' } }],
7804+ },
7805+ },
7806+ ],
7807+ },
7808+ ],
7809+ },
7810+ // A slotted page's named slot — the same component, the other authoring shape.
7811+ {
7812+ name: 'text_variant_levels_slotted',
7813+ kind: 'slotted',
7814+ regions: [],
7815+ slots: {
7816+ details: [{ type: 'element:text', properties: { content: 'Title', variant: 'heading' } }],
7817+ },
7818+ },
7819+ ],
7820+ },
7821+ after: {
7822+ pages: [
7823+ {
7824+ name: 'text_variant_levels',
7825+ regions: [
7826+ {
7827+ name: 'main',
7828+ components: [
7829+ { type: 'element:text', properties: { content: 'Overview', variant: 'h2', align: 'center' } },
7830+ { type: 'element:text', properties: { content: 'Details', variant: 'h3' } },
7831+ { type: 'element:text', properties: { content: 'Body copy', variant: 'h3' } },
7832+ { type: 'element:text', properties: { content: 'Default body' } },
7833+ { type: 'element:button', properties: { label: 'Go', variant: 'heading' } },
7834+ {
7835+ type: 'page:card',
7836+ properties: {
7837+ title: 'Card',
7838+ children: [{ type: 'element:text', properties: { content: 'In a card', variant: 'h3' } }],
7839+ },
7840+ },
7841+ ],
7842+ },
7843+ ],
7844+ },
7845+ {
7846+ name: 'text_variant_levels_slotted',
7847+ kind: 'slotted',
7848+ regions: [],
7849+ slots: {
7850+ details: [{ type: 'element:text', properties: { content: 'Title', variant: 'h2' } }],
7851+ },
7852+ },
7853+ ],
7854+ },
7855+ // One per rewritten `variant`: the region pair, the nested card child and
7856+ // the slotted one — the published level, the absent key and the
7857+ // other component type are untouched.
7858+ expectedNotices: 4,
7859+ },
7860+ };
7861+
77217862/**
77227863 * The inline grid column's one mechanical respelling, shared by both of its
77237864 * carriers — a relationship field's `inlineColumns`
@@ -14439,6 +14580,7 @@ const MAJOR_18_CONVERSIONS: readonly OrderedConversion[] = [
1443914580 { conversion: elementFilterRemoved, order: 4 },
1444014581 { conversion: elementFormRemoved, order: 5 },
1444114582 { conversion: elementInputTargetVariableRemoved, order: 3 },
14583+ { conversion: elementTextVariantHeadingLevels, order: 59 },
1444214584 { conversion: fieldColumnListsCanonicalized, order: 6 },
1444314585 { conversion: fieldMalformedScalePrecisionRemoved, order: 1 },
1444414586 { conversion: fieldReferenceToAlias, order: 18 },
0 commit comments