From 60f083a51fdb853622af8c4f2ba84cda5258a69c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 05:39:45 +0000 Subject: [PATCH 1/2] feat(types)!: narrow twelve component schemas to the content channel their renderer reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `BaseSchema` declares two optional content channels, `body` and `children`, and its own docblock admits that "some components use `children` instead of `body`" without saying which. The zod base is `.passthrough()` and both keys are optional, so a node carrying the wrong channel type-checked, parsed green, was preserved by the parse, and then rendered an EMPTY element — no signal at authoring time, at validation time, or at render time. Resolve it per component, not with a second alias: each of the twelve schemas whose renderer was MEASURED to read exactly one channel now tombstones the other, `?: never` on the TypeScript face and a by-name refusal on the zod mirror whose message points at the channel to write instead. Reads `children`, refuses `body`: box, span, container, flex, stack, grid, scroll-area, form, toggle. Reads `body`, refuses `children`: alert, badge, tooltip. The read side is measured with the TypeScript type checker over every ComponentRegistry registration in packages/components, so a docblock mention cannot score as a read. Five documents were found authoring a channel their renderer never reads and are corrected here; they rendered nothing before. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ --- .../8284-content-channel-per-component.md | 51 ++++ content/docs/guide/expressions.md | 2 +- content/docs/guide/schema-rendering.md | 4 +- packages/components/TESTING.md | 2 +- packages/react/README.md | 2 +- ...content-channel-per-component-8284.test.ts | 242 ++++++++++++++++++ packages/types/src/data-display.ts | 38 ++- packages/types/src/form.ts | 38 +++ packages/types/src/layout.ts | 133 ++++++++++ packages/types/src/overlay.ts | 20 ++ packages/types/src/zod/data-display.zod.ts | 18 +- packages/types/src/zod/form.zod.ts | 18 +- packages/types/src/zod/layout.zod.ts | 58 ++++- packages/types/src/zod/overlay.zod.ts | 11 +- 14 files changed, 623 insertions(+), 14 deletions(-) create mode 100644 .changeset/8284-content-channel-per-component.md create mode 100644 packages/types/src/__tests__/content-channel-per-component-8284.test.ts diff --git a/.changeset/8284-content-channel-per-component.md b/.changeset/8284-content-channel-per-component.md new file mode 100644 index 0000000000..4b103d0764 --- /dev/null +++ b/.changeset/8284-content-channel-per-component.md @@ -0,0 +1,51 @@ +--- +'@object-ui/types': minor +--- + +Resolve the `body` / `children` duality PER COMPONENT: twelve component schemas now +narrow to the content channel their renderer actually reads and tombstone the other on +both published faces (objectui#8284, maintainer ruling summon #17 decision batch #2, +2026-09-07). + +**BREAKING for authored metadata, deliberately** — and `minor` because this repo's +fixed version group never ships `major` (see AGENTS.md 版本号策略). + +`BaseSchema` declares two optional content channels and its own docblock admits that +"some components use `children` instead of `body`" without saying which. The zod base is +`.passthrough()` and both keys are optional, so a node carrying the wrong channel +type-checked, parsed green, was preserved by the parse — and then rendered an EMPTY +element. No error at authoring time, none at validation time, none at render time. Seven +earlier cards repaired one page of that each (objectui#5027, #3900, #6773, #6806, #8197, +#8234, #6939) before the declaration itself was named. + +**What changes.** For each component below, the channel its renderer does not read is now +`?: never` on the TypeScript face and refused BY NAME on the zod mirror, with a message +that names the channel to write instead: + +| the renderer reads | components | now refused | +|---|---|---| +| `children` | `box`, `span`, `container`, `flex`, `stack`, `grid`, `scroll-area`, `form`, `toggle` | `body` | +| `body` | `alert`, `badge`, `tooltip` (which reads `content` first, `body` as its fallback) | `children` | + +Which channel each renderer reads was measured with the TypeScript type checker over +every `ComponentRegistry.register(...)` call in `packages/components` — a read site is a +property access filed under the type of the object it is read from, so a docblock mention +cannot score. The full 114-row table, including the components deliberately NOT narrowed +here, is on objectui#8284. + +**Migration.** Nothing that renders today stops rendering: a document authoring the +channel its renderer reads is unchanged, and a document authoring the other one rendered +an empty element before and is now refused instead. The repo-wide census found five +documents in this state — `packages/react/README.md`, `content/docs/guide/expressions.md`, +two blocks in `content/docs/guide/schema-rendering.md` and `packages/components/TESTING.md` +— every one of them a `form` or `container` authoring `body`; all five are corrected in +this change. If your own metadata authors the refused channel on one of these twelve node +types, the component was already drawing nothing there; rename the key to the one in the +table. + +**Not narrowed here, and why.** Components whose renderer reads BOTH channels through a +live `children || body` fallback (`div`, `card`, `button`, `aspect-ratio`, the `page` +family), components that read neither, and components with no dedicated declaration +(`sidebar-*`, the `any`-typed registrations) keep both channels. Each is named on +objectui#8284 with the specific measurement it still needs; acting on any of them from the +`packages/components`-only sweep would have been a guess. diff --git a/content/docs/guide/expressions.md b/content/docs/guide/expressions.md index 28354a6103..b890795763 100644 --- a/content/docs/guide/expressions.md +++ b/content/docs/guide/expressions.md @@ -425,7 +425,7 @@ or author each variant and gate it with a condition key: ```json { "type": "form", - "body": [ + "children": [ { "type": "select", "name": "country", diff --git a/content/docs/guide/schema-rendering.md b/content/docs/guide/schema-rendering.md index b07eaa28f4..3cc6830514 100644 --- a/content/docs/guide/schema-rendering.md +++ b/content/docs/guide/schema-rendering.md @@ -172,7 +172,7 @@ Use arrays for multiple items: ```json { "type": "container", - "body": [ + "children": [ { "type": "text", "content": "First item" }, { "type": "text", "content": "Second item" }, { "type": "text", "content": "Third item" } @@ -429,7 +429,7 @@ Always type your schemas for better IDE support and fewer runtime errors. ```json { "type": "container", - "body": { + "children": { "type": "spinner", "visibleOn": "${loading}" } diff --git a/packages/components/TESTING.md b/packages/components/TESTING.md index 2a0e336084..14d1e3264a 100644 --- a/packages/components/TESTING.md +++ b/packages/components/TESTING.md @@ -179,7 +179,7 @@ Components should have valid DOM structure: it('should have valid structure', () => { const { container } = renderComponent({ type: 'container', - body: [{ type: 'text', content: 'Content' }], + children: [{ type: 'text', content: 'Content' }], }); const domCheck = checkDOMStructure(container); diff --git a/packages/react/README.md b/packages/react/README.md index f135b3e478..b706716068 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -43,7 +43,7 @@ import { SchemaRenderer } from '@object-ui/react' const schema = { type: 'form', - body: [ + children: [ { // `content` is evaluated on every component type. `input` has no row in // the spec's expression carriage map, so a `${…}` in ITS `value` would be diff --git a/packages/types/src/__tests__/content-channel-per-component-8284.test.ts b/packages/types/src/__tests__/content-channel-per-component-8284.test.ts new file mode 100644 index 0000000000..5178c3964a --- /dev/null +++ b/packages/types/src/__tests__/content-channel-per-component-8284.test.ts @@ -0,0 +1,242 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * objectui#8284 — the `body` / `children` duality on `BaseSchema` is resolved + * PER COMPONENT: each component schema narrows to the channel its renderer + * actually reads and TOMBSTONES the other, on both published faces + * (maintainer ruling, summon #17 decision batch #2, 2026-09-07, verbatim + * 「同意」). + * + * ## The defect this pins closed + * + * `BaseSchema` declares TWO optional content channels and its own docblock + * admits that "some components use `children` instead of `body`" WITHOUT + * saying which. The zod base is `.passthrough()` and both keys are optional, + * so a node carrying the WRONG channel type-checked, parsed green, was + * PRESERVED by the parse, and then rendered an EMPTY element — no error at + * authoring time, none at validation time, none at render time. Seven cards + * repaired one page of that each (#5027 · #3900 · #6773 · #6806 · #8197 · + * #8234 · #6939) before the declaration itself was named. + * + * ## The population here is FAMILY A + B of the measured table, not "all" + * + * The table posted on objectui#8284 is derived from every + * `ComponentRegistry.register(...)` call under + * `packages/components/src/renderers/**` (114 registrations) and the read side + * is measured with the TypeScript TYPE CHECKER — each `body` / `children` + * property access is filed under the TYPE of the object it is read from, so a + * docblock mention cannot score (the control: `layout/box.tsx:34` says + * `schema.body` in prose and `grep -l` counts it, the checker does not; the + * card's own `17 / 17` figure came from that query). + * + * The twelve rows below are the ones where the renderer reads EXACTLY ONE + * channel, the component owns a dedicated declaration, and exactly one + * registration claims its `type`. `sidebar` is measured into family B and held + * BACK from it, because two registrations claim `sidebar` and the second types + * its schema prop `any`. Families C (reads both — a live `children || body` + * fallback), D (reads neither) and E (no dedicated declaration) are named on + * the card with the measurement each still needs. + * + * ## What is NOT pinned here, and why + * + * ⛔ Not the renderers. Nothing about rendering changes: a document that + * authors the channel its renderer reads is byte-identical through both faces, + * and a document that authors the other one rendered nothing before and + * renders nothing now — it is merely REFUSED first. The counter-probes in + * `examples/schema-catalog/test/badge-demo-label-6829.test.tsx` and + * `packages/components/src/__tests__/span-children-rendering.test.tsx`, which + * deliberately author the dead channel and assert the empty render, therefore + * keep passing. + * + * ## ⚠️ Half of this file is a COMPILE-TIME assertion and vitest CANNOT read it + * + * `?: never` narrowings are erased before a test runs. The `@ts-expect-error` + * lines below are read by `tsc -p tsconfig.test.json` (the `type-check` + * script), NOT by this runner: under vitest alone, removing a tombstone from + * the TypeScript face leaves every case here GREEN. Both readers are the gate; + * either alone reports NOT MEASURED. + */ + +import { describe, it, expect } from 'vitest'; +import { z } from 'zod'; +import type { + BoxSchema, TextSpanSchema, ContainerSchema, FlexSchema, StackSchema, GridSchema, ScrollAreaSchema, +} from '../layout'; +import type { FormSchema, ToggleSchema } from '../form'; +import type { AlertSchema, BadgeSchema } from '../data-display'; +import type { TooltipSchema } from '../overlay'; +import { + BoxSchema as BoxMirror, + TextSpanSchema as SpanMirror, + ContainerSchema as ContainerMirror, + FlexSchema as FlexMirror, + StackSchema as StackMirror, + GridSchema as GridMirror, + ScrollAreaSchema as ScrollAreaMirror, +} from '../zod/layout.zod'; +import { FormSchema as FormMirror, ToggleSchema as ToggleMirror } from '../zod/form.zod'; +import { AlertSchema as AlertMirror, BadgeSchema as BadgeMirror } from '../zod/data-display.zod'; +import { TooltipSchema as TooltipMirror } from '../zod/overlay.zod'; +import { AnyComponentSchema } from '../zod/index.zod'; + +type Mirror = { safeParse: (v: unknown) => { success: boolean; error?: z.ZodError }; shape: Record }; + +/** + * One row of the measured table: the node, its mirror, the live channel, the + * tombstoned one, and the node's OTHER required members — `form` requires + * `fields`, so a bare `{ type: 'form' }` is refused for a reason that has + * nothing to do with this change and would read as a false positive here. + */ +const ROWS: ReadonlyArray, +]> = [ + ['box', BoxMirror as unknown as Mirror, 'children', 'body', {}], + ['span', SpanMirror as unknown as Mirror, 'children', 'body', {}], + ['container', ContainerMirror as unknown as Mirror, 'children', 'body', {}], + ['flex', FlexMirror as unknown as Mirror, 'children', 'body', {}], + ['stack', StackMirror as unknown as Mirror, 'children', 'body', {}], + ['grid', GridMirror as unknown as Mirror, 'children', 'body', {}], + ['scroll-area', ScrollAreaMirror as unknown as Mirror, 'children', 'body', {}], + ['form', FormMirror as unknown as Mirror, 'children', 'body', { fields: [] }], + ['toggle', ToggleMirror as unknown as Mirror, 'children', 'body', {}], + ['alert', AlertMirror as unknown as Mirror, 'body', 'children', {}], + ['badge', BadgeMirror as unknown as Mirror, 'body', 'children', {}], + ['tooltip', TooltipMirror as unknown as Mirror, 'body', 'children', {}], +]; + +const CONTENT = [{ type: 'text', content: 'measured' }]; +const issues = (m: Mirror, doc: unknown) => { + const r = m.safeParse(doc); + return r.success ? null : r.error!.issues.map((i) => ({ code: i.code, path: i.path.join('.'), message: i.message })); +}; + +/* ── (a) the tombstoned channel is REFUSED BY NAME, at its own path ───────── */ + +describe('objectui#8284 — the channel a renderer does not read is refused by name', () => { + it.each(ROWS)('`%s` refuses its DEAD channel at that key\'s own path', (type, mirror, _live, dead, required) => { + const found = issues(mirror, { type, ...required, [dead]: CONTENT }); + expect(found, `${type}.${dead} parsed green — the tombstone is not installed`).not.toBeNull(); + expect(found!.some((i) => i.path === dead && i.code === 'invalid_type')).toBe(true); + }); + + it.each(ROWS)('`%s` — the message names the dead key AND the live one, so the author is told what to write', (type, mirror, live, dead, required) => { + const issue = issues(mirror, { type, ...required, [dead]: CONTENT })!.find((i) => i.path === dead)!; + expect(issue.message).toContain(`\`${dead}\``); + expect(issue.message).toContain(`\`${live}\``); + expect(issue.message).toContain('objectui#8284'); + }); + + it.each(ROWS)('`%s` — ONE string feeds both author-facing channels: the issue message IS the `.describe()` metadata', (type, mirror, _live, dead, required) => { + const issue = issues(mirror, { type, ...required, [dead]: CONTENT })!.find((i) => i.path === dead)!; + expect(mirror.shape[dead]?.description).toBe(issue.message); + }); + + it.each(ROWS)('`%s` — the refusal is about the KEY, not a value domain: every value is refused', (type, mirror, _live, dead, required) => { + for (const value of [CONTENT, 'text', 42, null, {}, []]) { + expect(issues(mirror, { type, ...required, [dead]: value })?.some((i) => i.path === dead)).toBe(true); + } + }); +}); + +/* ── (b) CONTROLS — nothing that rendered stops parsing ───────────────────── */ + +describe('objectui#8284 — CONTROLS: the live channel, and an unrelated key, are untouched', () => { + it.each(ROWS)('`%s` still accepts its LIVE channel — the document that renders today is unchanged', (type, mirror, live, _dead, required) => { + expect(issues(mirror, { type, ...required, [live]: CONTENT }), `${type}.${live} was refused — the narrowing hit the wrong channel`).toBeNull(); + }); + + it.each(ROWS)('`%s` still accepts a bare node and a `className` — the unknown-key policy is byte-identical', (type, mirror, _live, _dead, required) => { + expect(issues(mirror, { type, ...required })).toBeNull(); + expect(issues(mirror, { type, ...required, className: 'p-4' })).toBeNull(); + }); + + it.each(ROWS)('`%s` — the tombstone is a MEMBER of the mirror shape, so the parity ratchet\'s key sets stay equal', (_type, mirror, _live, dead) => { + expect(Object.keys(mirror.shape)).toContain(dead); + }); +}); + +/* ── (c) the refusal reaches NESTED nodes, not only the root ──────────────── */ + +describe('objectui#8284 — a nested node is refused too (the objectui#8344 recursion point selects the arm)', () => { + it('a `scroll-area` authoring `body` INSIDE a `box`\'s `children` is REFUSED — the tombstone is not a root-only rule', () => { + const r = AnyComponentSchema.safeParse({ + type: 'box', + children: [{ type: 'scroll-area', body: CONTENT }], + }); + expect(r.success).toBe(false); + // ⚠️ The child slot is a `z.union`, so what surfaces is ONE + // `invalid_union` at `children` carrying objectui#8344's capped message — + // the nested `body` path and the remedy text are collapsed by that union, + // not by this change. Measured at the ROOT in block (a), where the arm is + // selected directly and the full guidance is reported. + expect(r.error!.issues.map((i) => ({ code: i.code, path: i.path.join('.') }))) + .toEqual([{ code: 'invalid_union', path: 'children' }]); + }); + + it('CONTROL — the same tree authoring `children` on the `scroll-area` parses', () => { + expect(AnyComponentSchema.safeParse({ + type: 'box', + children: [{ type: 'scroll-area', children: CONTENT }], + }).success).toBe(true); + }); +}); + +/* ── (d) the TypeScript face — ⚠️ READ BY `tsc`, NOT BY VITEST ────────────── */ + +describe('objectui#8284 — the TypeScript face refuses the dead channel at the AUTHORING site', () => { + it('the `@ts-expect-error` lines in this block are the assertion; vitest only proves they are reachable', () => { + // Family A — the renderer reads `children`; `body` is `?: never`. + // @ts-expect-error objectui#8284 — `box` reads `children`, never `body` + const box: BoxSchema = { type: 'box', body: CONTENT }; + // @ts-expect-error objectui#8284 — `span` reads `children`, never `body` + const span: TextSpanSchema = { type: 'span', body: CONTENT }; + // @ts-expect-error objectui#8284 — `container` reads `children`, never `body` + const container: ContainerSchema = { type: 'container', body: CONTENT }; + // @ts-expect-error objectui#8284 — `flex` reads `children`, never `body` + const flex: FlexSchema = { type: 'flex', body: CONTENT }; + // @ts-expect-error objectui#8284 — `stack` reads `children`, never `body` + const stack: StackSchema = { type: 'stack', body: CONTENT }; + // @ts-expect-error objectui#8284 — `grid` reads `children`, never `body` + const grid: GridSchema = { type: 'grid', body: CONTENT }; + // @ts-expect-error objectui#8284 — `scroll-area` reads `children`, never `body` + const scrollArea: ScrollAreaSchema = { type: 'scroll-area', body: CONTENT }; + // @ts-expect-error objectui#8284 — `form` reads `children`, never `body` + const form: FormSchema = { type: 'form', body: CONTENT }; + // @ts-expect-error objectui#8284 — `toggle` reads `children`, never `body` + const toggle: ToggleSchema = { type: 'toggle', body: CONTENT }; + + // Family B — the renderer reads `body`; `children` is `?: never`. + // @ts-expect-error objectui#8284 — `alert` reads `body`, never `children` + const alert: AlertSchema = { type: 'alert', children: CONTENT }; + // @ts-expect-error objectui#8284 — `badge` reads `body`, never `children` + const badge: BadgeSchema = { type: 'badge', children: CONTENT }; + // @ts-expect-error objectui#8284 — `tooltip` reads `content`/`body`, never `children` + const tooltip: TooltipSchema = { type: 'tooltip', children: CONTENT }; + + expect([box, span, container, flex, stack, grid, scrollArea, form, toggle, alert, badge, tooltip]).toHaveLength(12); + }); + + it('CONTROL — the LIVE channel compiles on every one of the twelve (no `@ts-expect-error` here, and `tsc` is the reader)', () => { + const live = [ + { type: 'box', children: CONTENT } satisfies BoxSchema, + { type: 'span', children: CONTENT } satisfies TextSpanSchema, + { type: 'container', children: CONTENT } satisfies ContainerSchema, + { type: 'flex', children: CONTENT } satisfies FlexSchema, + { type: 'stack', children: CONTENT } satisfies StackSchema, + { type: 'grid', children: CONTENT } satisfies GridSchema, + { type: 'scroll-area', children: CONTENT } satisfies ScrollAreaSchema, + { type: 'form', children: CONTENT } satisfies FormSchema, + { type: 'toggle', children: CONTENT } satisfies ToggleSchema, + { type: 'alert', body: CONTENT } satisfies AlertSchema, + { type: 'badge', body: CONTENT } satisfies BadgeSchema, + { type: 'tooltip', body: CONTENT } satisfies TooltipSchema, + ]; + expect(live).toHaveLength(12); + }); +}); diff --git a/packages/types/src/data-display.ts b/packages/types/src/data-display.ts index db0db1b7ab..bbabeff90f 100644 --- a/packages/types/src/data-display.ts +++ b/packages/types/src/data-display.ts @@ -55,9 +55,24 @@ export interface AlertSchema extends BaseSchema { */ onDismiss?: never; /** - * Child content + * REFUSED BY NAME (objectui#8284, ADR-0049) — `alert` reads `body`, and no + * renderer read consumes `children`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/data-display/alert.tsx:22`. The same sweep + * finds zero `children` reads for this node type. + * + * `children` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `alert` reads — author `body`. */ - children?: SchemaNode | SchemaNode[]; + children?: never; } /** @@ -106,9 +121,24 @@ export interface BadgeSchema extends BaseSchema { */ icon?: string; /** - * Child content + * REFUSED BY NAME (objectui#8284, ADR-0049) — `badge` reads `body`, and no + * renderer read consumes `children`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/data-display/badge.tsx:32`. The same sweep + * finds zero `children` reads for this node type. + * + * `children` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `badge` reads — author `body`. */ - children?: SchemaNode | SchemaNode[]; + children?: never; } /** diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 65b9980c4f..34923cfc8b 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -542,6 +542,25 @@ export interface ToggleSchema extends BaseSchema { * Child content */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `toggle` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/form/toggle.tsx:38`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `toggle` reads — author `children`. + */ + body?: never; } /** @@ -1378,6 +1397,25 @@ export interface FormSchema extends BaseSchema { * Child components (alternative to fields array) */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `form` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/form/form.tsx:1528`, `:1531`, `:1745`, `:1748`, `:3121`, `:3124`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `form` reads — author `children`. + */ + body?: never; } /** diff --git a/packages/types/src/layout.ts b/packages/types/src/layout.ts index b0a971ed74..a93bffdb49 100644 --- a/packages/types/src/layout.ts +++ b/packages/types/src/layout.ts @@ -54,6 +54,25 @@ export interface BoxSchema extends BaseSchema { * Child components */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `box` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/layout/box.tsx:56`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `box` reads — author `children`. + */ + body?: never; } /** @@ -69,6 +88,25 @@ export interface TextSpanSchema extends BaseSchema { * Child components */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `span` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/basic/span.tsx:143`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `span` reads — author `children`. + */ + body?: never; } /** @@ -251,6 +289,25 @@ export interface ContainerSchema extends BaseSchema { * Child components */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `container` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/layout/container.tsx:101`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `container` reads — author `children`. + */ + body?: never; } /** @@ -356,6 +413,25 @@ export interface FlexLayoutProps { */ export interface FlexSchema extends BaseSchema, FlexLayoutProps { type: 'flex'; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `flex` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/layout/flex.tsx:93`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `flex` reads — author `children`. + */ + body?: never; } /** @@ -367,6 +443,25 @@ export interface FlexSchema extends BaseSchema, FlexLayoutProps { */ export interface StackSchema extends BaseSchema, FlexLayoutProps { type: 'stack'; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `stack` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/layout/stack.tsx:99`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `stack` reads — author `children`. + */ + body?: never; } /** @@ -395,6 +490,25 @@ export interface GridSchema extends BaseSchema { * Child components */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `grid` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/layout/grid.tsx:168`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `grid` reads — author `children`. + */ + body?: never; } /** @@ -552,6 +666,25 @@ export interface ScrollAreaSchema extends BaseSchema { * Child components */ children?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `scroll-area` reads `children`, and no + * renderer read consumes `body`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/complex/scroll-area.tsx:34`. The same sweep + * finds zero `body` reads for this node type. + * + * `body` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `scroll-area` reads — author `children`. + */ + body?: never; } /** diff --git a/packages/types/src/overlay.ts b/packages/types/src/overlay.ts index 9164c74080..5d76a18145 100644 --- a/packages/types/src/overlay.ts +++ b/packages/types/src/overlay.ts @@ -483,6 +483,26 @@ export interface TooltipSchema extends BaseSchema { * registration as the "Rich Content" slot. */ body?: SchemaNode | SchemaNode[]; + /** + * REFUSED BY NAME (objectui#8284, ADR-0049) — `tooltip` reads `content` and, + * as the fallback for that same slot, `body`. No renderer read consumes + * `children`. + * + * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a + * docblock mention is not a read; `layout/box.tsx:34` is the control that + * separates the two): `packages/components/src/renderers/overlay/tooltip.tsx:31`. The same sweep + * finds zero `children` reads for this node type. + * + * `children` is inherited-and-optional from {@link BaseSchema}, whose own + * docblock admits "some components use `children` instead of `body`" without + * saying which — so authoring it here type-checked, parsed green through + * `.passthrough()`, and rendered an EMPTY element with no error and no + * warning. Per component, the channel a renderer does not read is now + * tombstoned on both published faces (maintainer ruling, summon #17 decision batch #2, 2026-09-07). + * + * @deprecated Not a channel `tooltip` reads — author `body`. + */ + children?: never; /** * Tooltip side * @default 'top' diff --git a/packages/types/src/zod/data-display.zod.ts b/packages/types/src/zod/data-display.zod.ts index 27a048773e..01784a4d0e 100644 --- a/packages/types/src/zod/data-display.zod.ts +++ b/packages/types/src/zod/data-display.zod.ts @@ -65,7 +65,14 @@ export const AlertSchema = BaseSchema.extend({ icon: z.string().optional().describe('Alert icon'), dismissible: z.boolean().optional().describe('Whether alert can be dismissed'), onDismiss: handlerKeyRefusal('onDismiss', 'retired', 'Dismiss handler'), - children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + children: aliasKeyRefusal( + 'children', + 'body', + 'this alert node', + '`alert` reads `body`, never `children` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/data-display/alert.tsx:22). ' + + '`children` is inherited from `BaseSchema`, so an authored `children` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -88,7 +95,14 @@ export const BadgeSchema = BaseSchema.extend({ label: z.string().optional().describe('Badge label'), variant: z.enum(['default', 'secondary', 'destructive', 'outline']).optional().describe('Badge variant'), icon: z.string().optional().describe('Badge icon'), - children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + children: aliasKeyRefusal( + 'children', + 'body', + 'this badge node', + '`badge` reads `body`, never `children` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/data-display/badge.tsx:32). ' + + '`children` is inherited from `BaseSchema`, so an authored `children` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** diff --git a/packages/types/src/zod/form.zod.ts b/packages/types/src/zod/form.zod.ts index 0d7db28821..b04b6bad22 100644 --- a/packages/types/src/zod/form.zod.ts +++ b/packages/types/src/zod/form.zod.ts @@ -17,7 +17,7 @@ */ import { z } from 'zod'; -import { handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; +import { aliasKeyRefusal, handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; import { SelectOptionSchema as SpecSelectOptionSchema } from '@objectstack/spec/data'; import { BaseSchema, SchemaNodeSchema } from './base.zod.js'; // The predicate wire shape (`string | { dialect?, source }`, #2212) was a @@ -337,6 +337,14 @@ export const ToggleSchema = BaseSchema.extend({ size: z.enum(['default', 'sm', 'lg']).optional().describe('Toggle size'), onChange: handlerKeyRefusal('onChange', 'retired', 'Change handler'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this toggle node', + '`toggle` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/form/toggle.tsx:38). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -684,6 +692,14 @@ export const FormSchema = BaseSchema.extend({ onChange: handlerKeyRefusal('onChange', 'runtime-slot', 'Change handler'), onCancel: handlerKeyRefusal('onCancel', 'runtime-slot', 'Cancel handler'), showActions: z.boolean().optional().describe('Show action buttons'), + body: aliasKeyRefusal( + 'body', + 'children', + 'this form node', + '`form` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/form/form.tsx:1528, :1531, :1745, :1748, :3121, :3124). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** diff --git a/packages/types/src/zod/layout.zod.ts b/packages/types/src/zod/layout.zod.ts index 1b9a1b32fb..3a8ae2567a 100644 --- a/packages/types/src/zod/layout.zod.ts +++ b/packages/types/src/zod/layout.zod.ts @@ -17,7 +17,7 @@ */ import { z } from 'zod'; -import { handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; +import { aliasKeyRefusal, handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; import { PageSchema as SpecPageSchema, PageTypeSchema as SpecPageTypeSchema, @@ -76,6 +76,14 @@ export const DivSchema = BaseSchema.extend({ export const BoxSchema = BaseSchema.extend({ type: z.literal('box'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this box node', + '`box` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/box.tsx:56). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -85,6 +93,14 @@ export const TextSpanSchema = BaseSchema.extend({ type: z.literal('span'), value: z.string().optional().describe('Text content'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this span node', + '`span` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/basic/span.tsx:143). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -213,6 +229,14 @@ export const ContainerSchema = BaseSchema.extend({ centered: z.boolean().optional().describe('Center the container'), padding: z.number().optional().describe('Padding value'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this container node', + '`container` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/container.tsx:101). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -232,6 +256,14 @@ export const FlexSchema = BaseSchema.extend({ gap: z.number().optional().describe('Gap between items (Tailwind scale 0-8)'), wrap: z.boolean().optional().describe('Allow items to wrap'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this flex node', + '`flex` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/flex.tsx:93). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -245,6 +277,14 @@ export const StackSchema = BaseSchema.extend({ gap: z.number().optional(), wrap: z.boolean().optional(), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this stack node', + '`stack` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/stack.tsx:99). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -273,6 +313,14 @@ export const GridSchema = BaseSchema.extend({ ]).optional().describe('Number of columns (responsive)'), gap: z.number().optional().describe('Gap between items (Tailwind scale 0-8)'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this grid node', + '`grid` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/grid.tsx:168). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** @@ -324,6 +372,14 @@ export const ScrollAreaSchema = BaseSchema.extend({ width: z.union([z.string(), z.number()]).optional().describe('Width of scroll container'), orientation: z.enum(['vertical', 'horizontal', 'both']).optional().describe('Scrollbar orientation'), children: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).optional(), + body: aliasKeyRefusal( + 'body', + 'children', + 'this scroll-area node', + '`scroll-area` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/complex/scroll-area.tsx:34). ' + + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** diff --git a/packages/types/src/zod/overlay.zod.ts b/packages/types/src/zod/overlay.zod.ts index 4010211aa3..67a936218a 100644 --- a/packages/types/src/zod/overlay.zod.ts +++ b/packages/types/src/zod/overlay.zod.ts @@ -19,7 +19,7 @@ import { z } from 'zod'; import { BaseSchema, SchemaNodeSchema } from './base.zod.js'; import type { MenuItem } from '../overlay.js'; -import { handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; +import { aliasKeyRefusal, handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; /** * Dialog Schema - Dialog/modal component @@ -262,6 +262,15 @@ export const TooltipSchema = BaseSchema.extend({ side: z.enum(['top', 'right', 'bottom', 'left']).optional().describe('Tooltip side'), align: z.enum(['start', 'center', 'end']).optional().describe('Tooltip alignment'), delayDuration: z.number().optional().describe('Delay before showing (ms)'), + children: aliasKeyRefusal( + 'children', + 'body', + 'this tooltip node', + '`tooltip` reads `content` first and `body` as the fallback for that same slot, and never `children` ' + + '(READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/overlay/tooltip.tsx:31). ' + + '`children` is inherited from `BaseSchema`, so an authored `children` parsed green here and rendered ' + + 'an EMPTY element — no error, no warning. objectui#8284.', + ), }); /** From d3fe8e010d32095cad3f46ad29fedb1c657032d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 06:06:00 +0000 Subject: [PATCH 2/2] test(types): record the tooltip accept-set change on its objectui#6939 pin, and cite read sites by content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups measured after the first commit. `overlay-trigger-mirror-6939.test.tsx` pinned "the accept set only WIDENED — the `children` spelling still parses" for `tooltip`, on the reasoning that `children` survives as BaseSchema's optional key. That half is exactly what the objectui#8284 ruling overrules for this component: the channel a renderer does not read is refused instead of drawing an empty tooltip. The case now asserts the refusal and keeps `context-menu` — deliberately not narrowed — as the live control beside it. The new docblocks and mirror messages cited read sites as file:line. `check-new-cross-file-line-citations.mjs` reports that as the pattern objectui#7853 / objectui#8875 asked authors to stop using, so each now names the read EXPRESSION and the file. Count went 47 new citations to 0; no pre-existing docblock is touched. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ --- .../test/overlay-trigger-mirror-6939.test.tsx | 27 +++++++-- ...content-channel-per-component-8284.test.ts | 7 ++- packages/types/src/data-display.ts | 16 ++++-- packages/types/src/form.ts | 16 ++++-- packages/types/src/layout.ts | 56 ++++++++++++------- packages/types/src/overlay.ts | 8 ++- packages/types/src/zod/data-display.zod.ts | 4 +- packages/types/src/zod/form.zod.ts | 4 +- packages/types/src/zod/layout.zod.ts | 14 ++--- packages/types/src/zod/overlay.zod.ts | 2 +- 10 files changed, 98 insertions(+), 56 deletions(-) diff --git a/examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx b/examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx index f992216515..f1e1275e86 100644 --- a/examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx +++ b/examples/schema-catalog/test/overlay-trigger-mirror-6939.test.tsx @@ -124,14 +124,31 @@ describe('objectui#6939 — `children` is no longer required on either member', expect(ContextMenuSchema.safeParse({ type: 'context-menu', items: [] }).success).toBe(true); }); - it('the accept set only WIDENED — the `children` spelling still parses', () => { - // The ruling's patch reasoning. Nothing that validated before this change - // may stop validating; `children` survives as `BaseSchema`'s optional key. - expect(TooltipSchema.safeParse({ + it('objectui#8284 SUPERSEDES the widen-only half for `tooltip`: `children` is now REFUSED by name', () => { + // This case used to assert "the accept set only WIDENED — the `children` + // spelling still parses", on objectui#6939's reasoning that `children` + // survives as `BaseSchema`'s optional key. That half is now overruled for + // `tooltip` by the objectui#8284 ruling (summon #17, decision batch #2, + // 2026-09-07): the channel a renderer does not read is tombstoned per + // component, so authoring it is refused at validation instead of drawing + // an empty tooltip. #6939's own declaration already said "nothing reads + // `children` here" — this is that sentence made enforceable. + const refused = TooltipSchema.safeParse({ type: 'tooltip', content: 'Helpful information', children: [{ type: 'button', label: 'Hover me' }], - }).success).toBe(true); + }); + expect(refused.success).toBe(false); + if (!refused.success) { + expect(refused.error.issues.map((i) => i.path.join('.'))).toEqual(['children']); + expect(refused.error.issues[0]!.message).toContain('objectui#8284'); + } + + // CONTROL, unchanged and deliberately so: `context-menu` is NOT in the + // family objectui#8284 narrowed (its renderer reads neither `body` nor + // `children`, and that verdict needs a cross-package sweep before it can + // be acted on). Its `children` still parses, which is what keeps the line + // above a reading about `tooltip` rather than about the whole mirror. expect(ContextMenuSchema.safeParse({ type: 'context-menu', items: [{ label: 'Copy' }], diff --git a/packages/types/src/__tests__/content-channel-per-component-8284.test.ts b/packages/types/src/__tests__/content-channel-per-component-8284.test.ts index 5178c3964a..a38bfed22b 100644 --- a/packages/types/src/__tests__/content-channel-per-component-8284.test.ts +++ b/packages/types/src/__tests__/content-channel-per-component-8284.test.ts @@ -31,9 +31,10 @@ * `packages/components/src/renderers/**` (114 registrations) and the read side * is measured with the TypeScript TYPE CHECKER — each `body` / `children` * property access is filed under the TYPE of the object it is read from, so a - * docblock mention cannot score (the control: `layout/box.tsx:34` says - * `schema.body` in prose and `grep -l` counts it, the checker does not; the - * card's own `17 / 17` figure came from that query). + * docblock mention cannot score (the control: the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and `grep -l` counts + * it, the checker does not; the card's own `17 / 17` figure came from that + * query). * * The twelve rows below are the ones where the renderer reads EXACTLY ONE * channel, the component owns a dedicated declaration, and exactly one diff --git a/packages/types/src/data-display.ts b/packages/types/src/data-display.ts index bbabeff90f..5afa19022c 100644 --- a/packages/types/src/data-display.ts +++ b/packages/types/src/data-display.ts @@ -59,9 +59,11 @@ export interface AlertSchema extends BaseSchema { * renderer read consumes `children`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/data-display/alert.tsx:22`. The same sweep - * finds zero `children` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.body` read in + * `packages/components/src/renderers/data-display/alert.tsx`. The same sweep finds zero `children` reads + * for this node type. * * `children` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -125,9 +127,11 @@ export interface BadgeSchema extends BaseSchema { * renderer read consumes `children`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/data-display/badge.tsx:32`. The same sweep - * finds zero `children` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.body` read in + * `packages/components/src/renderers/data-display/badge.tsx`. The same sweep finds zero `children` reads + * for this node type. * * `children` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 34923cfc8b..6ed2dfa649 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -547,9 +547,11 @@ export interface ToggleSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/form/toggle.tsx:38`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/form/toggle.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -1402,9 +1404,11 @@ export interface FormSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/form/form.tsx:1528`, `:1531`, `:1745`, `:1748`, `:3121`, `:3124`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/form/form.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without diff --git a/packages/types/src/layout.ts b/packages/types/src/layout.ts index a93bffdb49..1189a605c7 100644 --- a/packages/types/src/layout.ts +++ b/packages/types/src/layout.ts @@ -59,9 +59,11 @@ export interface BoxSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/layout/box.tsx:56`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/layout/box.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -93,9 +95,11 @@ export interface TextSpanSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/basic/span.tsx:143`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/basic/span.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -294,9 +298,11 @@ export interface ContainerSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/layout/container.tsx:101`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/layout/container.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -418,9 +424,11 @@ export interface FlexSchema extends BaseSchema, FlexLayoutProps { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/layout/flex.tsx:93`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/layout/flex.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -448,9 +456,11 @@ export interface StackSchema extends BaseSchema, FlexLayoutProps { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/layout/stack.tsx:99`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/layout/stack.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -495,9 +505,11 @@ export interface GridSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/layout/grid.tsx:168`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/layout/grid.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without @@ -671,9 +683,11 @@ export interface ScrollAreaSchema extends BaseSchema { * renderer read consumes `body`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/complex/scroll-area.tsx:34`. The same sweep - * finds zero `body` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.children` read in + * `packages/components/src/renderers/complex/scroll-area.tsx`. The same sweep finds zero `body` reads + * for this node type. * * `body` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without diff --git a/packages/types/src/overlay.ts b/packages/types/src/overlay.ts index 5d76a18145..c14a8d448a 100644 --- a/packages/types/src/overlay.ts +++ b/packages/types/src/overlay.ts @@ -489,9 +489,11 @@ export interface TooltipSchema extends BaseSchema { * `children`. * * READ SITE, measured with the TypeScript TYPE CHECKER and not with grep (a - * docblock mention is not a read; `layout/box.tsx:34` is the control that - * separates the two): `packages/components/src/renderers/overlay/tooltip.tsx:31`. The same sweep - * finds zero `children` reads for this node type. + * docblock mention is not a read; the `BoxSchema` docblock in + * `renderers/layout/box.tsx` says `schema.body` in prose and grep counts + * it): the `schema.body` read in + * `packages/components/src/renderers/overlay/tooltip.tsx`. The same sweep finds zero `children` reads + * for this node type. * * `children` is inherited-and-optional from {@link BaseSchema}, whose own * docblock admits "some components use `children` instead of `body`" without diff --git a/packages/types/src/zod/data-display.zod.ts b/packages/types/src/zod/data-display.zod.ts index 01784a4d0e..9d1b3926f7 100644 --- a/packages/types/src/zod/data-display.zod.ts +++ b/packages/types/src/zod/data-display.zod.ts @@ -69,7 +69,7 @@ export const AlertSchema = BaseSchema.extend({ 'children', 'body', 'this alert node', - '`alert` reads `body`, never `children` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/data-display/alert.tsx:22). ' + '`alert` reads `body`, never `children` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/data-display/alert.tsx`). ' + '`children` is inherited from `BaseSchema`, so an authored `children` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -99,7 +99,7 @@ export const BadgeSchema = BaseSchema.extend({ 'children', 'body', 'this badge node', - '`badge` reads `body`, never `children` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/data-display/badge.tsx:32). ' + '`badge` reads `body`, never `children` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/data-display/badge.tsx`). ' + '`children` is inherited from `BaseSchema`, so an authored `children` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), diff --git a/packages/types/src/zod/form.zod.ts b/packages/types/src/zod/form.zod.ts index b04b6bad22..01ca3b5fca 100644 --- a/packages/types/src/zod/form.zod.ts +++ b/packages/types/src/zod/form.zod.ts @@ -341,7 +341,7 @@ export const ToggleSchema = BaseSchema.extend({ 'body', 'children', 'this toggle node', - '`toggle` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/form/toggle.tsx:38). ' + '`toggle` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/form/toggle.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -696,7 +696,7 @@ export const FormSchema = BaseSchema.extend({ 'body', 'children', 'this form node', - '`form` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/form/form.tsx:1528, :1531, :1745, :1748, :3121, :3124). ' + '`form` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/form/form.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), diff --git a/packages/types/src/zod/layout.zod.ts b/packages/types/src/zod/layout.zod.ts index 3a8ae2567a..0328da80d2 100644 --- a/packages/types/src/zod/layout.zod.ts +++ b/packages/types/src/zod/layout.zod.ts @@ -80,7 +80,7 @@ export const BoxSchema = BaseSchema.extend({ 'body', 'children', 'this box node', - '`box` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/box.tsx:56). ' + '`box` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/layout/box.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -97,7 +97,7 @@ export const TextSpanSchema = BaseSchema.extend({ 'body', 'children', 'this span node', - '`span` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/basic/span.tsx:143). ' + '`span` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/basic/span.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -233,7 +233,7 @@ export const ContainerSchema = BaseSchema.extend({ 'body', 'children', 'this container node', - '`container` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/container.tsx:101). ' + '`container` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/layout/container.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -260,7 +260,7 @@ export const FlexSchema = BaseSchema.extend({ 'body', 'children', 'this flex node', - '`flex` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/flex.tsx:93). ' + '`flex` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/layout/flex.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -281,7 +281,7 @@ export const StackSchema = BaseSchema.extend({ 'body', 'children', 'this stack node', - '`stack` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/stack.tsx:99). ' + '`stack` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/layout/stack.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -317,7 +317,7 @@ export const GridSchema = BaseSchema.extend({ 'body', 'children', 'this grid node', - '`grid` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/layout/grid.tsx:168). ' + '`grid` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/layout/grid.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), @@ -376,7 +376,7 @@ export const ScrollAreaSchema = BaseSchema.extend({ 'body', 'children', 'this scroll-area node', - '`scroll-area` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/complex/scroll-area.tsx:34). ' + '`scroll-area` reads `children`, never `body` (READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/complex/scroll-area.tsx`). ' + '`body` is inherited from `BaseSchema`, so an authored `body` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ), diff --git a/packages/types/src/zod/overlay.zod.ts b/packages/types/src/zod/overlay.zod.ts index 67a936218a..ea6a75ab05 100644 --- a/packages/types/src/zod/overlay.zod.ts +++ b/packages/types/src/zod/overlay.zod.ts @@ -267,7 +267,7 @@ export const TooltipSchema = BaseSchema.extend({ 'body', 'this tooltip node', '`tooltip` reads `content` first and `body` as the fallback for that same slot, and never `children` ' - + '(READ SITE, measured with the TypeScript type checker: packages/components/src/renderers/overlay/tooltip.tsx:31). ' + + '(READ SITE, measured with the TypeScript type checker: `packages/components/src/renderers/overlay/tooltip.tsx`). ' + '`children` is inherited from `BaseSchema`, so an authored `children` parsed green here and rendered ' + 'an EMPTY element — no error, no warning. objectui#8284.', ),