From ac0d3d8a0ac2b630a5051cf7a4bffadc32f926b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 08:37:18 +0000 Subject: [PATCH 1/2] docs(guide,readme): spell the grid child list `children`, the key `grid` reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `grid` renderer draws its child list from `schema.children` and from nothing else, and `GridSchema` declares only `children`. Three teaching surfaces authored that list as `items` anyway — including the flagship example on the npm/GitHub landing page — so a reader who copied any of them got a grid element with no children. Nothing refused the key: `BaseSchema` is `.passthrough()` with an `[key: string]: any` index signature, so `items` validated, type-checked, rode onto the node and drew nothing. Repairs the documentation, not the renderer: coding standard #0.1 (contract-first) forbids the alternatives — teaching `grid` to read `items` as an alias, or declaring `items` on `GridSchema` — as lenient fallbacks that would mint a second child-key dialect on one node. The objectui#8310 render pin transcribed the flagship verbatim, `items` and all, so the defect could not hide inside a green. It now transcribes the repaired flagship, asserts the three `statistic` labels are drawn, keeps the pre-repair spelling as a labelled negative-space leg (grid element drawn, children absent), and gains a README-scan leg so the transcription cannot become fiction. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB --- .changeset/8912-grid-children-items.md | 10 + README.md | 2 +- content/docs/guide/schema-playground.md | 2 +- content/docs/guide/schema-rendering.md | 2 +- .../page-body-single-node-8310.test.tsx | 173 +++++++++++++----- 5 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 .changeset/8912-grid-children-items.md diff --git a/.changeset/8912-grid-children-items.md b/.changeset/8912-grid-children-items.md new file mode 100644 index 0000000000..0dfe25f307 --- /dev/null +++ b/.changeset/8912-grid-children-items.md @@ -0,0 +1,10 @@ +--- +--- + +Repair the three teaching surfaces that authored a `grid` node's child list as +`items` — a key the `grid` renderer never reads, so the root README's flagship +example drew an empty grid (objectui#8912). The root `README.md` "Basic Usage" +fence, `content/docs/guide/schema-rendering.md` and +`content/docs/guide/schema-playground.md` now spell it `children`, the key +`GridSchema` declares and `grid.tsx` reads. Documentation and test only: neither +the renderer nor `GridSchema` moves, and no package is released by this change. diff --git a/README.md b/README.md index d086dd5d0e..1e4b90963a 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ const schema = { body: { type: "grid", columns: 3, - items: [ + children: [ { type: "statistic", label: "Total Users", value: "${stats.users}" }, { type: "statistic", label: "Revenue", value: "${stats.revenue}" }, { type: "statistic", label: "Orders", value: "${stats.orders}" } diff --git a/content/docs/guide/schema-playground.md b/content/docs/guide/schema-playground.md index bcef234383..7c15edac72 100644 --- a/content/docs/guide/schema-playground.md +++ b/content/docs/guide/schema-playground.md @@ -211,7 +211,7 @@ Schemas can be nested to build complex layouts. Here is a dashboard that combine "type": "grid", "columns": 3, "gap": "md", - "items": [ + "children": [ { "type": "card", "title": "Open Issues", diff --git a/content/docs/guide/schema-rendering.md b/content/docs/guide/schema-rendering.md index b3c8366377..b07eaa28f4 100644 --- a/content/docs/guide/schema-rendering.md +++ b/content/docs/guide/schema-rendering.md @@ -128,7 +128,7 @@ Schemas can be nested to create complex UIs: "body": { "type": "grid", "columns": 2, - "items": [ + "children": [ { "type": "card", "title": "Card 1", diff --git a/packages/components/src/__tests__/page-body-single-node-8310.test.tsx b/packages/components/src/__tests__/page-body-single-node-8310.test.tsx index 9d8c4769fd..d5784b0522 100644 --- a/packages/components/src/__tests__/page-body-single-node-8310.test.tsx +++ b/packages/components/src/__tests__/page-body-single-node-8310.test.tsx @@ -8,70 +8,97 @@ /** * `PageRenderer` draws a `body` given as ONE node and a `body` given as a list - * (objectui#8310, maintainer ruling 2026-09-07, director decision batch #2). + * (objectui#8310, maintainer ruling 2026-09-07, director decision batch #2), + * and the root README's flagship example draws its CHILDREN (objectui#8912). * - * ## What this half is, and what it is NOT + * ## Two cards share this file, and they are not the same question * - * ⛔ This file is a **regression control**, not evidence for the card. The - * ruling's repair on this side is the deletion of `FlatContent`'s - * `content as SchemaNode` cast, which is a TYPE-LEVEL change with no runtime - * effect at all: both arities rendered before it and both render after it. An - * assertion green in both worlds proves nothing about a change, and this one is - * labelled so nobody later quotes it as if it did. + * objectui#8310 asked the ARITY of `PageNodeSchema.body` — one node, or a list. + * objectui#8912 asked whether the child key the flagship example AUTHORS has + * any reader at all. This file now pins both, because both are properties of + * the same transcribed snippet, and the second was originally left visible here + * on purpose (see "History" below) rather than repaired. * - * The discriminating half lives in - * `packages/types/src/__tests__/page-body-arity-8310.test.ts`: the declaration - * and the zod mirror both refused the single-node form before this card, and - * both admit it after. + * ## What each leg is, and which one is evidence * - * ## Why the control is worth having anyway + * - **objectui#8310 — a regression CONTROL, not evidence.** The ruling's repair + * on this side is the deletion of `FlatContent`'s `content as SchemaNode` + * cast, a TYPE-LEVEL change with no runtime effect: both arities rendered + * before it and both render after it. An assertion green in both worlds + * proves nothing about a change, and it is labelled so nobody quotes it as if + * it did. The discriminating half lives in + * `packages/types/src/__tests__/page-body-arity-8310.test.ts`. + * - **objectui#8912 — evidence, and it is RENDER OUTPUT.** The three + * `statistic` labels the flagship authors must appear in the rendered tree. + * ⛔ No compile-time check can stand in for this leg: `BaseSchema` is + * `.passthrough()` with an `[key: string]: any` index signature, so the + * defective key validated, type-checked, rode onto the node and drew nothing. + * `check:doc-types` only asks whether a fence's `type` literal is registered + * (`grid` is), and `check:doc-examples` compiles against that same index + * signature. Both are green on the defect BY DESIGN (objectui#4823), so a + * green from either is not a reading of this card. * - * The cast is what let the single-node branch survive a declaration that - * forbade it. Deleting the cast without a behavioural pin would leave that - * branch guarded by nothing but the union it now leans on — and the next - * narrowing of `PageNodeSchema.body` would delete a live code path with a green - * suite. So this asserts against the rendered output rather than against - * `FlatContent`'s internals, and it survives a rewrite of the normalization. + * ## ⚠️ Why the negative-space leg renders the OLD spelling * - * ## ⚠️ Why the grid's own children are spelled `children` here, not `items` + * The `grid` renderer reads `schema.children` and nothing else. The third + * describe block renders the pre-repair shape and asserts the grid element IS + * drawn while its children are NOT — which is precisely why objectui#8912 + * failed silently, and precisely why a naive "did anything render?" control + * passes on a broken build. It is also the floor set by coding standard #0.1 + * (contract-first): making `grid` read `items` as an alias for `children`, or + * declaring `items` on `GridSchema`, were both REFUSED on objectui#8912 as the + * lenient-fallback shape that standard forbids by name. Either one would redden + * this block. ⛔ Do not "fix" it by teaching the renderer the second spelling. * - * The root `README.md` flagship example spells the grid's child list `items`, - * and `grid` reads `children` and nothing else — so that example renders an - * EMPTY grid element. That is a real defect, measured while writing this file - * and filed as objectui#8912; it is a DIFFERENT defect from this card (a child - * key with no reader, versus the arity of `body`) and the ruling here forbids - * touching the README. This file therefore asserts the two facts separately: - * the flagship shape verbatim reaches the renderer and draws its `grid` node - * (which is what `body`'s arity governs), and a `children`-spelled subtree - * draws all the way down through either arity (which is what proves the whole - * channel works). ⛔ Do not "repair" this file by moving the flagship - * transcription to `children` — that would hide objectui#8912 inside a green. + * ## History — why this file once transcribed the defect on purpose + * + * Written for objectui#8310, when the README's flagship grid still spelled its + * child list `items`. That defect was measured while writing this file and + * filed as objectui#8912; the objectui#8310 ruling forbade touching the README, + * so the transcription deliberately preserved `items` so the defect could not + * hide inside a green. objectui#8912 has since repaired all three teaching + * surfaces to `children`, so the transcription now follows the repaired + * flagship — and the README-scan leg below keeps it from becoming fiction. + * ⛔ Do not delete or weaken these legs to make an unrelated change pass. */ import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/react'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; import { SchemaRenderer } from '@object-ui/react'; // Module scope, not a hook: registers `page`, `grid` and `statistic`. A cold // `await import()` inside a hook is billed to `hookTimeout` and races the // assertions (AGENTS.md §测试纪律, objectui#3010). import '../renderers'; -/** The README's flagship grid, verbatim — child list spelled `items` (objectui#8912). */ -const flagshipGridAsAuthored = { +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(HERE, '..', '..', '..', '..'); +const README = readFileSync(join(REPO_ROOT, 'README.md'), 'utf8'); + +/** The three `statistic` labels the README's flagship example authors. */ +const LABELS = ['Total Users', 'Revenue', 'Orders']; + +/** The README's flagship grid, transcribed as REPAIRED — child list `children`. */ +const flagshipGrid = { type: 'grid', columns: 3, - items: [ + children: [ { type: 'statistic', label: 'Total Users', value: '1234' }, { type: 'statistic', label: 'Revenue', value: '$56,789' }, { type: 'statistic', label: 'Orders', value: '432' }, ], }; -/** The same grid with its child list under the key `grid` actually reads. */ -const flagshipGridAsRead = { +/** + * The same grid spelled the way it was BEFORE objectui#8912 — `items`, a key + * `grid` never reads. Kept as the defect shape, never as a taught shape. + */ +const flagshipGridPreRepair = { type: 'grid', columns: 3, - children: flagshipGridAsAuthored.items, + items: flagshipGrid.children, }; function renderPage(body: unknown) { @@ -81,13 +108,13 @@ function renderPage(body: unknown) { const gridsIn = (container: HTMLElement) => container.querySelectorAll('[data-obj-type="grid"]'); describe('PageRenderer — both `body` arities reach the renderer (objectui#8310, CONTROL)', () => { - it('draws the node when `body` is ONE node — the README flagship shape, verbatim', () => { - const { container } = renderPage(flagshipGridAsAuthored); + it('draws the node when `body` is ONE node — the README flagship shape', () => { + const { container } = renderPage(flagshipGrid); expect(gridsIn(container)).toHaveLength(1); }); it('draws the node when `body` is a LIST of nodes', () => { - const { container } = renderPage([flagshipGridAsAuthored]); + const { container } = renderPage([flagshipGrid]); expect(gridsIn(container)).toHaveLength(1); }); @@ -97,16 +124,68 @@ describe('PageRenderer — both `body` arities reach the renderer (objectui#8310 }); }); -describe('PageRenderer — the whole subtree draws through either arity (objectui#8310)', () => { - const labels = ['Total Users', 'Revenue', 'Orders']; - +describe('PageRenderer — the flagship example draws its CHILDREN (objectui#8912)', () => { it('renders every child of a single-node `body`', () => { - const { container } = renderPage(flagshipGridAsRead); - for (const label of labels) expect(container.textContent).toContain(label); + const { container } = renderPage(flagshipGrid); + for (const label of LABELS) expect(container.textContent).toContain(label); }); it('renders every child of a list `body`', () => { - const { container } = renderPage([flagshipGridAsRead]); - for (const label of labels) expect(container.textContent).toContain(label); + const { container } = renderPage([flagshipGrid]); + for (const label of LABELS) expect(container.textContent).toContain(label); + }); +}); + +describe('`grid` reads `children` and nothing else — the defect shape (objectui#8912)', () => { + it('draws the grid ELEMENT for the pre-repair `items` spelling — the firing control', () => { + const { container } = renderPage(flagshipGridPreRepair); + expect(gridsIn(container)).toHaveLength(1); + }); + + it('draws NONE of its children — the silent failure objectui#8912 repaired', () => { + const { container } = renderPage(flagshipGridPreRepair); + for (const label of LABELS) expect(container.textContent).not.toContain(label); + }); +}); + +/* -------------------------------------------------------------------------- */ +/* README leg — the transcription above is not allowed to become fiction */ +/* -------------------------------------------------------------------------- */ + +/** + * The `const schema = {` object literal inside the README's "Basic Usage" + * fence, returned as source text. Scanned with brace-depth tracking rather than + * a regex so a nested array cannot end the span early. + */ +function basicUsageSchemaLiteral(): string { + const heading = README.indexOf('#### Basic Usage'); + expect(heading).toBeGreaterThan(-1); + const start = README.indexOf('const schema = {', heading); + expect(start).toBeGreaterThan(-1); + + let depth = 0; + for (let i = README.indexOf('{', start); i < README.length; i += 1) { + const ch = README[i]; + if (ch === '{') depth += 1; + else if (ch === '}') { + depth -= 1; + if (depth === 0) return README.slice(start, i + 1); + } + } + throw new Error('unbalanced braces in the README "Basic Usage" schema literal'); +} + +describe('the root README authors the grid child list `grid` actually reads (objectui#8912)', () => { + it('spells the flagship grid child list `children`, never `items`', () => { + const literal = basicUsageSchemaLiteral(); + expect(literal).toContain('children: ['); + expect(literal).not.toMatch(/(^|[^.\w])items\s*:/); + }); + + it('still authors the page/grid/statistic shape transcribed above', () => { + const literal = basicUsageSchemaLiteral(); + expect(literal).toContain('type: "grid"'); + expect(literal.match(/type: "statistic"/g) ?? []).toHaveLength(3); + for (const label of LABELS) expect(literal).toContain(`label: "${label}"`); }); }); From e1bf5c9e798536d3fcd1c939f6f96bb025d12854 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 09:06:38 +0000 Subject: [PATCH 2/2] docs(types): the 8310 arity pin follows the repaired flagship MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The objectui#8310 arity pin transcribed the README's flagship grid with its child list spelled `items`, and said so in prose: a header paragraph describing that spelling as a live, unrepaired defect, a docblock calling the literal "the `grid` node the README's flagship example hands to `body`", and a brace-scanner comment naming a nested `items[]`. Repairing the three teaching surfaces made all four statements false, and nothing went red — `BaseSchema` carries `[key: string]: any`, and this file's subject is `body` arity, not the child key. Stale-truth repair only. Every one of the file's 28 assertion lines is byte-identical before and after; outside comments the diff is the single key. What the pin asserts, and the arity ruling it enforces, are untouched. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB --- .../__tests__/page-body-arity-8310.test.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/types/src/__tests__/page-body-arity-8310.test.ts b/packages/types/src/__tests__/page-body-arity-8310.test.ts index 5f8c6e1623..4851e5c904 100644 --- a/packages/types/src/__tests__/page-body-arity-8310.test.ts +++ b/packages/types/src/__tests__/page-body-arity-8310.test.ts @@ -60,11 +60,14 @@ * arity of one declared key; it does not turn `PageNodeSchema` into a closed * surface, and nothing here should be quoted as if it did. * - * ⚠️ A second, unrelated defect in the same fence, measured while writing this - * file and filed as objectui#8912: the flagship grid spells its child list - * `items`, a key `grid` never reads, so that example draws an EMPTY grid. It is - * transcribed here verbatim — `items` and all — because this file asks about - * `body`'s ARITY and must not quietly repair a defect it does not own. + * ⚠️ A second, unrelated defect in the same fence was measured while writing + * this file and filed as objectui#8912: the flagship grid SPELLED its child + * list `items`, a key `grid` never reads, so that example drew an EMPTY grid. + * That card has since repaired all three teaching surfaces to `children`, and + * the transcription below follows the repaired flagship. This file still asks + * only about `body`'s ARITY and rules on nothing else; the child key is pinned + * by the render pin in + * `packages/components/src/__tests__/page-body-single-node-8310.test.tsx`. */ import { describe, it, expect } from 'vitest'; @@ -85,11 +88,15 @@ const README = readFileSync(join(REPO_ROOT, 'README.md'), 'utf8'); /* Leg 1 — compile-time, compiled by `tsc -p packages/types/tsconfig.test.json` */ /* -------------------------------------------------------------------------- */ -/** The `grid` node the README's flagship example hands to `body`. */ +/** + * The `grid` node the README's flagship example hands to `body` — child list + * spelled `children`, the key `GridSchema` declares and `grid` reads + * (objectui#8912). + */ const flagshipGrid: SchemaNode = { type: 'grid', columns: 3, - items: [ + children: [ { type: 'statistic', label: 'Total Users', value: '${stats.users}' }, { type: 'statistic', label: 'Revenue', value: '${stats.revenue}' }, { type: 'statistic', label: 'Orders', value: '${stats.orders}' }, @@ -221,7 +228,7 @@ describe('PageNodeSchema.body still refuses a non-node — the widening kept its /** * The `const schema = {` object literal inside the README's "Basic Usage" * fence, returned as source text. Scanned with brace-depth tracking rather - * than a regex so a nested `items[]` cannot end the span early. + * than a regex so a nested `children[]` cannot end the span early. */ function basicUsageSchemaLiteral(): string { const heading = README.indexOf('#### Basic Usage');