Repository navigation
fix(types): content-channel refusals name the parser tier's not-a-container warning (objectui#10928) - #10956
Conversation
…tainer warning (objectui#10928) The family-D / E3 content-channel tombstone messages, their `.describe()` text, the two builders that compose them (`neitherContentChannelGuidance` and ai.zod.ts's `neitherChannel`) and the matching TypeScript docblocks said an authored value rendered NOTHING with "no error, no warning, no element". The render path is right, but the parser tier's `validateTree` warns `not-a-container` for a child list in either key on a registration that declares no `children` input. Only that clause changes, to the wording the objectui#10907 changesets settled on; every other byte of each string stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…ectui#10928) The shared builder, one arm per message source (a literal, the shared `neitherContentChannelGuidance`, ai.zod.ts's `neitherChannel`) read through its refusal, and the population of family-D refusals all carry the clause that names the parser tier's `not-a-container` warning. `@object-ui/types` patch changeset for the published message text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs read: card #10928 (body and all three comments: triage ① Derived judgmentsAccept set: no change. Public surface: wording only. Each item named, right or wrong:
② Semver level
③ Boundary flagsDeviations (6), each answered:
Open question: add a live-registry behavioural pin in Out-of-scope findings (2), filed as one card objectui#10959. Verified: open, label Check-runs on the head, read at 2026-09-28T11:04Z right before this record was written: 43 total, all completed; 40 success, 3 skipped by design (the two coverage lanes and dependabot), 0 failures, 0 in progress. Type Check, all eight test shards and their Implemented-by: VERDICT: PASS |
… object-pivot is where a pivot drills (objectui#10932) (objectstack-ai#10972) Fixes objectstack-ai#10932 Clause-②: no — a retirement narrows the accept set; it widens nothing and adds no public surface. `drillDown` on the bare `pivot` node validated and did nothing. This retires it on both published faces, with a tombstone that names `object-pivot` as the remedy. That is option (b) of the card, as graded in triage comment `5868092364`. `object-pivot`'s own `drillDown` (`ObjectPivotDrillDownConfig`) is untouched and still drills. Implemented by the `domain:spec` dispatched dev, session `https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm`, on the PM claim `5868869683`. Base `95a7c8d38`. ## Premise gate (measured before any edit) The grade's premise was: no shipped or example document authors `drillDown` on a `pivot` node (as opposed to `object-pivot`), and a static-data drill has no meaning. **It holds.** - **objectui**, tree at base `95a7c8d38` (clean worktree; enumeration `git ls-files`, reads from the same checkout). A structural classifier looks up the `type` of the object that encloses every `drillDown` key, and of the parent widget when that object is an `options` bag. It also lists every `type: 'pivot'` / `'object-pivot'` object. It scanned 9,125 files (json/ts/tsx/js/md/mdx/yaml) and found 88 `drillDown` key sites. - Enclosing type `pivot`: 2 sites, both test fixtures. `drillRefusal-10789.test.tsx` renders `ObjectPivotTable`, so it is an `object-pivot` drill. `registered-type-arms-10859-b2.test.ts` is the objectui#10859 accept pin, which this PR updates. These are the matcher's positive control. - Non-test `type: 'pivot'` objects: the `DashboardGridLayout` static-data branch, `plugin-dashboard/SKILL.md`, a CHANGELOG entry, a code comment, and the declaration itself. None carries `drillDown`. - `examples/**` (504 files, 473 of them in `examples/schema-catalog`) has zero `pivot` or `drillDown` mentions. The control is that the same query finds `"type"` there. - `apps/**`, `content/docs/**`, every package README and `skills/**` hold no `pivot` node document at all. The only hits are type-name tables and prose. - **objectstack**, at `origin/main` `dbddf02c1`, fetched into a named ref. Enumeration and reads both come from that ref (`git grep` / `git show`). The local checkout was 15 commits behind, so it was not read. - 7 pivot-typed objects: 6 `pivot` and 1 `object-pivot` (in `sdui.manifest.json`). None carries `drillDown`. - The one shipped pivot widget, `pivot_tasks` in the app-showcase `chart-gallery` dashboard, is dataset-bound. It renders through `DatasetWidget`, not the `pivot` node. - 9 `drillDown` key sites, none on a pivot. All are spec pins: `bar`, `summary`, and the dashboard-widget refusal ("Drill-through on a dashboard is AUTOMATIC"). - **`DashboardGridLayout`'s static-data pivot** emits `{ type: 'pivot', ...options, data }`. It never writes a `drillDown` of its own; only its table branch defaults one. A pivot widget's `options.drillDown` would pass through, and none is authored anywhere above. - **Static-data drill meaning.** The only drill implementation is `ObjectPivotTable`'s `DrillDownDrawer`, and its `renderDrillDrawer` returns nothing without `schema.objectName`. A `pivot` node declares no object, so a drill has nothing to list. - **Runtime.** `onDrillDown` has exactly one producer, `ObjectPivotTable`. The `pivot` registration is bare `PivotTable`, and console's lazy stub loads that same registration. The inertness was also read at runtime, through the real `SchemaRenderer` and registry (the new plugin-dashboard probe): a `pivot` node carrying `drillDown: { enabled: true }` renders the cross-tab with zero `role=button` elements. ## What changed - `@object-ui/types` - `PivotTableSchema.drillDown` is a `?: never` tombstone. Its docblock follows the `DataTableSchema.toolbar` convention and points at `object-pivot` / `ObjectPivotDrillDownConfig`. - The zod `pivot` arm's `drillDown` is `retirementTombstone(PIVOT_DRILL_DOWN_RETIRED)`. The one string feeds both the parse-time message and `.describe()`, and it names `object-pivot`. - `PIVOT_NEITHER_CHANNEL`, the `body` / `children` refusal, no longer lists `drillDown` among what a `pivot` renders. - Three docblocks that described the old accept are updated: `DrillDownConfigSchema`, the zod `PivotTableSchema`, and `ObjectPivotDrillDownConfig`. - `@object-ui/plugin-dashboard` - `PivotTable` reads nothing off `schema` for its drill. The host's `onDrillDown` is now the only switch. - `ObjectPivotTable` types its `schema` as `PivotTableSchema` minus `drillDown` (key remapping, not `Omit`: `BaseSchema`'s index signature makes `Omit` drop every declared member), intersected with its own `drillDown?: ObjectPivotDrillDownConfig`. It strips that key before handing the node to `PivotTable`. - `.changeset/10932-pivot-drilldown-retired.md`: `@object-ui/types` and `@object-ui/plugin-dashboard`, both `minor`, with the break stated. The changeset names the three pending entries whose sentences this supersedes (see Acceptance notes). ### Decision the dispatch asked for: the component's drill switch `PivotTable` had no `drillDown` React prop. It read `schema.drillDown` and gated it with `isDrillEnabled`. With the node key tombstoned, it had to stop reading it, and there were two routes: - **(i) Move the config to a new `drillDown` React prop. Not taken.** `SchemaRenderer` spreads a node's keys as React props, so a prop of that name would hand an authored `drillDown` on a `pivot` node straight back to the component. That reopens the channel the tombstone closes. - **(ii) Make the host's handler the only switch. Taken.** The only thing `PivotTable` took from the config was the on/off bit, and its one drilling host already decides that before it passes `onDrillDown`. Behaviour of `object-pivot` is unchanged. For a React caller the published behaviour changes: a handler now turns the drill on by itself, and a `schema.drillDown` no longer gates it (the type refuses that key anyway). The changeset states this. ### Claimed file surface: exceeded, on purpose The claim listed `packages/types/src/data-display.ts`, `packages/types/src/zod/data-display.zod.ts`, pins under `packages/types/src/__tests__/` and one changeset. This PR also edits `packages/plugin-dashboard/src/PivotTable.tsx`, `packages/plugin-dashboard/src/ObjectPivotTable.tsx` and two plugin-dashboard tests. That is forced by the ruled TS tombstone: reverse verification 5 below shows `ObjectPivotTable` stops compiling without the change. The dispatch's mechanism assumption 3 also asked for this decision. ## Tests All runs are from head `fd2befe42`. - `pnpm --filter @object-ui/types type-check` (all three legs): exit 0. - `pnpm exec vitest run packages/types/`: 269 files, 5,963 tests passed. - `pnpm --filter @object-ui/plugin-dashboard type-check` (both legs): exit 0. `tsc -p tsconfig.test.json --listFiles` includes `PivotTable.drill.test.tsx`, `pivotNode.drillDownRetired-10932.test.tsx` and `ObjectPivotTable.drillDownRefusal-10685.test.tsx`. - `pnpm exec vitest run packages/plugin-dashboard/`: 149 files; 1,346 passed, 6 skipped. - `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` + `packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts`: 2 files, 213 passed. - Build: `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-dashboard^...' build`, exit 0. It includes `@object-ui/types`, whose build runs `check-dist-completeness`. The rebuilt `dist/data-display.d.ts` carries `drillDown?: never`. ### New and updated pins - `packages/types/src/__tests__/pivot-drilldown-retired-10932.test.ts` (new) - Refusal: `code` `invalid_type`, `expected` `never`, `path` `['drillDown']`, on the arm, `safeValidateSchema` and `StrictAnyComponentSchema`. - Message: names `` `object-pivot` ``, equals `.describe()`, and is not zod's generic text. - The key stays declared: a tombstone, not a deletion. - Control: the same node without `drillDown` parses on all three. - TS: a `@ts-expect-error` line, plus `Equal` rows (`drillDown` reads `undefined`; `title` stays `string | undefined`). - `registered-type-arms-10859-b2.test.ts`: the "fully populated" fixture drops `drillDown`. The member-level pin now asserts the whole-key refusal at `['drillDown']`. - `PivotTable.drill.test.tsx`: the node's `drillDown` can neither turn the drill on (no handler) nor turn it off (`enabled: false` with a handler). The fixtures no longer author the retired key. - `pivotNode.drillDownRetired-10932.test.tsx` (new): the `SchemaRenderer` + real-registry probe above, with a lit control (the same query finds the affordance when a host passes `onDrillDown`). - Not a zod control: `object-pivot` with `drillDown` has no zod arm, so a `safeParse` of it is refused at `type` either way. The `object-pivot` accept controls are the existing `live` case in `ObjectPivotTable.drillDownRefusal-10685.test.tsx` (TS, green) and the CONTROL case in `drillRefusal-10789.test.tsx` (the drawer opens, green). ### Reverse verifications Each was run from committed state through `ablation-replace.mjs`, which checks the anchor hit and restores on a trap. Every restore was proven: the blob equals HEAD and `git diff HEAD` is empty. 1. Zod arm back to `DrillDownConfigSchema.optional()`. Five tests go red: 4 in the new pin and the rewritten objectui#10859 path pin. Exit 1. 2. TS member back to `drillDown?: DrillDownConfig`. `tsc -p tsconfig.test.json` in `packages/types` reports 4 errors: the pin's `Equal` row, an unused `@ts-expect-error`, the objectui#10859 `MismatchedKeys` row, and the `zod-mirror-parity` ledger. Exit 2. 3. `PivotTable`'s switch re-reads the node key (`... && Boolean(schema.drillDown?.enabled)`). 11 tests go red: 6 in `PivotTable.drill.test.tsx` and 5 in `drillRefusal-10789.test.tsx`, since `ObjectPivotTable` no longer hands the key down. Exit 1. 4. Non-vacuity of the SchemaRenderer probe, which is green on base by design because it records the premise: with the switch forced to `true`, the probe goes red. Exit 1. 5. Cross-package, which also proves the rebuilt `.d.ts` is read: `ObjectPivotTable`'s schema intersected with the full `PivotTableSchema` again gives 4 `TS2339` errors on `drillDown?.target / columns / maxRows / report` (property of `never`). Exit 2. ### Gates These were hand-derived from objectui's `package.json` and `.github/workflows/`, since objectui has no `dispatch-gates`. - Exit 0 at head: `check-control-bytes`, `check-new-cross-file-line-citations` (0 new citations), `check-spec-symbol-derivation`, `check-changeset-presence`, `check-changeset-no-major`, `check-changeset-overwrite` (no pre-existing changeset modified), `check-changeset-fixed`, `check-changeset-claims`, `check-pending-changeset-literals`, `check-handler-key-read-sites`, `check-component-surface-parity`, `check-test-path-roots`, `check-unreferenced-sources`. - Governed-surface guard `--test` on the 10 paths: NOT GOVERNED. - A control-byte grep of the changed files found no hits. - Lint, narrowed, measured per file rather than run repo-wide. The repo-wide `pnpm lint` belongs to CI. The evidence has three parts: 1. The population is the 9 changed TS files, linted under the root `eslint.config.js`. No package has its own config. 2. `--format json` reports 9 files, 0 errors. 3. The config enables no type-aware linting (no `parserOptions.project` / `projectService`), and no rule in `eslint-rules/` reads another file, so this diff cannot move a verdict on an untouched file. - Warning counts are identical base vs head for each of the 7 pre-existing files. The 2 new test files have 0 warnings. - NOT MEASURED: `check-sdui-registration-pins`. Its prerequisite is a console bundle (exit 2, prerequisite not met), and no registration changed. - NOT MEASURED: e2e, and the full `pnpm test` / `pnpm lint`. Those runs belong to CI. ## Acceptance notes - **Release text.** Three pending changesets say something this change makes false for the release. They are **not** edited here: `check-changeset-overwrite` guards pre-existing entries, and the repo's precedent is a separate dated-note PR. This PR's own changeset names all three. - `.changeset/10859-pivot-object-block-zod-arms.md`: "`drillDown` is the shared `DrillDownConfigSchema`". - The dated note in `.changeset/7352-drill-down-config-mirror.md`: "its `drillDown` is this entry's `DrillDownConfigSchema`, so the opening paragraph's two referencing declarations are three for the release". - The dated note in `.changeset/10685-drilldown-per-block.md`: "a validator does read the members of a `pivot` node's `drillDown`, and accepts `mode` there". - **Stale prose, not touched.** The `dataProvider` tombstone docblock in `ObjectPivotTable.tsx` and the header of `widgetDataProviderRetired-7353.test.tsx` still say `PivotTableSchema` has no zod mirror. That has been false since objectui#10859, and it concerns a different member. - `main` moved 3 commits past the base (`328abeb55`), and none touches these files. PR objectstack-ai#10956 (objectui#10928), the serial neighbour, was already in the base. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…e parser tier answers (objectui#10981) (objectstack-ai#11006) Fixes objectstack-ai#10981 Clause-②: no — message, `.describe()` and docblock text only; every accept or refuse verdict stays byte-identical. Implemented by the `os-dev` agent dispatched from the `domain:spec @ objectui` seat, session `https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm` (`mode:subagent`). Base `4b742f41d`. PR objectstack-ai#10986 landed on `zod/objectql.zod.ts` meanwhile, so `main` `665025908` was merged in before the final push. Head `ebd156ef5`. ## What changed Only the false clause changes, on four sites. Each clause names the code the parser tier answers for that key and value shape, as measured on the live registry (next section). | site | face | before | after | |---|---|---|---| | `zod/form.zod.ts`, the `FormSchema.mode` refusal (objectui#10286) | zod message and `.describe()`, one string through `retirementTombstone` | "rendered the same form — no error, no warning" | "rendered the same form — no render-time error or warning; only the parser tier's `unknown-prop` warning noticed it" | | `data-display.ts`, the `TimelineSchema.events` docblock (objectui#6170) | TS face, emitted `.d.ts` | "drew an EMPTY rail, with no error and no warning" | "drew an EMPTY rail, with no render-time error or warning; only the parser tier's `unknown-prop` warning noticed it" | | `base.ts`, the `data-table` sentence in the `BaseSchema.bind` docblock | TS face, emitted `.d.ts` | "renders its header over an empty body, with no error and no warning" | "renders its header over an empty body, with no render-time error; nothing on the page says why, but a render-time console warning (`[ObjectUI] DataTable bind:`, objectui#6575) and the parser tier's `unknown-prop` warning both name it" | | `zod/objectql.zod.ts`, the note on `OBJECT_GRID_EXPORT_OPTIONS_GUIDANCE` (objectui#7762) | source note on a non-exported constant; it ships in the emitted JS, in no `.d.ts` | "no error, no warning, no console line" | "no render-time error, warning or console line (only the parser tier's `type-mismatch` warning noticed it)" | - **Why `base.ts` has its own wording.** That site was not silent at render either. `data-table` has logged `[ObjectUI] DataTable bind:` for an authored `bind` since objectui#6575 (`aff3d7aef`, 2026-08-28). The docblock was written a day later (`99a3c2db6`, 2026-08-29). `merge-base --is-ancestor aff3d7a 99a3c2d` answers 0, with the control leg `94d4876df` also answering 0. So "only the parser tier noticed it" would be false there too. - **Why `exportOptions` names `type-mismatch`.** The `object-grid` registration declares `exportOptions` as `type: 'object'`. So `checkType` answers an array, not the undeclared-key branch. - **New pin:** `packages/types/src/__tests__/silence-clause-parser-tier-closure-10981.test.ts`. - **Optional item, taken:** `packages/components/src/renderers/__tests__/header-bar-unknown-prop-parser-tier-10981.test.tsx`, a new file beside `container-declaration-ratchet`. - **Changeset:** `.changeset/10981-silence-clause-parser-tier-closure.md`, `@object-ui/types: patch`. ## The parser tier, measured before each clause was written **Instrument.** The same as PR objectstack-ai#10980: a one-shot probe, deleted and never committed, under `packages/components/src/renderers/__tests__/`. - It imported the components renderers, `fields` and 18 of the 19 `plugin-*` packages (all but `plugin-designer`). `plugin-tree` has no vitest alias, so it came in by relative source path. - The manifest was built from every KNOWN registry key through `manifestFromConfigs`, the way `container-declaration-ratchet.test.tsx` builds it. - Each leg ran `validateTree` on a control and on the control plus the one key, and reports only the diagnostics that key added. - Each leg also rendered through the real `SchemaRenderer`, inside `PredicateScopeProvider` and `SchemaRendererProvider` with a stub data source, all inside `act`. `console.warn` and `console.error` were spied, and the HTML was compared with the control's. | site | keys | value shapes | the parser tier adds | render leg | |---|---|---|---|---| | `FormSchema.mode` | `form`, `ui:form`. Bare `form` resolves to `ui:form`; `view:form` is `skipFallback` | `edit`, `read`, `disabled`, `create`, `view` (the objectui#10286 pin's set) | exactly one `unknown-prop` on `mode`, 10 of 10 legs | 0 warnings, 0 errors on every variant. HTML equals the control's except for one `mode` attribute on the form element | | `TimelineSchema.events` | `timeline` (resolves to `view:timeline`), `view:timeline`, `plugin-timeline:timeline` (the `TimelineRenderer` registration `TimelineSchema` describes) | 2 events; `[]`; `items` plus `events` | exactly one `unknown-prop` on `events`, 7 of 7 | byte-identical HTML, 0 warnings, 0 errors | | `BaseSchema.bind` on `data-table` | `data-table`, `ui:data-table` | `bind: 'users'` and `bind: 'app.settings.users'`, each with `data` absent and with `data: []` | exactly one `unknown-prop` on `bind`, 8 of 8 | byte-identical HTML (the "No results" body in both), 0 errors, and exactly 1 console warning on every variant: `[ObjectUI] DataTable bind: … is ignored` | | `exportOptions` | `object-grid`, `plugin-grid:object-grid` | `['csv', 'xlsx']`, `['csv']`, `[]` | exactly one `type-mismatch` ("expected an object"), 6 of 6 | the control is `{ formats: ['csv', 'json'] }`, the default an array loses to. Byte-identical HTML, the export control and the rows present in both, 0 warnings, 0 errors | **Controls.** - `form` with an invented key draws `unknown-prop`. `view:form` with `mode: 'edit'` draws nothing, because that registration declares `mode`. - On `object-grid`: an `exportOptions` object draws nothing, a string draws `type-mismatch`, and an invented key draws `unknown-prop`. So the probe tells the two codes apart on one node. - `accordion` with a `body` list draws `not-a-container`, the third code. - With `data` absent, the `data-table` control already carries `missing-required-prop` on `data`; the table above counts only what `bind` adds. - The first control render in the file logged one `react-i18next` initialisation warning. No variant did. **The same answer before each retirement (source reading, not executed).** The `mode`, `events` and `exportOptions` sentences are past tense. - `FormSchema.mode`, at `a05c35063^`, the parent of the objectui#10286 commit: the `ui:form` registration declared the same 13 inputs as today, none named `mode`. `plugin-form`'s `form` was `namespace: 'view', skipFallback: true`. `validate.ts` had the same base-prop set, without `mode`, and one `unknown-prop` branch. - `TimelineSchema.events`, at `195052fff^`, the parent of the objectui#6170 tombstone commit: neither `plugin-timeline:timeline` nor `view:timeline` declared `events`. A `git log -G` for an `events` input name over `packages` finds no registration that ever declared one, back to the shallow boundary (2026-07-20). - `exportOptions`, at `f08d1a86a^`, the parent of the objectui#7762 refusal: `GRID_QUERY_INPUTS` declared `exportOptions` as `type: 'object'`. It had done so since `375efb402` (2026-08-17), and objectui#7762 was filed on 2026-09-05. `checkType` gave a single non-enum arm `type-mismatch`, as today. - `BaseSchema.bind` is present tense, measured today. - The history reads ran on a deepened checkout (`git fetch --deepen=4000`, shallow boundary 2026-07-20). ## Census over joined text (the closure nail) **Instrument.** A one-shot script. It enumerates the non-test `.ts` files of `packages/types/src` from the ref with `git ls-tree` and reads each one from the ref. It joins JSDoc and line-comment continuations and `'…' + '…'` concatenations, and unescapes quotes. Then it counts: - A: "no (adjective) error" and "no (adjective) warning" within 60 characters, with no sentence break; - B: the same, in reverse order; - C: "no (adjective) error or / nor / and (adjective) warning", the family's corrected shape; - D: "without (an) error or / and warning"; - E: "neither error nor warning"; - F: "silent…" within 80 characters of a negated warning, in either order; - G, the wide net: any negated warning ("no warning", "without a warning", "never warns", …); - H, the wider net over other nouns: "no diagnostic", "no console line", "without complaint", "not a warning". | reading | base `4b742f41d` | main `665025908` | head `ebd156ef5` | |---|---|---|---| | files enumerated | 67 | 67 | 67 | | A | 4 | 4 | 0 | | B, D, E | 0 | 0 | 0 | | C, and how many are followed by "the parser tier's" | 302, 300 | 302, 300 | 302, 302 | | F | 1 | 1 | 0 | | G | 6 | 6 | 2 | | H | 25 | 25 | 24 | | control: the objectui#10928 clause | 282 | 282 | 282 | | control: the objectui#10959 tail | 18 | 18 | 20 | - **Positive control.** C fires on every one of the 300 known objectui#10928 / objectui#10959 control strings. A fires on exactly the four sites the card names, which were found independently. - **Every non-control hit at base, read.** - A is the four sites and nothing else. The two C hits without the parser tier's clause are two of the four (`base.ts`, `data-display.ts`). F is the fourth site again. - G adds two hits that say "no warning window" (`objectql.ts`, `zod/objectql.zod.ts`, objectui#6475). That is a deprecation window, not a silence claim, so they stay. - H is outside the card's phrasing family. It is listed under Acceptance notes and not changed. ## Every other byte, and only comment and string lines - Substitute the old clause back for the new one on joined text: each head file equals its base file, 4 of 4, against `4b742f41d` and again against `665025908`. - The first four ablation legs put the old clause back with the anchor tool. Each mutated blob equals the base blob byte for byte: `eba1c495f`, `4dfce4ac6`, `b1abfe909` and `e2c561734` are the base blobs of the four files. - `git diff -U0` over the four files: every changed line is a ` * ` JSDoc line or a `+ '…'` string-literal line. - The objectui#10286, objectui#6170, objectui#7762 and `bind` pins pass unchanged; they are in the full `types` run. - **Emitted `dist`**, after `pnpm --filter @object-ui/types build` at head: - the old clauses, both spellings: 0 files; - the `mode` clause: `zod/form.zod.js`, `zod/index.zod.js`; - the `events` clause: `data-display.d.ts`; the `bind` clause: `base.d.ts`; - the `exportOptions` note: `zod/objectql.zod.js`, `zod/index.zod.js` (the emitted JS keeps comments). ## Reverse verification (ablation), after the fix was committed **Instrument.** objectstack's `scripts/ablation-replace.mjs` in WRAP mode, run from this worktree. - The anchor must hit exactly once and the on-disk blob must move. The tool proves each restore: blob equals HEAD and `git diff HEAD` is empty. - The driver re-proved each restore independently, and ran inside an EXIT / INT / TERM trap. - The types pin imports `../zod/*` source and reads the files from disk. The components pin resolves the registry from source through the vitest aliases. So no build sits between a mutation and a run. | leg | red | red tests | |---|---|---| | the old clause back into `FormSchema.mode` | 3 / 9 | the `mode` arm, the spelling walk, the pairs walk | | the old clause back into `TimelineSchema.events` | 3 / 9 | the `events` docblock, the pairs walk, the parser-tier walk | | the old clause back into `BaseSchema.bind` | 3 / 9 | the `bind` docblock, both walks | | the old clause back into the `exportOptions` note | 2 / 9 | the note, the pairs walk | | the note names `unknown-prop` in place of `type-mismatch` | 1 / 9 | the note | | `zod/ai.zod.ts`, which the pin does not name: an objectui#10928 clause loses its parser-tier half | 1 / 9 | the parser-tier walk | | `header-bar.tsx` starts declaring `title` (components pin) | 4 / 6 | both population legs, both lit controls | - Every leg restored to its HEAD blob (`3a595a747`, `33e3a4770`, `a12a28cf1`, `4e75a80cf` twice, `a40e67ff6`, `ce3850854`) with an empty `git diff HEAD`. After the run, `git status --porcelain` was empty. - **The first `header-bar` attempt was a no-op.** Its replacement contained the anchor, so the tool refused (anchor count 1 to 1) and ran no test. It was re-run with a replacement that does not contain the anchor. The table shows the second run. ## The pins **`silence-clause-parser-tier-closure-10981.test.ts`.** It follows the house style of PR objectstack-ai#10956 and PR objectstack-ai#10980. `@object-ui/types` imports no workspace package, so it holds the prose. - **Zod face.** `FormSchema.mode` is read through its refusal: code `invalid_type` and path `['mode']` first, then the clause. The walk covers every spelling of the objectui#10286 pin, and the `.describe()` text. - **`exportOptions`.** The bare array is refused with `invalid_type` at `['exportOptions']`, with exactly the `.describe()` string. Then the note above `OBJECT_GRID_EXPORT_OPTIONS_GUIDANCE` must carry the clause and name no `unknown-prop`. - **TS face.** The `TimelineSchema.events` and `BaseSchema.bind` docblocks. - **The closure walk.** It reads every non-test `.ts` file of `packages/types/src` from disk, with the root taken from the pin's own `import.meta.url`. - No joined text may pair "no error" with "no warning" (patterns A, B, D, E). - Every C phrasing must have "the parser tier's" within 80 characters after it. - Non-vacuity, by name: the four site files are reached, and both earlier waves' clauses are found. **`header-bar-unknown-prop-parser-tier-10981.test.tsx`.** It answers the open question from PR objectstack-ai#10956 and PR objectstack-ai#10980, on the live components registry. - On `header-bar` and `ui:header-bar`, each of the 9 refused keys draws exactly one `unknown-prop` naming the key. This holds in every value shape the objectui#10387 and objectui#10286 pins refuse. - The control draws nothing. - The lit control shows `crumbs` declared, and no refused key declared. ## Dispatch assumptions, checked 1. **Holds.** There are exactly four sites, one per file, on `4b742f41d` and on `665025908`. The census was widened to patterns A–H above, with controls. 2. **Holds, and made more exact.** - `form` with `mode` draws `unknown-prop`: for all five spellings, on `form` and `ui:form`. - A bare `timeline` with `events` draws `unknown-prop`. The bare key resolves to `view:timeline`. The docblock describes `TimelineRenderer`, which is registered as `plugin-timeline:timeline` (`skipFallback`). That registration also answers `unknown-prop`, so the clause is true for the node the docblock names and for the bare key. - `data-table` with `bind` draws `unknown-prop`. But "only the parser tier noticed it" would be false there, because a render-time console warning fires too (objectui#6575). The site names both. - `object-grid` with an `exportOptions` array draws `type-mismatch`: for three array shapes, on both keys. 3. **Holds**, by source reading at the parent of each retirement. See above. 4. **Changed during the run.** PR objectstack-ai#10986 landed (`8aa68b159`) on `zod/objectql.zod.ts`. `main` `665025908` was merged in before the final push, with no conflict. The note edited here is away from that hunk. PR objectstack-ai#10997 had not landed at `665025908`. ## Gates, head `ebd156ef5` All ran after the merge commit. Exit codes were captured before any pipe. - `pnpm --filter @object-ui/types build` → 0. Its `check-dist-completeness` verified 134 emitted files. - `pnpm --filter @object-ui/types type-check` → 0. `tsc -p tsconfig.test.json --listFiles` includes the new pin. - `pnpm --filter @object-ui/types lint` → 0, with 0 errors. The 295 warnings are existing `no-explicit-any` ones; none is in the new file. - `pnpm exec vitest run packages/types/` → 0: 272 files and 5993 tests passed. - Outside `types` → 0: 27 files and 931 tests passed. - The script tests that read `packages/types/src`: `body-dialect-producer-scan`, `changeset-polarity-census`, `check-action-forward-parity`, `check-changeset-claims`, `check-component-surface-parity`, `check-doc-component-types`, `check-doc-example-types`, `check-handler-key-read-sites`, `check-readme-exports`, `check-spec-symbol-derivation`, `component-node-vocabulary-7434`, `one-authority-per-exported-name-6273`, `check-pending-changeset-literals`, `spec-symbol-ledger-types-7265`, `unconsumed-widget-option-claim-6186`, `vite-types-zod-lazy`; - `component-fixture-declared-keys` in `examples/schema-catalog`; - the new components pin, `container-declaration-ratchet`, the four `header-bar` component tests, `skill-guide-data-table-binding` and `data-table-bind-diagnostic`; - the parser pins `body-dialect-6771` and `containment-declared-slot-9910`. - `pnpm --filter '@object-ui/components^...' build`, then `pnpm --filter @object-ui/components type-check` → 0. `tsconfig.test.json --listFiles` includes the new components pin. `eslint` over the two new test files → 0, with no findings. - `check-changeset-presence`, `check-changeset-no-major`, `check-changeset-fixed`, `check-changeset-overwrite` → 0. `check-changeset-claims` → 0; it is report-only. - `pnpm check:control-bytes`, `check:new-line-citations` (0 new), `check:pending-changeset-literals`, `check:test-path-roots`, `check:phantom-deps` and `check:self-import` → 0. - `check-governed-queue-guard --test` over the 7 paths → NOT GOVERNED. - Declared to CI: repo-wide `pnpm lint`, full `pnpm test` and the other workflow gates. ## Acceptance notes Not changed here, and left for the seat to route. - **Same false claim, outside this package.** `skills/objectui/rules/protocol.md`, the "Readers only." paragraph, says a `bind` on `data-table` renders its header over an empty body "no error, no warning". The render leg and the parser leg above both contradict it. It is on the governed `skills/**` surface, outside this card. - **Same claim in unpublished test prose.** The headers of `base-bind-declared.test.ts` and `object-grid-export-options-refusal-7762.test.ts` repeat it. `mirror-partial-record-narrowing-8516.test.ts` has a different "no error and no warning" claim, about grid columns. All three are test files, outside the census. - **Pending changesets.** `.changeset/7762-object-grid-export-options-bare-array-refusal.md` and `.changeset/6575-data-table-bind-diagnostic.md` describe the pre-fix behaviour in the old words. Pending changesets are out of scope here. - **Adjacent wording, left in place.** - The `base.ts` heading "the one documented silent failure" stays. The corrected sentence now says in which sense it is silent: nothing on the page. - The `exportOptions` guidance string says an array "is silently dropped for the csv/json default". That is present tense about the renderer, and "silently" alone is outside the phrasing family. - **H, the "no diagnostic" nouns (24 at head).** Most are scoped to a named tier or instrument: "on either face" (`objectql.ts`), "zero unresolved-module diagnostics" from `tsc`, `checkType` by name, the TS index signature. A few are unscoped claims about an authored value: `ai.ts` (`maxResults`), `crud.ts`, `data-display.ts` (three) and `zod/data-display.zod.ts`. They were not measured here. - **Observation.** A `mode` on a `form` reaches the DOM as an inert `mode` attribute on the form element. The visible form is identical. - **Boundary.** `view:form` declares `mode` (enum `create`, `edit`, `view`). It is a different registration and node type, which the `FormSchema` docblock does not describe. - **A parser-tier finding, reported to the seat.** `validateTree` answers `bind` with `unknown-prop` on `list` and `tree-view` too, although both read `bind` through `useDataScope`. No registration declares a `bind` input, and the parser's base-prop set has no `bind`. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #10928
Clause-②: no — message and
.describe()text only; every accept or refuse verdict stays byte-identical (auxiliary face).Implemented by the
os-devdispatched from thedomain:specseat, sessionhttps://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm(mode:subagent). Base5c94589f0, head5833df79f.What changed
Only the false clause changes. It changes on every refusal of a node whose renderer reads NEITHER content channel (family D and the E3 residual, objectui#9256). "no error, no warning, no element" now reads:
This is the wording the objectui#10907 changesets settled on. Every site is in the past tense, so the clause is too. Every other byte of each string is unchanged; the proof is below.
.describe()text are one string. There are 131 literal sites in the 12*.zod.tsfiles, plus the two builders that compose the rest:neitherContentChannelGuidanceinzod/tombstone.zod.ts(13 consts, 26 arms);ai.zod.ts's localneitherChannel(6 arms).body?: never/children?: neverdocblocks in the 12 matchingpackages/types/src/*.tsfiles. They ship in the emitted.d.ts.git diff -U0, filtered).packages/types/src/__tests__/content-channel-parser-tier-clause-10928.test.ts..changeset/10928-content-channel-parser-tier-clause.md,@object-ui/types: patch.The parser tier, measured before the clause was written
Instrument. A one-shot probe, deleted and not committed. It builds the manifest from the live registry the way
container-declaration-ratchet.test.tsxdoes: every KNOWN key, throughmanifestFromConfigs.components,fieldsand 18 of the 19plugin-*packages (all butplugin-designer) are imported. It then runsvalidateTreeonce per leg.Population. Every registry key, bare and namespaced, of the 81 node types whose refusal says "reads NEITHER content channel". That is 177 keys: 164 real registrations and 13 lazy stubs that have no inputs yet. None of the 177 declares a
childreninput.children: [node]not-a-container(warning)body: [node]orbody: nodenot-a-container(warning), fromcheckRetiredBodyDialectover the same containment predicatebody: 'hi'(a scalar)unknown-prop(warning)Controls.
div,ui:div,spanandui:spandeclarechildren. On them, addingchildrenadds nothing, and adding abodylist addsunknown-prop.Reading. The dispatch assumed that a node "with
body(notchildren)" stays silent. It does not: abodychild list draws the samenot-a-container. So the settled clause is true for both members, which share one string. A scalarbodydrawsunknown-propinstead; see the Acceptance notes.Census, base
5c94589f0vs head5833df79fThe card's 126 and 139 come from a line-bound grep. The same clause also sits on 25 sites where a line break or a
'…' + '…'join falls inside it: 15 zod (the shared builder among them) and 10 TS docblocks. So the count below joins those first.NEITHER content channel, base / headqqzz_absent_token_9999reads 0 in every cell.HeaderBarSchema:title,logo,nav,left,center,right,stickyandheight.unknown-prop, notnot-a-container.dist, afterpnpm --filter @object-ui/types buildat head, joined the same way:.d.ts: old 0, new 149.HeaderBarSchemastrings, twice (zod/navigation.zod.jsandzod/index.zod.js).The command, exactly as run. The enumeration and the read both come from REF, and
__tests__is excluded. JSDoc continuations and'…' + '…'joins are removed, and an escaped quote is unescaped.Verdicts unchanged, and every other byte of every message
Instrument. A one-shot probe, deleted and not committed. It records 777 keyed entries, taken once at base and once at head:
{}, achildrenlist, abodylist, a scalarbody, a scalarchildren, and both keys;.describe()text of every direct shape member of 196 object schemas;safeValidateSchemaover 85 node types, each over 4 legs: none,children, abodylist and a scalarbody.Result.
HeaderBarSchemarefusals.dist,safeValidateSchema({ type: 'accordion', children: [...] })answersinvalid_typeatchildren, and its message carries the new clause.Reverse verification (ablation), after the fix was committed
Instrument. objectstack's
scripts/ablation-replace.mjs, in WRAP mode. The anchor must hit the declared number of times, and the on-disk blob must move. The restore is proven by blob == HEAD and an emptygit diff HEAD. Each leg puts the old clause back in one message source and runs the new pin.8ff6af3b9(the base blob)object-grid× 2, the populationaccordion's two literal arms751bf2d6faccordion× 2, the populationai.zod.ts'sneitherChannel, back to its base text1fd44f9e0(the base blob)ai-form-assist× 2, the populationda56e22f5,f7d88b6c5,a40e67ff6) with an emptygit diff HEAD.../zod/*source directly, so no build sits between a mutation and the run.The pin, and why it holds the prose rather than the parser
@object-ui/typesdeclares no workspace dependency, and its tests import none. So@object-ui/sdui-parsercannot be imported from here without declaring it (check:phantom-deps). The claim's file surface also put the pin underpackages/types/src/__tests__/.What the pin holds:
accordion), the shared builder (object-grid) and the local builder (ai-form-assist). Each answers codeinvalid_typeat the key's own path, and its message carries the clause.The parser behaviour the clause describes stays pinned where the parser and the registry live, and the new file's header names those pins:
containment-declared-slot-9910.test.tsandbody-dialect-6771.test.ts, inpackages/sdui-parser;container-declaration-ratchet.test.tsx, inpackages/components. It holds declared-slot ⇔ rendered-list over the live registry.Whether to add a live-registry behavioural pin in
packages/componentsas well is left as an open question for the seat.Gates, head
5833df79fThe gate list was derived by hand from objectui's root
package.jsonand.github/workflows/, against this diff.pnpm --filter @object-ui/types build(tsc, vite, dist completeness)pnpm --filter @object-ui/types type-check(tsc, examples, tests)tsc -p tsconfig.test.json --listFileslists the new pinpnpm --filter @object-ui/types lintno-explicit-any, none on the new filepnpm exec vitest run packages/types/check-changeset-presence·changeset:check·check-changeset-overwritecheck:pending-changeset-literals·check:control-bytes·check:new-line-citations(0 new)check:handler-key-reads·type-check:coverage·lint:coverage·check:test-path-roots·check:unreferenced-sources·check:phantom-deps·check:published-tsconfig-exclude·check:esm-specifiers·check:spec-symbols·check:component-surface-parity·check:doc-typescheck:changeset-claims--testover the diff's pathsAGENTS.md: exit 3)check:readme-exports. It judges README self-imports against every package'sdist/*.d.ts. This tree built only@object-ui/types, so the gate printed "could not be judged … runpnpm buildfirst" and exited 1. That is a prerequisite, not a finding. This diff touches no README and no export.pnpm testbeyondpackages/types, thedom/dom-heavyprojects, E2E andtest:dist.Serial constraints
zod/complex.zod.ts, in theGlobalFilterSchemaJSDoc.git merge-treeof this head against its head1daf7edf7is clean.mainmoved two commits since the base, to06a96e948. Neither touchespackages/typesorpackages/sdui-parser, andgit merge-treeagainst it is clean.Acceptance notes: out of scope, not changed here
HeaderBarSchema's nine non-channel refusals (zod/navigation.zod.ts).title,logo,nav,left,center,right,stickyandheight.variantsays "no error, no warning, no class".unknown-prop(measured, same probe).safeValidateSchema({ type: 'header-bar', title: 'T' })hands back the "no warning" message (measured on the builtdist).unknown-prop. So it is reported to the seat, not swept here.body?: neverdocblocks onbox,span,container,flex,stack,grid,scroll-area,formandtoggle(inlayout.tsandform.ts).body"rendered an EMPTY element with no error and no warning".children, and abodylist on it drawsunknown-prop(measured on every bare andui:key).bodyon a reads-neither node drawsunknown-prop, notnot-a-container. The clause speaks of an authored content value, which in both channels is a child list or a node. The scalar case is not written into it.Generated by Claude Code