From 24f4cd3af74245c580bbfa6dc3e56cb0976078b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 13:12:14 +0000 Subject: [PATCH 1/2] feat(types)!: refuse both content channels on the re-measure's residual (objectui#9256) The family-D re-measure keyed the population on each registered key's type LITERAL rather than on the renderer's declared props type. Thirteen declarations whose renderer reads neither `body` nor `children` still accepted `children`: - markdown, chart, bar-chart, code-editor, detail, report: `?: never` pair on the TypeScript face and two `retirementTombstone` members fed one `neitherContentChannelGuidance` string on the zod mirror; - list-view: the zod mirror only, since `ListViewSchema` is `z.input` of that mirror intersected with its runtime props; - the six designer faces (page, data-model, process, report, object manager, field): TypeScript only, since none has a zod mirror. New pin file content-channel-remeasure-9256.test.ts. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk --- .../content-channel-remeasure-9256.test.ts | 294 ++++++++++++++++++ packages/types/src/crud.ts | 34 ++ packages/types/src/data-display.ts | 98 ++++++ packages/types/src/designer.ts | 218 +++++++++++++ packages/types/src/form.ts | 33 ++ packages/types/src/reports.ts | 34 ++ packages/types/src/zod/crud.zod.ts | 15 +- packages/types/src/zod/data-display.zod.ts | 36 +++ packages/types/src/zod/form.zod.ts | 12 + packages/types/src/zod/objectql.zod.ts | 15 + packages/types/src/zod/reports.zod.ts | 15 +- 11 files changed, 802 insertions(+), 2 deletions(-) create mode 100644 packages/types/src/__tests__/content-channel-remeasure-9256.test.ts diff --git a/packages/types/src/__tests__/content-channel-remeasure-9256.test.ts b/packages/types/src/__tests__/content-channel-remeasure-9256.test.ts new file mode 100644 index 0000000000..f9d969c98d --- /dev/null +++ b/packages/types/src/__tests__/content-channel-remeasure-9256.test.ts @@ -0,0 +1,294 @@ +/** + * 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#9256 — the RE-MEASURE slice: registrations whose renderer reads + * NEITHER content channel and whose `type` literal has a published declaration + * that still accepted `children`. Family D's earlier pins live in + * `content-channel-family-d-9256.test.ts`, `content-channel-e3-residual-9256.test.ts` + * and `content-channel-input-9256.test.ts`; this file carries the ones this + * slice adds, and nothing it pins is restated there. + * + * ## Why these were missed, and how they were found + * + * The first family-D sweep keyed its population on the renderer's DECLARED + * schema type: a registration typed with a published declaration was family D, + * and one typed with an inline or local type was filed as having no published + * face to narrow. That second bucket was wrong for every registration whose + * `type` LITERAL has a published declaration anyway — the declaration an author + * types against is chosen by the literal, not by the renderer's props type. The + * re-measure keys the population on the literal instead: every registered key + * is joined to the published TypeScript declarations and zod arms that carry + * its literal, and each governing registration's reads are taken from the + * TypeScript compiler-API instrument (one program per workspace package, on a + * BUILT tree, every `.body` / `.children` read filed under its receiver's + * declared type, with the inline-typed and prop-spread hops attributed file by + * file). The table is on objectui#9256. + * + * ## What is narrowed here + * + * - `markdown`, `chart`, `bar-chart`, `code-editor`, `detail`, `report` — + * both channels `?: never` on the TypeScript face and a by-name refusal on + * the zod mirror (two `retirementTombstone` members fed one + * `neitherContentChannelGuidance` string), each kept a MEMBER so + * `zod-mirror-parity`'s key sets stay equal. + * - `list-view` — the zod mirror only, because this arm FEEDS its TypeScript + * face: `ListViewSchema` is `z.input` of the mirror intersected with + * `ListViewRuntimeProps`, so the two members are what put the refusal on + * both faces. + * - the six designers (`page-designer`, `data-model-designer`, + * `process-designer`, `report-designer`, `object-manager`, + * `field-designer`) — the TypeScript face only: none of them has a zod + * mirror, so that face is the only gate, as it was for `nl-query`. + * + * `body` was already refused on every one of these faces by `BaseSchema` + * (objectui#6771); it is restated because that refusal names `children` as the + * remedy, and `children` is dead here too. + * + * ## ⚠️ Half of this file is a COMPILE-TIME assertion and vitest CANNOT read it + * + * `?: never` is erased before a test runs. The `@ts-expect-error` lines in the + * last block are read by `tsc -p tsconfig.test.json` (the `type-check` script), + * NOT by this runner: under vitest alone, deleting a tombstone from the + * TypeScript face leaves every case here GREEN. Both readers are the gate. + */ + +import { describe, it, expect } from 'vitest'; +import type { z } from 'zod'; +import { + MarkdownSchema as MarkdownMirror, + ChartSchema as ChartMirror, + BarChartSchema as BarChartMirror, +} from '../zod/data-display.zod'; +import { CodeEditorSchema as CodeEditorMirror } from '../zod/form.zod'; +import { DetailSchema as DetailMirror } from '../zod/crud.zod'; +import { ReportComponentSchema as ReportMirror } from '../zod/reports.zod'; +import { ListViewSchema as ListViewMirror } from '../zod/objectql.zod'; +import { AnyComponentSchema } from '../zod/index.zod'; +import type { MarkdownSchema, ChartSchema, BarChartSchema } from '../data-display'; +import type { CodeEditorSchema } from '../form'; +import type { DetailSchema } from '../crud'; +import type { ReportComponentSchema } from '../reports'; +import type { ListViewSchema } from '../objectql'; +import type { + PageDesignerSchema, + DataModelDesignerSchema, + ProcessDesignerSchema, + ReportDesignerSchema, + ObjectManagerSchema, + FieldDesignerSchema, +} from '../designer'; + +type Mirror = { + safeParse: (v: unknown) => { success: boolean; error?: z.ZodError }; + shape: Record; +}; + +/** + * One row per narrowed zod arm: its `type`, the mirror, and the node's OTHER + * required members — `{ type: 'markdown' }` alone is refused for a missing + * `content`, which has nothing to do with this change and would read here as a + * false positive. The CONTROL block proves every `required` set parses. + */ +const ROWS: ReadonlyArray]> = [ + ['markdown', MarkdownMirror as unknown as Mirror, { content: '# Title' }], + ['chart', ChartMirror as unknown as Mirror, { chartType: 'bar', series: [] }], + ['bar-chart', BarChartMirror as unknown as Mirror, {}], + ['code-editor', CodeEditorMirror as unknown as Mirror, {}], + ['detail', DetailMirror as unknown as Mirror, {}], + ['report', ReportMirror as unknown as Mirror, {}], + ['list-view', ListViewMirror as unknown as Mirror, { objectName: 'account' }], +]; + +const CHANNELS = ['body', 'children'] as const; +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 })); +}; +const CASES = ROWS.flatMap(([type, mirror, required]) => + CHANNELS.map((key) => [`${type}.${key}`, type, mirror, key, required] as const)); + +/* ── (a) both channels are REFUSED BY NAME, at the key's own path ─────────── */ + +describe('objectui#9256 re-measure — both content channels are refused where the renderer reads neither', () => { + it('the population is the narrowed one — a row dropped from the table fails here', () => { + expect(ROWS).toHaveLength(7); + expect(CASES).toHaveLength(14); + }); + + it.each(CASES)('%s is refused at that key\'s own path', (label, type, mirror, key, required) => { + const found = issues(mirror, { ...required, type, [key]: CONTENT }); + expect(found, `${label} parsed green — the tombstone is not installed`).not.toBeNull(); + expect(found!.some((i) => i.path === key && i.code === 'invalid_type')).toBe(true); + }); + + it.each(CASES)('%s — the message names the channel, the card, and what the node renders instead', (_label, type, mirror, key, required) => { + const issue = issues(mirror, { ...required, type, [key]: CONTENT })!.find((i) => i.path === key)!; + expect(issue.message).toContain(`\`${key}\``); + expect(issue.message).toContain(`\`${type}\` reads NEITHER content channel`); + expect(issue.message).toContain('objectui#9256'); + expect(issue.message).toContain('What it renders instead: '); + // ⛔ Not `BaseSchema`'s generic `body` refusal, which names `children` as + // the remedy — on these nodes `children` is refused as well. + expect(issue.message).not.toContain('Did you mean'); + }); + + it.each(CASES)('%s — ONE string feeds both author-facing channels: the issue message IS the `.describe()` metadata', (_label, type, mirror, key, required) => { + const issue = issues(mirror, { ...required, type, [key]: CONTENT })!.find((i) => i.path === key)!; + expect(mirror.shape[key]?.description).toBe(issue.message); + }); + + it.each(CASES)('%s — the refusal is about the KEY, not a value domain: every value is refused', (_label, type, mirror, key, required) => { + for (const value of [CONTENT, 'text', 42, null, {}, []]) { + expect(issues(mirror, { ...required, type, [key]: value })?.some((i) => i.path === key)).toBe(true); + } + }); + + it.each(CASES)('%s — the refusal reaches the node through `AnyComponentSchema`, not only its own arm', (_label, type, _mirror, key, required) => { + expect(AnyComponentSchema.safeParse({ ...required, type }).success).toBe(true); + expect(AnyComponentSchema.safeParse({ ...required, type, [key]: CONTENT }).success).toBe(false); + }); +}); + +/* ── (b) CONTROLS — nothing that parsed before stops parsing ──────────────── */ + +describe('objectui#9256 re-measure — CONTROLS', () => { + it.each(ROWS)('`%s` still parses with its own required members and a `className`', (type, mirror, required) => { + expect(issues(mirror, { ...required, type })).toBeNull(); + expect(issues(mirror, { ...required, type, className: 'p-4' })).toBeNull(); + }); + + it.each(CASES)('%s — the tombstone is a MEMBER of the mirror shape, so the parity ratchet\'s key sets stay equal', (_label, _type, mirror, key) => { + expect(Object.keys(mirror.shape)).toContain(key); + }); + + it('a nested narrowed node is refused inside a container that reads `children`', () => { + const tree = (child: Record) => ({ type: 'div', children: [child] }); + expect(AnyComponentSchema.safeParse(tree({ type: 'markdown', content: '# Title' })).success).toBe(true); + expect(AnyComponentSchema.safeParse( + tree({ type: 'markdown', content: '# Title', children: CONTENT }), + ).success).toBe(false); + }); + + it('family C still reads a content channel — `div` takes `children`', () => { + // The distinction this card rests on: a reader keeps its channel, a + // non-reader loses both. Measured in the same run as the rows above. + expect(AnyComponentSchema.safeParse({ type: 'div', children: CONTENT }).success).toBe(true); + }); + + it('the ITEM channels of the narrowed nodes are untouched — `detail` tabs still carry `content`', () => { + // `DetailSchema.tabs[].content` is an ITEM key, not the node's own channel; + // the tombstone above is about the node's `body` / `children` only. + expect(issues(DetailMirror as unknown as Mirror, { + type: 'detail', + tabs: [{ key: 'notes', label: 'Notes', content: CONTENT }], + })).toBeNull(); + }); +}); + +/* ── (c) the TypeScript face — ⚠️ READ BY `tsc`, NOT BY VITEST ────────────── */ + +describe('objectui#9256 re-measure — the TypeScript face refuses both channels at the AUTHORING site', () => { + // Each refused line spreads a base that the CONTROL test below proves + // compiles, so the ONLY difference an `@ts-expect-error` can be answering is + // the channel written beside it — not a missing required member. + const markdown = { type: 'markdown', content: '# Title' } satisfies MarkdownSchema; + const chart = { type: 'chart', chartType: 'bar', series: [] } satisfies ChartSchema; + const barChart = { type: 'bar-chart' } satisfies BarChartSchema; + const codeEditor = { type: 'code-editor' } satisfies CodeEditorSchema; + const detail = { type: 'detail' } satisfies DetailSchema; + const report = { type: 'report' } satisfies ReportComponentSchema; + const listView = { type: 'list-view', objectName: 'account' } satisfies ListViewSchema; + const pageDesigner = { + type: 'page-designer', canvas: { width: 800, height: 600 }, components: [], + } satisfies PageDesignerSchema; + const dataModelDesigner = { + type: 'data-model-designer', entities: [], relationships: [], + } satisfies DataModelDesignerSchema; + const processDesigner = { + type: 'process-designer', processName: 'approval', nodes: [], edges: [], + } satisfies ProcessDesignerSchema; + const reportDesigner = { + type: 'report-designer', reportName: 'pipeline', objectName: 'account', sections: [], + } satisfies ReportDesignerSchema; + const objectManager = { type: 'object-manager', objects: [] } satisfies ObjectManagerSchema; + const fieldDesigner = { type: 'field-designer', objectName: 'account', fields: [] } satisfies FieldDesignerSchema; + + it('the `@ts-expect-error` lines in this block are the assertion; vitest only proves they are reachable', () => { + // @ts-expect-error objectui#9256 — `markdown` reads neither channel + const markdownBody: MarkdownSchema = { ...markdown, body: CONTENT }; + // @ts-expect-error objectui#9256 — `markdown` reads neither channel + const markdownChildren: MarkdownSchema = { ...markdown, children: CONTENT }; + // @ts-expect-error objectui#9256 — `chart` reads neither channel + const chartBody: ChartSchema = { ...chart, body: CONTENT }; + // @ts-expect-error objectui#9256 — `chart` reads neither channel + const chartChildren: ChartSchema = { ...chart, children: CONTENT }; + // @ts-expect-error objectui#9256 — `bar-chart` reads neither channel + const barChartBody: BarChartSchema = { ...barChart, body: CONTENT }; + // @ts-expect-error objectui#9256 — `bar-chart` reads neither channel + const barChartChildren: BarChartSchema = { ...barChart, children: CONTENT }; + // @ts-expect-error objectui#9256 — `code-editor` reads neither channel + const codeEditorBody: CodeEditorSchema = { ...codeEditor, body: CONTENT }; + // @ts-expect-error objectui#9256 — `code-editor` reads neither channel + const codeEditorChildren: CodeEditorSchema = { ...codeEditor, children: CONTENT }; + // @ts-expect-error objectui#9256 — `detail` reads neither channel + const detailBody: DetailSchema = { ...detail, body: CONTENT }; + // @ts-expect-error objectui#9256 — `detail` reads neither channel + const detailChildren: DetailSchema = { ...detail, children: CONTENT }; + // @ts-expect-error objectui#9256 — `report` reads neither channel + const reportBody: ReportComponentSchema = { ...report, body: CONTENT }; + // @ts-expect-error objectui#9256 — `report` reads neither channel + const reportChildren: ReportComponentSchema = { ...report, children: CONTENT }; + // @ts-expect-error objectui#9256 — `list-view` reads neither channel (this face is `z.input` of the mirror) + const listViewBody: ListViewSchema = { ...listView, body: CONTENT }; + // @ts-expect-error objectui#9256 — `list-view` reads neither channel (this face is `z.input` of the mirror) + const listViewChildren: ListViewSchema = { ...listView, children: CONTENT }; + // @ts-expect-error objectui#9256 — `page-designer` reads neither channel (no zod mirror; this face is the only gate) + const pageDesignerBody: PageDesignerSchema = { ...pageDesigner, body: CONTENT }; + // @ts-expect-error objectui#9256 — `page-designer` reads neither channel (no zod mirror; this face is the only gate) + const pageDesignerChildren: PageDesignerSchema = { ...pageDesigner, children: CONTENT }; + // @ts-expect-error objectui#9256 — `data-model-designer` reads neither channel + const dataModelDesignerBody: DataModelDesignerSchema = { ...dataModelDesigner, body: CONTENT }; + // @ts-expect-error objectui#9256 — `data-model-designer` reads neither channel + const dataModelDesignerChildren: DataModelDesignerSchema = { ...dataModelDesigner, children: CONTENT }; + // @ts-expect-error objectui#9256 — `process-designer` reads neither channel + const processDesignerBody: ProcessDesignerSchema = { ...processDesigner, body: CONTENT }; + // @ts-expect-error objectui#9256 — `process-designer` reads neither channel + const processDesignerChildren: ProcessDesignerSchema = { ...processDesigner, children: CONTENT }; + // @ts-expect-error objectui#9256 — `report-designer` reads neither channel + const reportDesignerBody: ReportDesignerSchema = { ...reportDesigner, body: CONTENT }; + // @ts-expect-error objectui#9256 — `report-designer` reads neither channel + const reportDesignerChildren: ReportDesignerSchema = { ...reportDesigner, children: CONTENT }; + // @ts-expect-error objectui#9256 — `object-manager` reads neither channel + const objectManagerBody: ObjectManagerSchema = { ...objectManager, body: CONTENT }; + // @ts-expect-error objectui#9256 — `object-manager` reads neither channel + const objectManagerChildren: ObjectManagerSchema = { ...objectManager, children: CONTENT }; + // @ts-expect-error objectui#9256 — `field-designer` reads neither channel + const fieldDesignerBody: FieldDesignerSchema = { ...fieldDesigner, body: CONTENT }; + // @ts-expect-error objectui#9256 — `field-designer` reads neither channel + const fieldDesignerChildren: FieldDesignerSchema = { ...fieldDesigner, children: CONTENT }; + + expect([ + markdownBody, markdownChildren, chartBody, chartChildren, barChartBody, barChartChildren, + codeEditorBody, codeEditorChildren, detailBody, detailChildren, reportBody, reportChildren, + listViewBody, listViewChildren, pageDesignerBody, pageDesignerChildren, + dataModelDesignerBody, dataModelDesignerChildren, processDesignerBody, processDesignerChildren, + reportDesignerBody, reportDesignerChildren, objectManagerBody, objectManagerChildren, + fieldDesignerBody, fieldDesignerChildren, + ]).toHaveLength(26); + }); + + it('CONTROL — the same nodes WITHOUT a content channel compile (no `@ts-expect-error` here, and `tsc` is the reader)', () => { + const ok = [ + markdown, chart, barChart, codeEditor, detail, report, listView, + pageDesigner, dataModelDesigner, processDesigner, reportDesigner, objectManager, fieldDesigner, + ]; + expect(ok).toHaveLength(13); + }); +}); diff --git a/packages/types/src/crud.ts b/packages/types/src/crud.ts index e8c11d9c76..a43874558d 100644 --- a/packages/types/src/crud.ts +++ b/packages/types/src/crud.ts @@ -364,6 +364,40 @@ export interface DetailSchema extends BaseSchema { * @default false */ loading?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `detail` reads NEITHER + * content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `detail` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `detail` reads NEITHER + * content channel, so an authored child list here rendered NOTHING: no + * render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The bare + * `detail` key is owned by `view:detail` (`@object-ui/plugin-detail`), + * which hands the node to `DetailView`; `DetailView` reads it as + * `DetailViewSchema`, and no `body` or `children` read is filed under that + * type. `SchemaRenderer` strips `body` and `children` out of the props bag + * it spreads, so neither reaches the component by another route, and the + * registration declares no `children` slot (objectui#9910). + * + * What it renders instead: the record named by `objectName` and + * `resourceId`, laid out by `DetailView` from `fields`, `sections` and + * `tabs`. + * + * @deprecated Not a channel `detail` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/data-display.ts b/packages/types/src/data-display.ts index 881682acb0..17a2b99d7d 100644 --- a/packages/types/src/data-display.ts +++ b/packages/types/src/data-display.ts @@ -1759,6 +1759,38 @@ export interface MarkdownSchema extends BaseSchema { * @deprecated Not part of `MarkdownSchema`'s contract — the value was inert. */ components?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `markdown` reads NEITHER + * content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `markdown` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `markdown` reads NEITHER + * content channel, so an authored child list here rendered NOTHING: no + * render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `markdown` registration (`@object-ui/plugin-markdown`) reads the node + * through an inline props type and forwards only `content` and `className` + * to the Markdown implementation; nothing on that path reads the node's + * `body` or `children`. `SchemaRenderer` strips `body` and `children` out + * of the props bag it spreads, so neither reaches the component by another + * route, and the registration declares no `children` slot (objectui#9910). + * + * What it renders instead: `content`, rendered as sanitized Markdown. + * + * @deprecated Not a channel `markdown` reads — nothing renders it. + */ + children?: never; } /** @@ -2322,6 +2354,39 @@ export interface ChartSchema extends BaseSchema { * segment opens a filtered list view (drawer/dialog). */ drillDown?: DrillDownConfig; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `chart` reads NEITHER content + * channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `chart` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `chart` reads NEITHER content + * channel, so an authored child list here rendered NOTHING: no render-time + * error or warning and no element; only the parser tier's `not-a-container` + * warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The `chart` + * registration (`@object-ui/plugin-charts`, and its `chart:bar` alias) + * reads the node through `ChartRenderer`'s inline props type; nothing on + * that path reads the node's `body` or `children`. `SchemaRenderer` strips + * `body` and `children` out of the props bag it spreads, so neither reaches + * the component by another route, and the registration declares no + * `children` slot (objectui#9910). + * + * What it renders instead: a chart of the `data` rows, drawn by `chartType` + * and `series`. + * + * @deprecated Not a channel `chart` reads — nothing renders it. + */ + children?: never; } /** @@ -3288,6 +3353,39 @@ export interface BarChartSchema extends BaseSchema { * @default '#8884d8' */ color?: string; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `bar-chart` reads NEITHER + * content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `bar-chart` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `bar-chart` reads NEITHER + * content channel, so an authored child list here rendered NOTHING: no + * render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `bar-chart` registration (`@object-ui/plugin-charts`) reads the node + * through `ChartBarRenderer`'s inline props type, which forwards `data`, + * `dataKey`, `xAxisKey`, `height`, `className` and `color` and nothing + * else. `SchemaRenderer` strips `body` and `children` out of the props bag + * it spreads, so neither reaches the component by another route, and the + * registration declares no `children` slot (objectui#9910). + * + * What it renders instead: one bar per `data` row, its value from `dataKey` + * and its category from `xAxisKey`. + * + * @deprecated Not a channel `bar-chart` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/designer.ts b/packages/types/src/designer.ts index 39f9bc3d5b..1e5b47f192 100644 --- a/packages/types/src/designer.ts +++ b/packages/types/src/designer.ts @@ -95,6 +95,42 @@ export interface PageDesignerSchema extends BaseSchema { undoRedo?: boolean; /** Read-only mode */ readOnly?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `page-designer` reads NEITHER + * content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `page-designer` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `page-designer` reads NEITHER + * content channel, so an authored child list here rendered NOTHING: no + * render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `page-designer` registration (`@object-ui/plugin-designer`) takes no + * `schema` prop: `SchemaRenderer` spreads the node's other keys into + * `PageDesigner` as props, and `PageDesigner` reads no `children` prop of + * its own. `SchemaRenderer` strips `body` and `children` out of the props + * bag it spreads, so neither reaches the component by another route, and + * the registration declares no `children` slot (objectui#9910). + * + * This declaration has no zod mirror, so this face is the only gate. + * + * What it renders instead: the designer UI `PageDesigner` draws from its + * props; the registration declares `canvas`, `components`, + * `showComponentTree`, `undoRedo` and `readOnly` as its inputs. + * + * @deprecated Not a channel `page-designer` reads — nothing renders it. + */ + children?: never; } /** Component palette category */ @@ -204,6 +240,44 @@ export interface DataModelDesignerSchema extends BaseSchema { autoLayout?: boolean; /** Read-only mode */ readOnly?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `data-model-designer` reads + * NEITHER content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `data-model-designer` reads — nothing renders + * it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `data-model-designer` reads + * NEITHER content channel, so an authored child list here rendered NOTHING: + * no render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `data-model-designer` registration (`@object-ui/plugin-designer`) takes + * no `schema` prop: `SchemaRenderer` spreads the node's other keys into + * `DataModelDesigner` as props, and `DataModelDesigner` reads no `children` + * prop of its own. `SchemaRenderer` strips `body` and `children` out of the + * props bag it spreads, so neither reaches the component by another route, + * and the registration declares no `children` slot (objectui#9910). + * + * This declaration has no zod mirror, so this face is the only gate. + * + * What it renders instead: the designer UI `DataModelDesigner` draws from + * its props; the registration declares `entities`, `relationships`, + * `autoLayout` and `readOnly` as its inputs. + * + * @deprecated Not a channel `data-model-designer` reads — nothing renders + * it. + */ + children?: never; } // ============================================================================ @@ -312,6 +386,42 @@ export interface ProcessDesignerSchema extends BaseSchema { showToolbar?: boolean; /** Read-only mode */ readOnly?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `process-designer` reads + * NEITHER content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `process-designer` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `process-designer` reads + * NEITHER content channel, so an authored child list here rendered NOTHING: + * no render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `process-designer` registration (`@object-ui/plugin-designer`) takes no + * `schema` prop: `SchemaRenderer` spreads the node's other keys into + * `ProcessDesigner` as props, and `ProcessDesigner` reads no `children` + * prop of its own. `SchemaRenderer` strips `body` and `children` out of the + * props bag it spreads, so neither reaches the component by another route, + * and the registration declares no `children` slot (objectui#9910). + * + * This declaration has no zod mirror, so this face is the only gate. + * + * What it renders instead: the designer UI `ProcessDesigner` draws from its + * props; the registration declares `processName`, `nodes`, `edges`, + * `showMinimap`, `showToolbar` and `readOnly` as its inputs. + * + * @deprecated Not a channel `process-designer` reads — nothing renders it. + */ + children?: never; } // ============================================================================ @@ -391,6 +501,42 @@ export interface ReportDesignerSchema extends BaseSchema { previewMode?: boolean; /** Read-only mode */ readOnly?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `report-designer` reads + * NEITHER content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `report-designer` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `report-designer` reads + * NEITHER content channel, so an authored child list here rendered NOTHING: + * no render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `report-designer` registration (`@object-ui/plugin-designer`) takes no + * `schema` prop: `SchemaRenderer` spreads the node's other keys into + * `ReportDesigner` as props, and `ReportDesigner` reads no `children` prop + * of its own. `SchemaRenderer` strips `body` and `children` out of the + * props bag it spreads, so neither reaches the component by another route, + * and the registration declares no `children` slot (objectui#9910). + * + * This declaration has no zod mirror, so this face is the only gate. + * + * What it renders instead: the designer UI `ReportDesigner` draws from its + * props; the registration declares `reportName`, `objectName`, `sections`, + * `showToolbar`, `showPropertyPanel` and `readOnly` as its inputs. + * + * @deprecated Not a channel `report-designer` reads — nothing renders it. + */ + children?: never; } // ============================================================================ @@ -715,6 +861,42 @@ export interface ObjectManagerSchema extends BaseSchema { readOnly?: boolean; /** Show system objects */ showSystemObjects?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `object-manager` reads + * NEITHER content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `object-manager` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `object-manager` reads + * NEITHER content channel, so an authored child list here rendered NOTHING: + * no render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `object-manager` registration (`@object-ui/plugin-designer`) takes no + * `schema` prop: `SchemaRenderer` spreads the node's other keys into + * `ObjectManager` as props, and `ObjectManager` reads no `children` prop of + * its own. `SchemaRenderer` strips `body` and `children` out of the props + * bag it spreads, so neither reaches the component by another route, and + * the registration declares no `children` slot (objectui#9910). + * + * This declaration has no zod mirror, so this face is the only gate. + * + * What it renders instead: the designer UI `ObjectManager` draws from its + * props; the registration declares `objects`, `showSystemObjects` and + * `readOnly` as its inputs. + * + * @deprecated Not a channel `object-manager` reads — nothing renders it. + */ + children?: never; } // ============================================================================ @@ -887,6 +1069,42 @@ export interface FieldDesignerSchema extends BaseSchema { fields: DesignerFieldDefinition[]; /** Read-only mode */ readOnly?: boolean; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `field-designer` reads + * NEITHER content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `field-designer` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `field-designer` reads + * NEITHER content channel, so an authored child list here rendered NOTHING: + * no render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `field-designer` registration (`@object-ui/plugin-designer`) takes no + * `schema` prop: `SchemaRenderer` spreads the node's other keys into + * `FieldDesigner` as props, and `FieldDesigner` reads no `children` prop of + * its own. `SchemaRenderer` strips `body` and `children` out of the props + * bag it spreads, so neither reaches the component by another route, and + * the registration declares no `children` slot (objectui#9910). + * + * This declaration has no zod mirror, so this face is the only gate. + * + * What it renders instead: the designer UI `FieldDesigner` draws from its + * props; the registration declares `objectName`, `fields` and `readOnly` as + * its inputs. + * + * @deprecated Not a channel `field-designer` reads — nothing renders it. + */ + children?: never; } // ============================================================================ diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts index 0123d61988..cd241992cf 100644 --- a/packages/types/src/form.ts +++ b/packages/types/src/form.ts @@ -2555,6 +2555,39 @@ export interface CodeEditorSchema extends BaseSchema { * read by `plugin-editor` (`onChange ?? schema.onChange`). */ onChange?: (value: string | undefined) => void; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `code-editor` reads NEITHER + * content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `code-editor` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `code-editor` reads NEITHER + * content channel, so an authored child list here rendered NOTHING: no + * render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The + * `code-editor` registration (`@object-ui/plugin-editor`) reads the node + * through `CodeEditorRenderer`'s inline props type, which forwards `value`, + * `language`, `theme`, `height`, `readOnly` and `className` to Monaco and + * nothing else. `SchemaRenderer` strips `body` and `children` out of the + * props bag it spreads, so neither reaches the component by another route, + * and the registration declares no `children` slot (objectui#9910). + * + * What it renders instead: a Monaco editor over `value`, in `language` and + * `theme`. + * + * @deprecated Not a channel `code-editor` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/reports.ts b/packages/types/src/reports.ts index 9659cb3bf0..f8a149fd69 100644 --- a/packages/types/src/reports.ts +++ b/packages/types/src/reports.ts @@ -507,6 +507,40 @@ export interface ReportComponentSchema extends BaseSchema { xAxisField?: string; yAxisFields?: string[]; }; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `report` reads NEITHER + * content channel; see `children` below for the measurement. + * + * {@link BaseSchema} already refuses `body` (objectui#6771), but its + * refusal names `children` as the remedy, which this node does not read + * either. The member is restated here so the refusal points at what the + * node renders instead. + * + * @deprecated Not a channel `report` reads — nothing renders it. + */ + body?: never; + /** + * REFUSED BY NAME (objectui#9256, ADR-0049) — `report` reads NEITHER + * content channel, so an authored child list here rendered NOTHING: no + * render-time error or warning and no element; only the parser tier's + * `not-a-container` warning (objectui#9910) noticed it. + * + * Measured with the TypeScript type checker, not grep, over one program per + * workspace package on a built tree (the family-D re-measure). The `report` + * registration (`@object-ui/plugin-report`) dispatches the node through + * `ReportRenderer`, whose three paths read the report definition; no read + * of the node's `body` or `children` exists in that package. + * `SchemaRenderer` strips `body` and `children` out of the props bag it + * spreads, so neither reaches the component by another route, and the + * registration declares no `children` slot (objectui#9910). + * + * What it renders instead: a dataset-bound report (ADR-0021), a stored + * pre-9.0 spec report bridged to `report-viewer`, or the legacy `data` / + * `columns` / `chart` presentation. + * + * @deprecated Not a channel `report` reads — nothing renders it. + */ + children?: never; } /** diff --git a/packages/types/src/zod/crud.zod.ts b/packages/types/src/zod/crud.zod.ts index cf4327b681..05d5fb9899 100644 --- a/packages/types/src/zod/crud.zod.ts +++ b/packages/types/src/zod/crud.zod.ts @@ -20,7 +20,7 @@ import { z } from 'zod'; import { BaseSchema, SchemaNodeSchema } from './base.zod.js'; -import { handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; +import { handlerKeyRefusal, neitherContentChannelGuidance, retirementTombstone } from './tombstone.zod.js'; import type { ActionSchema as ActionDeclaration } from '../crud.js'; /** @@ -160,6 +160,15 @@ const RECORD_ID_IS_A_STRING_GUIDANCE = + 'A backend whose primary keys are numeric converts at its OWN adapter boundary, in one typed ' + 'place, so every author, every caller and every adapter sees one shape.'; +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `DetailSchema`. */ +const DETAIL_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'detail', + 'its registration (`view:detail`, the owner of the bare `detail` key) hands the node to `DetailView`, ' + + 'which reads it as `DetailViewSchema`', + 'the record named by `objectName` and `resourceId`, laid out by `DetailView` from `fields`, `sections` ' + + 'and `tabs`', +); + /** * Detail Schema */ @@ -238,6 +247,10 @@ export const DetailSchema = BaseSchema.extend({ */ onAddComment: handlerKeyRefusal('onAddComment', 'runtime-slot', 'New comment callback'), loading: z.boolean().optional().describe('Whether to show loading state'), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(DETAIL_NEITHER_CHANNEL), + children: retirementTombstone(DETAIL_NEITHER_CHANNEL), }); /** diff --git a/packages/types/src/zod/data-display.zod.ts b/packages/types/src/zod/data-display.zod.ts index 9b2e889b00..5c44db9ce4 100644 --- a/packages/types/src/zod/data-display.zod.ts +++ b/packages/types/src/zod/data-display.zod.ts @@ -506,6 +506,14 @@ export const DataTableSchema = BaseSchema.extend({ ), }); +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `MarkdownSchema`. */ +const MARKDOWN_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'markdown', + 'its registration (`plugin-markdown:markdown`) reads the node through an inline props type and forwards ' + + 'only `content` and `className` to the Markdown implementation', + '`content`, rendered as sanitized Markdown', +); + /** * Markdown Schema - Markdown content renderer * @@ -537,6 +545,10 @@ export const MarkdownSchema = BaseSchema.extend({ + 'and a map of React component overrides is not a JSON-authorable value. Delete the key; the fenced ' + 'mermaid / metadata block overrides are the renderer\'s own fixed map, not an authoring surface.', ), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(MARKDOWN_NEITHER_CHANNEL), + children: retirementTombstone(MARKDOWN_NEITHER_CHANNEL), }); /** @@ -932,6 +944,14 @@ export const DrillDownConfigSchema = z.object({ maxRows: z.number().optional().describe('Hard cap on rows fetched'), }); +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `ChartSchema`. */ +const CHART_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'chart', + 'its registration (`plugin-charts:chart`, and the `chart:bar` alias) reads the node through ' + + '`ChartRenderer`\'s inline props type', + 'a chart of the `data` rows, drawn by `chartType` and `series`', +); + /** * Chart Schema - Chart/graph component * @@ -1026,6 +1046,10 @@ export const ChartSchema = BaseSchema.extend({ animate: z.boolean().optional().describe('Enable animations'), config: z.record(z.string(), z.any()).optional().describe('Additional chart configuration'), drillDown: DrillDownConfigSchema.optional().describe('Drill-down: clicking a chart segment opens a filtered list view (drawer / dialog)'), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(CHART_NEITHER_CHANNEL), + children: retirementTombstone(CHART_NEITHER_CHANNEL), }).overwrite(foldChartXAxisAlias); /** objectui#9256 (family D): ONE refusal string for both content channels of `PivotTableSchema`. */ @@ -1445,6 +1469,14 @@ export const HtmlSchema = BaseSchema.extend({ ), }); +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `BarChartSchema`. */ +const BAR_CHART_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'bar-chart', + 'its registration (`plugin-charts:bar-chart`) reads the node through `ChartBarRenderer`\'s inline props ' + + 'type, which forwards `data`, `dataKey`, `xAxisKey`, `height`, `className` and `color`', + 'one bar per `data` row, its value from `dataKey` and its category from `xAxisKey`', +); + /** * Data Display Schema Union - All data display component schemas */ @@ -1472,6 +1504,10 @@ export const BarChartSchema = BaseSchema.extend({ xAxisKey: z.string().optional().describe('Row key holding the bar category (x axis)'), height: z.number().optional().describe('Chart height in pixels'), color: z.string().optional().describe('Bar fill colour'), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(BAR_CHART_NEITHER_CHANNEL), + children: retirementTombstone(BAR_CHART_NEITHER_CHANNEL), }); export const DataDisplaySchema = z.discriminatedUnion('type', [ diff --git a/packages/types/src/zod/form.zod.ts b/packages/types/src/zod/form.zod.ts index eba1c495f6..0076f1af71 100644 --- a/packages/types/src/zod/form.zod.ts +++ b/packages/types/src/zod/form.zod.ts @@ -1157,6 +1157,14 @@ export const UiCalendarSchema = CalendarObjectSchema.extend({ .describe('The `ui`-namespaced date-picker primitive — `calendar` alone names the plugin-calendar view'), }).superRefine(calendarSelectionFitsMode); +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `CodeEditorSchema`. */ +const CODE_EDITOR_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'code-editor', + 'its registration (`plugin-editor:code-editor`) reads the node through `CodeEditorRenderer`\'s inline ' + + 'props type, which forwards `value`, `language`, `theme`, `height`, `readOnly` and `className` to Monaco', + 'a Monaco editor over `value`, in `language` and `theme`', +); + /** * Form Component Schema Union - All form component schemas */ @@ -1183,6 +1191,10 @@ export const CodeEditorSchema = BaseSchema.extend({ height: z.string().optional().describe('Editor height as a CSS length'), readOnly: z.boolean().optional().describe('Whether the editor refuses edits'), onChange: handlerKeyRefusal('onChange', 'runtime-slot', 'Change handler'), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(CODE_EDITOR_NEITHER_CHANNEL), + children: retirementTombstone(CODE_EDITOR_NEITHER_CHANNEL), }); export const FormComponentSchema = z.discriminatedUnion('type', [ diff --git a/packages/types/src/zod/objectql.zod.ts b/packages/types/src/zod/objectql.zod.ts index b38ed38abc..a8f5673477 100644 --- a/packages/types/src/zod/objectql.zod.ts +++ b/packages/types/src/zod/objectql.zod.ts @@ -1528,6 +1528,14 @@ function checkListViewDatasetChartFilter( } } +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `ListViewSchema`. */ +const LIST_VIEW_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'list-view', + 'its registration (`plugin-list:list-view`) reads the node as `ListViewSchema` itself', + 'the records of `objectName` in the visualization `viewType` names, shaped by `columns`, `filter`, ' + + '`sort` and `options`', +); + export const ListViewSchema = BaseSchema // Spec-owned fields by reference. `specFieldsExcept` reads the spec object's // `.shape` rather than calling `.omit()`, which zod 4 refuses on a schema @@ -1778,6 +1786,13 @@ export const ListViewSchema = BaseSchema onDensityChange: handlerKeyRefusal('onDensityChange', 'runtime-slot', 'Row density change handler'), onNavigate: handlerKeyRefusal('onNavigate', 'runtime-slot', 'Record navigation handler'), onPageSizeChange: handlerKeyRefusal('onPageSizeChange', 'runtime-slot', 'Page size change handler'), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(LIST_VIEW_NEITHER_CHANNEL), + children: retirementTombstone(LIST_VIEW_NEITHER_CHANNEL), + // ⚠️ This arm feeds its own TypeScript face (`ListViewInferred` below), so these two members are + // what put `body?: undefined` / `children?: undefined` on `ListViewSchema`; there is no separate + // `?: never` pair to keep in step. }) // ⭐ THE SPEC'S OBJECT-LEVEL CHECKS, re-attached (objectui#7715, ruling B1). // diff --git a/packages/types/src/zod/reports.zod.ts b/packages/types/src/zod/reports.zod.ts index e83ab17765..4a90b4b23d 100644 --- a/packages/types/src/zod/reports.zod.ts +++ b/packages/types/src/zod/reports.zod.ts @@ -19,7 +19,7 @@ import { z } from 'zod'; import { BaseSchema, SchemaNodeSchema } from './base.zod.js'; import { ChartSchema } from './data-display.zod.js'; -import { handlerKeyRefusal, retirementTombstone } from './tombstone.zod.js'; +import { handlerKeyRefusal, neitherContentChannelGuidance, retirementTombstone } from './tombstone.zod.js'; /** * Report Export Format Schema @@ -136,6 +136,15 @@ export const ReportExportConfigSchema = z.object({ options: z.record(z.string(), z.any()).optional().describe('Custom options'), }); +/** objectui#9256 (family-D re-measure): ONE refusal string for both content channels of `ReportComponentSchema`. */ +const REPORT_NEITHER_CHANNEL = neitherContentChannelGuidance( + 'report', + 'its registration (`plugin-report:report`) dispatches the node through `ReportRenderer`, whose three ' + + 'paths read the report definition and never the node\'s own child list', + 'a dataset-bound report (ADR-0021), a stored pre-9.0 spec report bridged to `report-viewer`, or the ' + + 'legacy `data` / `columns` / `chart` presentation', +); + /** * Report Schema */ @@ -177,6 +186,10 @@ export const ReportComponentSchema = BaseSchema.extend({ refreshInterval: z.number().optional().describe('Auto-refresh interval (in seconds)'), loading: z.boolean().optional().describe('Loading state'), data: z.array(z.any()).optional().describe('Report data'), + // objectui#9256 (family-D re-measure): the renderer reads NEITHER content channel, so both are + // refused by name here as on the TypeScript twin, each kept a MEMBER. + body: retirementTombstone(REPORT_NEITHER_CHANNEL), + children: retirementTombstone(REPORT_NEITHER_CHANNEL), }); /** From 7d451884d910b2d5fa8ceb9a1e6b756863a889c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 13:13:10 +0000 Subject: [PATCH 2/2] chore(changeset): declare the objectui#9256 re-measure narrowing `@object-ui/types` minor with an explicit BREAKING note and a migration line, the spelling the earlier family-D slices used under this repo's no-major rule. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk --- .changeset/9256-remeasure-content-channels.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .changeset/9256-remeasure-content-channels.md diff --git a/.changeset/9256-remeasure-content-channels.md b/.changeset/9256-remeasure-content-channels.md new file mode 100644 index 0000000000..0c6c601c95 --- /dev/null +++ b/.changeset/9256-remeasure-content-channels.md @@ -0,0 +1,44 @@ +--- +'@object-ui/types': minor +--- + +**BREAKING (shipped as `minor` — see below):** thirteen node types now refuse +both content channels by name. Each one's renderer reads neither `body` nor +`children`, so an authored child list on it rendered nothing, with no +render-time error or warning and no element; only the parser tier's +`not-a-container` warning (objectui#9910) noticed it (objectui#9256). + +- `markdown`, `chart`, `bar-chart`, `code-editor`, `detail`, `report`: both keys + are now `?: never` on the TypeScript face (`MarkdownSchema`, `ChartSchema`, + `BarChartSchema`, `CodeEditorSchema`, `DetailSchema`, + `ReportComponentSchema`) and a by-name refusal on the zod mirror, each kept a + member of the mirror shape. +- `list-view`: a by-name refusal on the zod mirror. `ListViewSchema`'s + TypeScript face is derived from that mirror, so it refuses both keys too. +- `page-designer`, `data-model-designer`, `process-designer`, + `report-designer`, `object-manager`, `field-designer`: `?: never` on the + TypeScript face (`PageDesignerSchema`, `DataModelDesignerSchema`, + `ProcessDesignerSchema`, `ReportDesignerSchema`, `ObjectManagerSchema`, + `FieldDesignerSchema`). None of them has a zod mirror, so the TypeScript face + is the only one that changes. + +What moves for an author: + +- `children` on any of these nodes type-checked, and parsed green where a zod + mirror exists; it is now refused at authoring time and, where a zod mirror + exists, at `safeParse` time, at its own path. +- `body` was already refused on these faces, by `BaseSchema`. On the zod + mirrors its refusal message now names what the node renders instead, where + it used to point at `children`, which these nodes do not read either. + +No render behaviour changes: nothing read these keys, which is the whole reason +they could be refused. + +Migration: each of these nodes renders from its own keys, so there is no channel +to move the content to. Put it in the key the node does render (`markdown`'s +`content`, a `detail` tab's `content`, a chart's `data`), place it beside the +node in a container that reads `children`, or drop it. + +`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.