Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/8912-grid-children-items.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}" }
Expand Down
2 changes: 1 addition & 1 deletion content/docs/guide/schema-playground.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion content/docs/guide/schema-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Schemas can be nested to create complex UIs:
"body": {
"type": "grid",
"columns": 2,
"items": [
"children": [
{
"type": "card",
"title": "Card 1",
Expand Down
173 changes: 126 additions & 47 deletions packages/components/src/__tests__/page-body-single-node-8310.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
});

Expand All @@ -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}"`);
});
});
23 changes: 15 additions & 8 deletions packages/types/src/__tests__/page-body-arity-8310.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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}' },
Expand Down Expand Up @@ -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');
Expand Down
Loading