From d96367213107f26428de8e95bb17e92cab6de752 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 00:18:41 +0000 Subject: [PATCH] feat(types)!: refuse both content channels on six of the nine bare-name hold-outs (objectui#9256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `text`, `image`, `icon`, `tabs`, `accordion` and `calendar` read NEITHER content channel, so `body` and `children` become `?: never` on the TypeScript face and a by-name `retirementTombstone` refusal on the zod mirror — kept a MEMBER so `zod-mirror-parity`'s key sets stay equal. These were held out of the earlier family-D slice because `ComponentRegistry.register` writes the bare-name fallback last-one-wins and which declaration governed a bare node was unmeasured. `check:registry-bare-names` measures it now, and every row was re-derived at this branch point rather than inherited. `button` was in the dispatched set and is deliberately NOT narrowed: its measured owner `ui:button` reads `schema.label || renderChildren(schema.body || schema.children)` — a LIVE fallback, which makes it family C and a behaviour change rather than a declaration repair. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa --- .changeset/nine-holdouts-six-narrow.md | 28 ++ content/docs/api/schema-reference.md | 8 +- .../content-channel-family-d-9256.test.ts | 126 ++++++++- packages/types/src/disclosure.ts | 66 +++++ packages/types/src/form.ts | 110 ++++++++ packages/types/src/layout.ts | 264 ++++++++++++++++++ packages/types/src/zod/disclosure.zod.ts | 20 ++ packages/types/src/zod/form.zod.ts | 28 ++ packages/types/src/zod/layout.zod.ts | 80 ++++++ 9 files changed, 719 insertions(+), 11 deletions(-) create mode 100644 .changeset/nine-holdouts-six-narrow.md diff --git a/.changeset/nine-holdouts-six-narrow.md b/.changeset/nine-holdouts-six-narrow.md new file mode 100644 index 0000000000..146be55aff --- /dev/null +++ b/.changeset/nine-holdouts-six-narrow.md @@ -0,0 +1,28 @@ +--- +'@object-ui/types': minor +--- + +**BREAKING (shipped as `minor` — see below):** six component schemas now refuse +both content channels by name. `text`, `image`, `icon`, `tabs`, `accordion` and +`calendar` (and `ui:calendar`, which inherits the narrowing by declaration) read +NEITHER `body` nor `children`, so both keys become `?: never` on the TypeScript +face and a by-name `retirementTombstone` refusal on the zod mirror — kept a +MEMBER of the mirror shape, so the parity ratchet's key sets stay equal. + +A key that parsed GREEN through `BaseSchema.passthrough()` and rendered NOTHING +is now refused at its own path, at authoring time and at `safeParse` time. No +render behaviour changes: nothing read these keys, which is the whole reason +they could be refused. + +These six were HELD OUT of the earlier family-D slice because +`ComponentRegistry.register` writes the bare-name fallback last-one-wins and +which declaration governed a bare node was unmeasured. It is measured now — +`check:registry-bare-names` reports one owner per name — so each narrowing rests +on a measured owner rather than on import order. `button` was in the dispatched +set and is deliberately NOT here: its measured owner reads +`schema.body || schema.children` live, which makes it family C and a behaviour +change, not a declaration repair. + +`minor` rather than `major` because this repo's version policy forbids `major` +in any changeset — one `fixed` group — and records `minor` plus an explicit +breaking note as the spelling for a breaking change here. diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md index de0e6c95bf..3379a61a2b 100644 --- a/content/docs/api/schema-reference.md +++ b/content/docs/api/schema-reference.md @@ -118,7 +118,7 @@ Top-level page container. Defines a full page with optional regions (header, sid "regions": [ { "name": "header", - "body": [{ "type": "text", "body": "Welcome back" }] + "body": [{ "type": "text", "content": "Welcome back" }] } ], "body": [ @@ -153,7 +153,7 @@ A generic container element. The simplest layout primitive. "type": "div", "className": "flex items-center gap-4 p-6", "children": [ - { "type": "text", "body": "Hello World" }, + { "type": "text", "content": "Hello World" }, { "type": "button", "label": "Click me" } ] } @@ -246,7 +246,7 @@ A tabbed interface for organizing content into switchable panels. "value": "overview", "label": "Overview", "icon": "Info", - "content": { "type": "div", "body": [{ "type": "text", "body": "Overview content" }] } + "content": { "type": "div", "body": [{ "type": "text", "content": "Overview content" }] } }, { "value": "settings", @@ -443,7 +443,7 @@ did; authoring them is now refused by validation instead of silently ignored). { "id": 1, "customer": "Acme Corp", "amount": "$1,200", "status": "Paid" }, { "id": 2, "customer": "Globex Inc", "amount": "$3,400", "status": "Pending" } ], - "footer": { "type": "text", "body": "Showing 2 of 156 orders" } + "footer": { "type": "text", "content": "Showing 2 of 156 orders" } } ``` diff --git a/packages/types/src/__tests__/content-channel-family-d-9256.test.ts b/packages/types/src/__tests__/content-channel-family-d-9256.test.ts index 67f0a78592..711afbffc2 100644 --- a/packages/types/src/__tests__/content-channel-family-d-9256.test.ts +++ b/packages/types/src/__tests__/content-channel-family-d-9256.test.ts @@ -38,11 +38,32 @@ * * ## What is held OUT of this file, and why — each is a measurement, not appetite * - * - the nine `type` names carrying 2+ registrations with different - * declarations (`accordion`, `tabs`, `text`, `image`, `icon`, `list`, - * `calendar`, `timeline`, and the `UIActionSchema` arm of `button`): + * - ⭐ SIX of the nine bare-name hold-outs are narrowed HERE, by objectui#9256 + * slice 2, and the reason is a measurement rather than a ruling. * `ComponentRegistry.register` writes a bare-name fallback LAST-ONE-WINS, so - * which declaration governs an authored node depends on import order. + * which declaration governs an authored node was unmeasured — that is what + * held them. `check:registry-bare-names` (objectui#9264, the gate; run it + * with `--table` for the population) now measures it, and `text`, `image`, + * `icon`, `tabs` and `accordion` each have ONE claimant of the bare key + * because every rival passes `skipFallback: true`, while `calendar`'s + * several claimants name ONE full type. `ui:calendar` rides along on + * `calendar`'s tombstone by declaration (`Omit` on the TS face, `.extend()` + * on the mirror) and is pinned as its own row so the propagation is + * measured and not assumed. + * - `list` and `timeline` stay out: both declare in `data-display.ts`, which + * objectui#7804 slice 1 is editing on another branch. A serial constraint, + * not a verdict. + * - ⛔ `button` stays out, and the reason CHANGED under re-derivation. + * `check:registry-bare-names` reports `ui:button` as the sole owner of the + * bare key — but that owner's renderer reads + * `schema.label || renderChildren(schema.body || schema.children)`, a LIVE + * `body || children` fallback. `button` is therefore family C, not family D: + * removing a live read is a behaviour change, and AGENTS.md #0.1 says the + * fallback itself is the defect. That pulls the other way, and it is the + * maintainer's call on objectui#8284. The `UIActionSchema` arm has nothing + * to tombstone at all — it does not extend `BaseSchema` and carries only + * `bodyExtra` / `bodyShape`, the prefix keys grep confuses for the channel. + * A LIVE CONTROL below keeps `button` parsing both channels. * - `InputSchema`: `InputShorthandSchema` is declared as an `Omit` of it, so a * tombstone here would propagate onto the `email` / `password` shorthand * face, whose registrations are `any`-typed — family E, frozen. @@ -70,6 +91,10 @@ import type { z } from 'zod'; import { SeparatorSchema as SeparatorMirror, ResizableSchema as ResizableMirror, + TextSchema as TextMirror, + ImageSchema as ImageMirror, + IconSchema as IconMirror, + TabsSchema as TabsMirror, } from '../zod/layout.zod'; import { HtmlSchema as HtmlMirror, @@ -100,6 +125,9 @@ import { FileUploadSchema as FileUploadMirror, ComboboxSchema as ComboboxMirror, CommandSchema as CommandMirror, + CalendarSchema as CalendarMirror, + UiCalendarSchema as UiCalendarMirror, + ButtonSchema as ButtonFamilyCControl, } from '../zod/form.zod'; import { ProgressSchema as ProgressMirror, @@ -125,6 +153,7 @@ import { import { CollapsibleSchema as CollapsibleMirror, ToggleGroupSchema as ToggleGroupMirror, + AccordionSchema as AccordionMirror, } from '../zod/disclosure.zod'; import { CarouselSchema as CarouselMirror, @@ -161,6 +190,9 @@ import type { CarouselSchema, ChatbotSchema } from '../complex'; import type { ReportViewerSchema } from '../reports'; import type { ViewSwitcherSchema } from '../views'; import type { NLQuerySchema } from '../ai'; +import type { TextSchema, ImageSchema, IconSchema, TabsSchema } from '../layout'; +import type { AccordionSchema } from '../disclosure'; +import type { CalendarSchema, UiCalendarSchema } from '../form'; type Mirror = { safeParse: (v: unknown) => { success: boolean; error?: z.ZodError }; @@ -236,6 +268,20 @@ const ROWS: ReadonlyArray describe('objectui#9256 — family D refuses the content channels its renderers never read', () => { it('the population is the measured one — a row dropped from the table fails here', () => { - expect(ROWS).toHaveLength(58); + expect(ROWS).toHaveLength(65); // 3 fewer than 2x: the chatbot faces carry `children` only. - expect(CASES).toHaveLength(58 * 2 - 3); + expect(CASES).toHaveLength(65 * 2 - 3); }); it.each(CASES)('%s is refused at that key\'s own path', (_label, mirror, key, required) => { @@ -306,6 +352,30 @@ describe('objectui#9256 — CONTROLS: the node itself, and the held-out channel, expect(r.success).toBe(true); }); + it('LIVE CONTROL — `button` still accepts BOTH channels: it is family C, not family D', () => { + // Re-derived at this branch point, and it contradicts the table this slice + // was dispatched from: `ui:button` owns the bare `button` key, and that + // renderer reads `schema.label || renderChildren(schema.body || schema.children)` + // — a LIVE fallback. Narrowing it would REMOVE A LIVE READ, which is a + // behaviour change and the maintainer's call (objectui#8284), so `button` + // is untouched and this line is what keeps the rows above a reading about + // the six that moved rather than about the whole mirror. + const mirror = ButtonFamilyCControl as unknown as Mirror; + expect(issues(mirror, { type: 'button', body: CONTENT })).toBeNull(); + expect(issues(mirror, { type: 'button', children: CONTENT })).toBeNull(); + }); + + it('LIVE CONTROL — the ITEM-level channel is untouched: a `tabs` item still parses `content`', () => { + // `tabs.tsx` renders `item.content || (item as any).body` — an ITEM key, + // filed under `TabItem` and not under `TabsSchema`. An instrument that + // attributed that read to the NODE would have made `tabs` a body reader and + // this whole row a mistake, so the item channel is pinned as still live. + expect(issues(TabsMirror as unknown as Mirror, { + type: 'tabs', + items: [{ value: 't1', label: 'One', content: CONTENT }], + })).toBeNull(); + }); + it('LIVE CONTROL — family C is NOT narrowed: a `div` still accepts both channels', () => { // `div` reads `children || body` (a live fallback). Removing a live read is // a behaviour change and is with the maintainer, so this line must stay @@ -376,12 +446,54 @@ describe('objectui#9256 — the TypeScript face refuses both channels at the AUT const pivotBody: PivotTableSchema = { type: 'pivot', body: CONTENT }; // @ts-expect-error objectui#9256 — `chatbot` reads neither channel; `children` is narrowed, `body` is held out const chatbotChildren: ChatbotSchema = { type: 'chatbot', messages: [], children: CONTENT }; + // ── slice 2: the six bare-name hold-outs, one NAMED pair each ───────── + // @ts-expect-error objectui#9256 — `text` reads `content`, never a content channel + const textBody: TextSchema = { type: 'text', body: CONTENT }; + // @ts-expect-error objectui#9256 — `text` reads `content`, never a content channel + const textChildren: TextSchema = { type: 'text', children: CONTENT }; + // @ts-expect-error objectui#9256 — `image` reads `src` / `alt` + const imageBody: ImageSchema = { type: 'image', src: '/a.png', body: CONTENT }; + // @ts-expect-error objectui#9256 — `image` reads `src` / `alt` + const imageChildren: ImageSchema = { type: 'image', src: '/a.png', children: CONTENT }; + // @ts-expect-error objectui#9256 — `icon` reads `icon` / `size` / `color` + const iconBody: IconSchema = { type: 'icon', icon: 'check', body: CONTENT }; + // @ts-expect-error objectui#9256 — `icon` reads `icon` / `size` / `color` + const iconChildren: IconSchema = { type: 'icon', icon: 'check', children: CONTENT }; + // @ts-expect-error objectui#9256 — `tabs` renders `items[].content`, never the node's own channel + const tabsBody: TabsSchema = { type: 'tabs', items: [], body: CONTENT }; + // @ts-expect-error objectui#9256 — `tabs` renders `items[].content`, never the node's own channel + const tabsChildren: TabsSchema = { type: 'tabs', items: [], children: CONTENT }; + // @ts-expect-error objectui#9256 — `accordion` renders `items[].content`, never the node's own channel + const accordionBody: AccordionSchema = { type: 'accordion', items: [], body: CONTENT }; + // @ts-expect-error objectui#9256 — `accordion` renders `items[].content`, never the node's own channel + const accordionChildren: AccordionSchema = { type: 'accordion', items: [], children: CONTENT }; + // @ts-expect-error objectui#9256 — `calendar` reads neither channel on either of its two readers + const calendarBody: CalendarSchema = { type: 'calendar', body: CONTENT }; + // @ts-expect-error objectui#9256 — `calendar` reads neither channel on either of its two readers + const calendarChildren: CalendarSchema = { type: 'calendar', children: CONTENT }; + // ⚠️ TRIPWIRE, and deliberately NOT a `@ts-expect-error`. `UiCalendarSchema` + // is declared as `Omit`, and `BaseSchema` carries an + // index signature — so `Omit` resolves through `Exclude` = + // `string` and collapses every member into that signature. MEASURED with the + // checker: `UiCalendarSchema['body']` answers `any`, and so does + // `UiCalendarSchema['mode']`, which is a key this change never touched. That + // face therefore carries NONE of `CalendarSchema`'s members, tombstones + // included, and the line below compiles today. Its MIRROR does refuse the + // key — the `ui:calendar` row above is the reading — so this is a measured + // asymmetry between the two published faces, not a gap in the mirror. + // When the collapse is repaired this line goes RED, which is the signal to + // restate the two tombstones on `UiCalendarSchema` and turn it back into a + // `@ts-expect-error` pair. + const uiCalendarBody: UiCalendarSchema = { type: 'ui:calendar', body: CONTENT }; expect([ separatorBody, separatorChildren, kbdBody, skeletonChildren, checkboxBody, paginationChildren, dialogChildren, collapsibleChildren, carouselBody, galleryBody, viewSwitcherChildren, reportBody, nlQueryChildren, pivotBody, chatbotChildren, - ]).toHaveLength(15); + textBody, textChildren, imageBody, imageChildren, iconBody, iconChildren, + tabsBody, tabsChildren, accordionBody, accordionChildren, calendarBody, calendarChildren, + uiCalendarBody, + ]).toHaveLength(28); }); it('CONTROL — the same nodes WITHOUT a content channel compile (no `@ts-expect-error` here, and `tsc` is the reader)', () => { diff --git a/packages/types/src/disclosure.ts b/packages/types/src/disclosure.ts index 3813974abe..73eacc1630 100644 --- a/packages/types/src/disclosure.ts +++ b/packages/types/src/disclosure.ts @@ -84,6 +84,72 @@ export interface AccordionSchema extends BaseSchema { * @default 'default' */ variant?: 'default' | 'bordered' | 'separated'; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `accordion` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `accordionType`, `collapsible`, `items` (in `packages/components/src/renderers/disclosure/accordion.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "accordion" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:accordion` + * as the SOLE claimant of the bare key, because `page:accordion` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `accordion` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `accordion` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `accordionType`, `collapsible`, `items` (in `packages/components/src/renderers/disclosure/accordion.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "accordion" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:accordion` + * as the SOLE claimant of the bare key, because `page:accordion` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `accordion` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 066a2f172a..110478cc31 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -1167,6 +1167,116 @@ export interface CalendarSchema extends BaseSchema { * @deprecated Not part of this contract — the value was inert. */ onChange?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `calendar` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `defaultValue`, `mode`, `value` (in `packages/components/src/renderers/form/calendar.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "calendar" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `view:calendar` + * as the SOLE claimant of the bare key, because `ui:calendar` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * ⚠️ TWO readers, and both answer NEITHER. The bare `calendar` key resolves to + * plugin-calendar's full CRUD calendar VIEW (`view:calendar`, the renderer in + * `packages/plugin-calendar/src/index.tsx`), which reads its declared view + * surface — `objectName`, `calendar`, `filter`, `sort`, `defaultView`, + * `locale`, `data`, `staticData`, `loading` — and neither content channel. + * This declaration's own key set describes the date-picker primitive reached + * at `ui:calendar` only (`packages/components/src/renderers/form/calendar.tsx`), + * which reads `defaultValue`, `mode` and `value` and neither content channel. + * + * ⚠️ The tombstone does NOT reach {@link UiCalendarSchema} on THIS face, and + * that was measured rather than assumed. `UiCalendarSchema` is declared as an + * `Omit` of this interface, and {@link BaseSchema} carries an index signature, + * so `Omit` resolves through `Exclude` = `string` and collapses + * every member into that signature: the checker answers `any` for + * `UiCalendarSchema['body']` — and for `UiCalendarSchema['mode']` as well, so + * this is not about the tombstone. That face has never carried this key set, + * whatever its own docblock says. The MIRROR does propagate, because + * `zod/form.zod.ts` builds it with `.extend()` and zod carries the shape. + * Both halves are pinned in `__tests__/content-channel-family-d-9256.test.ts` + * — the mirror as a refusal row, this face as a tripwire that reddens the day + * the collapse is repaired. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `calendar` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `calendar` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `defaultValue`, `mode`, `value` (in `packages/components/src/renderers/form/calendar.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "calendar" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `view:calendar` + * as the SOLE claimant of the bare key, because `ui:calendar` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * ⚠️ TWO readers, and both answer NEITHER. The bare `calendar` key resolves to + * plugin-calendar's full CRUD calendar VIEW (`view:calendar`, the renderer in + * `packages/plugin-calendar/src/index.tsx`), which reads its declared view + * surface — `objectName`, `calendar`, `filter`, `sort`, `defaultView`, + * `locale`, `data`, `staticData`, `loading` — and neither content channel. + * This declaration's own key set describes the date-picker primitive reached + * at `ui:calendar` only (`packages/components/src/renderers/form/calendar.tsx`), + * which reads `defaultValue`, `mode` and `value` and neither content channel. + * + * ⚠️ The tombstone does NOT reach {@link UiCalendarSchema} on THIS face, and + * that was measured rather than assumed. `UiCalendarSchema` is declared as an + * `Omit` of this interface, and {@link BaseSchema} carries an index signature, + * so `Omit` resolves through `Exclude` = `string` and collapses + * every member into that signature: the checker answers `any` for + * `UiCalendarSchema['body']` — and for `UiCalendarSchema['mode']` as well, so + * this is not about the tombstone. That face has never carried this key set, + * whatever its own docblock says. The MIRROR does propagate, because + * `zod/form.zod.ts` builds it with `.extend()` and zod carries the shape. + * Both halves are pinned in `__tests__/content-channel-family-d-9256.test.ts` + * — the mirror as a refusal row, this face as a tripwire that reddens the day + * the collapse is repaired. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `calendar` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/layout.ts b/packages/types/src/layout.ts index 6742e36630..01496bc198 100644 --- a/packages/types/src/layout.ts +++ b/packages/types/src/layout.ts @@ -170,6 +170,72 @@ export interface TextSchema extends BaseSchema { * Text alignment */ align?: 'left' | 'center' | 'right' | 'justify'; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `text` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `align`, `className`, `content`, `variant` (in `packages/components/src/renderers/basic/text.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "text" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:text` + * as the SOLE claimant of the bare key, because `element:text` and `field:text` pass + * `skipFallback: true` and never write it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `text` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `text` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `align`, `className`, `content`, `variant` (in `packages/components/src/renderers/basic/text.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "text" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:text` + * as the SOLE claimant of the bare key, because `element:text` and `field:text` pass + * `skipFallback: true` and never write it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `text` reads — nothing renders it. + */ + children?: never; } /** @@ -197,6 +263,72 @@ export interface ImageSchema extends BaseSchema { * Object fit property */ objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `image` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `alt`, `src` (in `packages/components/src/renderers/basic/image.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "image" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:image` + * as the SOLE claimant of the bare key, because `element:image` and `field:image` pass + * `skipFallback: true` and never write it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `image` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `image` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `alt`, `src` (in `packages/components/src/renderers/basic/image.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "image" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:image` + * as the SOLE claimant of the bare key, because `element:image` and `field:image` pass + * `skipFallback: true` and never write it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `image` reads — nothing renders it. + */ + children?: never; } /** @@ -248,6 +380,72 @@ export interface IconSchema extends BaseSchema { * Icon color */ color?: string; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `icon` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `className`, `color`, `icon`, `name`, `size` (in `packages/components/src/renderers/basic/icon.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "icon" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:icon` + * as the SOLE claimant of the bare key, because `action:icon` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `icon` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `icon` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `className`, `color`, `icon`, `name`, `size` (in `packages/components/src/renderers/basic/icon.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "icon" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:icon` + * as the SOLE claimant of the bare key, because `action:icon` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `icon` reads — nothing renders it. + */ + children?: never; } /** @@ -674,6 +872,72 @@ export interface TabsSchema extends BaseSchema { * its own `onValueChange`. */ onValueChange?: (value: string) => void; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `tabs` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `defaultValue`, `items`, `orientation`, `value` (in `packages/components/src/renderers/layout/tabs.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "tabs" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:tabs` + * as the SOLE claimant of the bare key, because `page:tabs` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `tabs` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `tabs` reads NEITHER content + * channel: no renderer read consumes `body` or `children` for this node. + * + * MEASURED with the TypeScript TYPE CHECKER and not with grep, over one + * program per workspace package plus the apps and the examples, on a BUILT + * tree reporting zero unresolved-module diagnostics — an unbuilt tree answers + * `any`, and `any` reads as NEITHER. A docblock mention is not an AST node, + * and `schema.bodyExtra` in + * `packages/components/src/renderers/action/action-button.tsx` is the kind of + * prefix hit grep scores. Every read is filed under the TYPE of the object it + * is read from; this declaration carries none. What the renderer DOES read + * off this node: `defaultValue`, `items`, `orientation`, `value` (in `packages/components/src/renderers/layout/tabs.tsx`). + * + * ⭐ WHY THIS NAME WAS HELD OUT OF objectui#9261, AND WHAT CHANGED. + * `ComponentRegistry.register` writes the bare-name fallback last-one-wins, + * so which declaration governs an authored `{ "type": "tabs" }` node was + * unmeasured and narrowing it would have been a guess. It is measured now: + * `pnpm check:registry-bare-names --table` (objectui#9264) reports `ui:tabs` + * as the SOLE claimant of the bare key, because `page:tabs` passes + * `skipFallback: true` and never writes it. Re-derive from that instrument + * rather than from this sentence. + * + * `body` and `children` are inherited-and-optional from {@link BaseSchema}, + * whose own docblock admits "some components use `children` instead of + * `body`" without saying which — so authoring either here type-checked, + * parsed green through `.passthrough()`, and rendered NOTHING: no error, no + * warning, no element. `SchemaRenderer` strips both keys out of the props bag + * it spreads, so neither reaches the component by another route either. + * + * @deprecated Not a channel `tabs` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/zod/disclosure.zod.ts b/packages/types/src/zod/disclosure.zod.ts index c2d3452f44..01835d39ae 100644 --- a/packages/types/src/zod/disclosure.zod.ts +++ b/packages/types/src/zod/disclosure.zod.ts @@ -42,6 +42,26 @@ export const AccordionSchema = BaseSchema.extend({ value: z.union([z.string(), z.array(z.string())]).optional().describe('Controlled open item(s)'), onValueChange: handlerKeyRefusal('onValueChange', 'runtime-slot', 'Value change handler'), variant: z.enum(['default', 'bordered', 'separated']).optional().describe('Accordion variant'), + body: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `accordion` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `accordionType`, `collapsible`, `items`. ' + + '`ui:accordion` is the measured SOLE owner of the bare `accordion` key (`page:accordion` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), + children: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `accordion` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `accordionType`, `collapsible`, `items`. ' + + '`ui:accordion` is the measured SOLE owner of the bare `accordion` key (`page:accordion` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), }); /** diff --git a/packages/types/src/zod/form.zod.ts b/packages/types/src/zod/form.zod.ts index 0846f9118a..94ce691020 100644 --- a/packages/types/src/zod/form.zod.ts +++ b/packages/types/src/zod/form.zod.ts @@ -532,6 +532,34 @@ export const CalendarSchema = BaseSchema.extend({ minDate: z.union([z.string(), z.date()]).optional().describe('Minimum date'), maxDate: z.union([z.string(), z.date()]).optional().describe('Maximum date'), onChange: handlerKeyRefusal('onChange', 'retired', 'Change handler'), + body: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `calendar` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `defaultValue`, `mode`, `value`. ' + + 'Both readers answer NEITHER: the bare key resolves to plugin-calendar’s calendar VIEW, and this ' + + 'key set describes the `ui:calendar` date-picker primitive. This refusal propagates onto ' + + '`UiCalendarSchema`, which `.extend()`s this mirror — on the MIRROR only; the TypeScript `Omit` ' + + 'collapses into `BaseSchema`’s index signature and carries no member of this declaration at all. ' + + '`view:calendar` is the measured SOLE owner of the bare `calendar` key (`ui:calendar` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), + children: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `calendar` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `defaultValue`, `mode`, `value`. ' + + 'Both readers answer NEITHER: the bare key resolves to plugin-calendar’s calendar VIEW, and this ' + + 'key set describes the `ui:calendar` date-picker primitive. This refusal propagates onto ' + + '`UiCalendarSchema`, which `.extend()`s this mirror — on the MIRROR only; the TypeScript `Omit` ' + + 'collapses into `BaseSchema`’s index signature and carries no member of this declaration at all. ' + + '`view:calendar` is the measured SOLE owner of the bare `calendar` key (`ui:calendar` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), }); /** diff --git a/packages/types/src/zod/layout.zod.ts b/packages/types/src/zod/layout.zod.ts index ccae643633..8cea0a857d 100644 --- a/packages/types/src/zod/layout.zod.ts +++ b/packages/types/src/zod/layout.zod.ts @@ -127,6 +127,26 @@ export const TextSchema = BaseSchema.extend({ .optional() .describe('Text variant/style'), align: z.enum(['left', 'center', 'right', 'justify']).optional().describe('Text alignment'), + body: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `text` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `align`, `className`, `content`, `variant`. ' + + '`ui:text` is the measured SOLE owner of the bare `text` key (`element:text` and `field:text` pass `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), + children: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `text` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `align`, `className`, `content`, `variant`. ' + + '`ui:text` is the measured SOLE owner of the bare `text` key (`element:text` and `field:text` pass `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), }); /** @@ -139,6 +159,26 @@ export const ImageSchema = BaseSchema.extend({ width: z.union([z.string(), z.number()]).optional().describe('Image width'), height: z.union([z.string(), z.number()]).optional().describe('Image height'), objectFit: z.enum(['contain', 'cover', 'fill', 'none', 'scale-down']).optional().describe('Object fit property'), + body: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `image` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `alt`, `src`. ' + + '`ui:image` is the measured SOLE owner of the bare `image` key (`element:image` and `field:image` pass `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), + children: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `image` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `alt`, `src`. ' + + '`ui:image` is the measured SOLE owner of the bare `image` key (`element:image` and `field:image` pass `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), }); /** @@ -206,6 +246,26 @@ export const IconSchema = BaseSchema.extend({ .describe('Lucide glyph name, kebab-case (objectui#5631: was `name`)'), size: z.number().optional().describe('Icon size in pixels'), color: z.string().optional().describe('Icon color'), + body: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `icon` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `className`, `color`, `icon`, `name`, `size`. ' + + '`ui:icon` is the measured SOLE owner of the bare `icon` key (`action:icon` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), + children: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `icon` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `className`, `color`, `icon`, `name`, `size`. ' + + '`ui:icon` is the measured SOLE owner of the bare `icon` key (`action:icon` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), }); /** @@ -375,6 +435,26 @@ export const TabsSchema = BaseSchema.extend({ orientation: z.enum(['horizontal', 'vertical']).optional().describe('Tabs orientation'), items: z.array(TabItemSchema).describe('Tab items configuration'), onValueChange: handlerKeyRefusal('onValueChange', 'runtime-slot', 'Change handler'), + body: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `tabs` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `defaultValue`, `items`, `orientation`, `value`. ' + + '`ui:tabs` is the measured SOLE owner of the bare `tabs` key (`page:tabs` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), + children: retirementTombstone( + 'REFUSED (objectui#9256, ADR-0049) — `tabs` reads NEITHER content channel: measured with the ' + + 'TypeScript type checker over one program per workspace package plus the apps and examples, on a ' + + 'BUILT tree, no renderer read consumes `body` or `children` for this node, and `SchemaRenderer` ' + + 'strips both out of the props bag it spreads. An authored value therefore rendered NOTHING — no ' + + 'error, no warning, no element. ' + + 'What it renders instead: `defaultValue`, `items`, `orientation`, `value`. ' + + '`ui:tabs` is the measured SOLE owner of the bare `tabs` key (`page:tabs` passes `skipFallback: true`); ' + + 're-derive with `pnpm check:registry-bare-names --table` (objectui#9264).', + ), }); /**