Repository navigation
fix(types): a gate-wrapped arm waives objectName when dataSource.object binds the node (objectui#11117) - #11312
Conversation
…bject` binds the node (objectui#11117) One shared refinement, `requireRecordSource(type, rungs)`, now carries the record-source requirement of every gate-wrapped arm that has one, with the node's `dataSource.object` counted as a rung on the predicate the spec gate and the runtime use (`dataSourceSuppliesObject`, exported from `public-blocks.zod.ts` as the one copy): - object-grid and list-view: `objectName` is optional as a member and required by the refinement unless the binding names the object; - object-map / object-gantt / object-calendar: their ladder gains the binding; - object-kanban: its hand-copied ladder is replaced by the shared helper with its own rung list. An empty `dataSource.object` supplies nothing, and a node with neither key is still refused. `ObjectViewSchema.table` rebuilds the grid shape instead of `.omit()`ing the now-refined grid. The TS twin of `ObjectGridSchema` makes `objectName` optional to stay in parity with its mirror. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…nd the documented bindings (objectui#11117) - New pin file: per arm (object-grid, list-view, object-kanban, object-calendar, object-gantt, object-map) the binding parses on the member, the tolerant face and the strict face; a node with neither key is refused at the root keyed RECORD_SOURCE_REQUIRED; an empty or non-record binding supplies nothing; a wrong-typed objectName beside a binding is still the member's refusal. Every json binding fence in content/docs/utilities/data-objectstack.mdx and content/docs/guide/data-source.md is read off disk and parsed on the tolerant face. - The kanban and calendar record-source pins take the message that names the binding; the kanban pin that held the documented fragment refused on the record-source rule now holds it parsing. - Docs: the "Narrowing a saved view" fence takes the spec's rule-array filter, the ElementDataSource excerpt types filter as ViewFilterRule[], and the data-binding guide says a bound node needs no objectName. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…ledger the mirror's waiver (objectui#11117) Making `ObjectGridSchema.objectName` optional on the TypeScript face reddens five `translateOptions(schema.objectName, ...)` read sites in `@object-ui/plugin-grid` (measured with a source-mapped tsc program over the consumer packages), a renderer-typing change outside this card's file surface. The declaration stays `string`, its doc says why, and the mirror's wider `objectName` is recorded in the parity test's `WiderThanDeclared` ledger (CONCRETE, one arm), which reddens when the faces agree again. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…texts it makes false (objectui#11117) - `.changeset/11117-datasource-objectname-waiver.md`: `@object-ui/types` minor (a widening). - Dated, append-only corrections on the pending changesets of objectui#6939, #7313, #7780 and #8990, whose bodies say a bound node with none of the rungs is refused, or name the retired `requireKanbanRecordSource`; front matter byte-identical. - `content/docs/api/schema-reference.md`: the object-grid `objectName` row and the kanban record-source note name the binding. - `content/docs/guide/data-source.md`: the bound-node sentence moves to its own paragraph, after the line-items note it had split, and names the six blocks. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
…ung (objectui#11117) On `object-grid` and `list-view` the record-source refusal now sits at `objectName`, the path the required member used to report at, instead of the root. `objectui validate` locates a refusal by path, and its arm-selection pin (`validate-union-arm-selection.test.ts`) reads `Path: objectName` for a grid that names no object. The four ladder arms keep the root, where no single key is at fault. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
|
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
|
…description names the dataSource.object binding (objectui#11117) Round 2 of objectui#11117. The registrations' `objectName` input description said the schema refuses a block that declares none of `data`, `staticData` and `objectName`; since this PR the schema also accepts a block whose `dataSource.object` names the object. The sentence now says so, on `object-map`, `object-gantt` and `object-calendar` (`view:calendar` shares the calendar list). No input name, type, `required` flag or shape moved. The adjacent source comments that cite the refinement, and the `plugin-gantt` / `plugin-kanban` README sentences, carry the same correction, and the pending objectui#7470 / #10392 changesets get dated, append-only corrections. Changeset: the three plugins, patch. `scripts/markdown-test-inputs.mjs` adjudicates the round-1 pin `element-data-source-objectname-waiver-11117.test.ts`, which reads the binding fences of `data-objectstack.mdx` and `guide/data-source.md`; its absence from `ADJUDICATED` reddened `markdown-test-inputs.test.ts` in CI. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
✅ 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: Rendered 2026-10-01T02:47Z by the at-tier reviewer run inside the Gates on this head (read 2026-10-01T02:44Z): 43 check-runs, 40 ① Derived judgmentsThe mechanism, read from the head. One module-private Both faces, all six arms. The strict face is derived by
Refusals the card insists on, still held (pinned on the member and the tolerant face in
Issue-shape change, disclosed: on grid and list-view the neither-key refusal is now a Public-surface changes, each named:
Docs faces (AGENTS.md #2):
Changesets, which publish verbatim; each sentence judged.
② Semver level
③ Boundary flagsDev deviations (
Card pointers: Region: Implemented-by: VERDICT: PASS The accept-set change is right on both faces for all six arms, every refusal the card insists on holds, the semver level matches what the diff publishes, and every flag is answered. Two docs-clause defects are named above (the guide's "names its object in neither place" clause, over-strict for the four ladder arms; the changeset's "every per-element binding the docs teach", overstated ahead of its own enumeration); neither moves an accept set, a version or a pin, and each is a one-clause correction. |
…at was refused (objectui#11117) Round 3, from the at-tier contract record on 2666d82. - `content/docs/guide/data-source.md`: the validator refuses one of the six blocks only when it declares no other record source and names its object in neither place; a `staticData`-only map, gantt or calendar, or a `bind`- or `data`-only board, parses. The binding counts as "a" record source, not "the" one. - `.changeset/11117-datasource-objectname-waiver.md`: the base refused the documented `object-grid`, `list-view` and `object-kanban` bindings, not every documented binding; the `object-form` fence already parsed on the bag arm and `element:number` already had its waiver. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Rendered 2026-10-01T03:03Z by the at-tier reviewer run inside the Carried forward by reference. The at-tier record Gates on this head (read 2026-10-01T03:02Z, about ten minutes after the push, which the PR’s ① Derived judgmentsThe delta, in full (
No accept-set or public-surface change is implied by the delta: no schema, type, ② Semver levelUnchanged by the delta: ③ Boundary flags
Implemented-by: VERDICT: PASS The round-3 delta is two prose corrections, each now true against all six arms; it moves no accept set, no public surface and no version, and the parent head's judgments carry forward unchanged. |
…r (objectui#11117)
Brings in objectui#10859 batch 5 (an authored object-map is judged by its
`properties`-bag arm, `ObjectMapBlockSchema`) and the other merges since
`1ccb5ba7d`.
Conflicts, resolved by measurement:
- `packages/types/src/zod/objectql.zod.ts`: batch 5 gave `requireRecordSource`
an `at: 'node' | 'properties'` mode for the bag arm; this PR gave it a
per-arm rung list, the `dataSourceSuppliesObject` binding rung and
`when: () => true`. The merged helper is ONE function taking both:
`requireRecordSource(type, rungs, at)`. The bag arm now counts a NON-EMPTY
`dataSource.object` (batch 5 counted presence), so
`{ type: 'object-map', dataSource: { object: '' } }` with no bag rung is
refused, as on every other arm and as at runtime. In bag mode the message
names the node's `dataSource` binding.
- `packages/plugin-map/src/index.tsx`: batch 5's text is taken. Its
`objectName` input description already names the node's `dataSource`
binding, so this PR no longer edits that file.
Follow-ups in this merge:
- `object-map-properties-bag-10859-b5.test.ts`: the flat node now also
carries the root `RECORD_SOURCE_REQUIRED` issue beside its flat-key
refusals (`when: () => true`).
- The 11117 pin judges `object-map` on `ObjectMapBlockSchema`, with
`objectName` written in the bag, and pins the flat mirror separately.
- Both 11117 changesets are corrected: plugin-map leaves the round-2 one,
and the round-1 one names the object-map bag arm and its one narrowing.
Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
…s binding strings (objectui#11117) The merge resolution (80ec249) spelled the two `binding` strings as single-quoted literals with `\`` escapes, which `no-useless-escape` refuses (6 errors, the `Lint` check). The escapes go; the string values are byte-identical, so every message is unchanged. Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT Co-authored-by: Claude <noreply@anthropic.com>
✅ 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: Rendered 2026-10-01T03:47Z by the at-tier reviewer run inside the Carried forward by reference, by measurement. For every file of this PR, its old net patch ( Gates on this head (read 2026-10-01T03:46Z, after a bounded wait for the twelve runs still in progress at the first read): 43 check-runs, 40 ① Derived judgmentsRound 5, in full. One commit, Round 4, the merge (
No other public surface moved in the merge: ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS On the merged face the accept-set change is right on both faces for all six arms, including the bag arm |
… floatingConfig on both faces, file data-table's inline-edit flags by name (objectui#6152, round 5) (objectstack-ai#11320) Refs objectstack-ai#6152 Clause-②: yes (narrowing). `selectionStyle` and the base-face `floatingConfig` retire on both faces, and `editable` / `singleClickEdit` leave the authored docs, under the startup no-gradualism rule. It is priced in the changeset. Round 5 of objectui#6152, dispatched under claim `5923137359` (`domain:devx` seat 2, session `session_01TdiauJaVCHuj45EzZGUxHh`, on the maintainer's direct order). The rulings are the seat review `5921390451`: its three answers to round 4's open questions. ⛔ This is a draft from a dispatched dev: do not mark it ready and do not enable auto-merge. The card stays open: `ObjectGridSchema`'s keys and the spec-derived pairs (objectui#2231) are not this round's. ## What changed 1. **`DataTableSchema.editable` / `singleClickEdit`: answer 1, B.** Both keys are filed by name on `RuntimeOnlyNamedAllowList`, each with its reason, under round 4's class rule: a key a host produces in code and no document authors (the `showAddRow` precedent). No declaration moved. `content/docs/components/complex/data-table.mdx` no longer teaches them on an authored node. The interface block drops them, and the `editable: true` example becomes an "Inline editing" section. That section says the capability is host-paired: the table only stages an edit, and `object-grid` sets both keys on the table it builds and supplies the save path. It points a document author at `object-grid`. The two members' doc comments say the same. 2. **`DataTableSchema.selectionStyle`: answer 2, B.** It is retired on both faces: a `?: never` tombstone on `data-display.ts`, and a `retirementTombstone()` on `zod/data-display.zod.ts` whose message says to delete the key and that `selectable` alone turns selection on. The hover-only checkbox branch in `packages/components/src/renderers/complex/data-table.tsx` is dropped, so the `'always'` behaviour is the only one. 3. **Base-face `ChatbotSchema.floatingConfig`: answer 3, A.** It is retired on the `chatbot` face only: `?: never` on `complex.ts` and a `retirementTombstone()` on `zod/complex.zod.ts`, whose message names `type: "chatbot-floating"`. - **Mechanism, from the measurement:** a base tombstone only, with no re-declaration. `ChatbotFloatingSchema` does NOT inherit the base member. Its TS face is `BaseSchema` plus `Pick` of `ChatbotSchema` over `ChatbotSharedKey` and `maxToolRoundtrips`, plus its own `floatingConfig` member. Its zod twin is `BaseSchema.extend` with the shared pick spread and its own `floatingConfig` arm. So the floating registration's declaration, mirrored in round 4, was already separate on both faces and is untouched. - It is a tombstone and not a deletion because `ChatbotSchema` is a `BaseSchema` carrier, where a deleted member reads as `any` through the index signature. The discriminator is cited, not restated, so `tombstone-discriminator-agreement-9684.test.ts` still counts four statements. - The chatbot half of `floating-chatbot-trigger-icon-retired.test.ts` is flipped, not deleted. On a `chatbot` node it now asserts the refusal of the whole `floatingConfig` key on both faces, a live config included, with a no-`floatingConfig` control. `chatbot-registration-authoring-faces-7655.test.ts`'s type pins follow. 4. **Ledger.** `zod-mirror-parity.test.ts`: the `UnmirroredDeclared`, `RuntimeOnlyDeclared` and `RuntimeOnlyNamedAllowList` blocks, and every prose total reconciled to the parser count. One non-vacuity leg was reshaped; see the acceptance notes. 5. **Docs.** `content/docs/plugins/plugin-chatbot.mdx` no longer says `floatingConfig` type-checks and parses on a `chatbot` node. 6. **Changeset.** `.changeset/6152-unmirrored-round5.md` bumps `@object-ui/types` and `@object-ui/components` to minor, with the break stated. Three pending entries that this round makes false get dated, append-only `**Correction, 2026-10-01**` notes, with frontmatter and every prior line intact: `6152-unmirrored-round4.md`, `7654-floating-chatbot-trigger-icon-tombstone.md` and `7655-chatbot-registration-authoring-faces.md`. ## Per key | key | node | ruling | route | TS face | zod face | renderer | docs | |---|---|---|---|---|---|---|---| | `editable` | `data-table` | B | filed by name, runtime-only (host-paired) | unchanged (`boolean`), doc comment notes the pairing | unchanged (not mirrored) | unchanged | removed from the authored interface block; section rewritten | | `singleClickEdit` | `data-table` | B | filed by name, runtime-only (host-paired) | unchanged (`boolean`), doc comment notes the pairing | unchanged (not mirrored) | unchanged | as above | | `selectionStyle` | `data-table` | B | retired (ADR-0049) | `?: never` | `retirementTombstone()` | hover branch dropped | none taught it | | `floatingConfig` | `chatbot` | A | retired (ADR-0049) | `?: never` | `retirementTombstone()` | none (never read) | `plugin-chatbot.mdx` corrected | | `floatingConfig` | `chatbot-floating` | (kept) | none | unchanged | unchanged (round 4's arm) | unchanged | unchanged | ## Ledger totals, by TypeScript-parser count of `zod-mirror-parity.test.ts` | ledger (entries / keys) | before, base `3d09948` | after, head `c1b5767` | |---|---|---| | `KnownDrift` | 49 / 86 | 49 / 86 | | `UnmirroredDeclared` | 6 / 20 (3 / 14 spec-derived, 3 / 6 local) | 5 / 16 (3 / 14 spec-derived, 2 / 2 local) | | `RuntimeOnlyDeclared` | 4 / 27 | 4 / 29 | | `RuntimeOnlyNamedAllowList` | 3 / 20 | 3 / 22 | | `WiderThanDeclared` | 3 / 3 | 3 / 3 | | `MirroredUndeclared` | 2 / 2 | 2 / 2 | | shared entries / union of the two unmirrored ledgers | 1 of 4 / 9 (6 + 3) | 0 of 4 / 9 (5 + 4) |⚠️ **The local half is 2 / 2, not the 0 / 0 the dispatch predicted (hypothesis H1 falsified).** The two keys left are `displayMode` on `ChatbotSchema` and on `ChatbotFloatingSchema`. Under objectui#7654 maintainer ruling B, kept by round 4's Q3 B, their refusal stays TypeScript-only: stored designer documents carry `displayMode: 'floating'`. So they stay in `UnmirroredDeclared` by decision, not as mirroring debt. The ledger prose says so, and the non-vacuity pin "the LOCAL half read as EMPTY" still holds. After this round, `UnmirroredDeclared` holds only the spec-derived 3 / 14 (objectui#2231) and those two decided entries. ## What was measured, before editing (base `3d09948`) - **Type-checker read and producer census.** The round-4 instrument: the TS compiler API over every `packages/*/src` and `apps/*/src`, 5585 files. - `ChatbotSchema.floatingConfig`: 0 reads, 0 producers, 1 test producer (the tripwire). - `ChatbotFloatingSchema.floatingConfig`: 1 read (the floating registration). - `DataTableSchema.selectionStyle`: 1 read (the `data-table` destructure) and 0 producers. - `editable` / `singleClickEdit`: 1 read each (the destructure) and 1 producer each (`ObjectGrid`). - Lit controls: `ChatbotSchema.messages` 3 reads, `maxHeight` 2, `DataTableSchema.columns` 2 reads and 2 producers. - **Authored census in this repository.** 493 tracked JSON files, 354 Markdown JSON fences and 3202 TS/TSX files with a literal `type`. - `data-table`: `selectionStyle` 0, and `editable` / `singleClickEdit` 1 each, both `ObjectGrid` in code with no document. - `chatbot`: `floatingConfig` 1, a test. - Lit controls: `data-table.columns` 71, `selectable` 8, `chatbot-floating.floatingConfig` 4. - **objectstack census.** `git grep -i` on a self-named ref of objectstack `origin/main` (`9b0de7de7`): `selectionStyle`, `selection_style` and `floatingConfig` 0 hits. The lit control on the same ref was `singleClickEdit`, with 6 hits, all on the spec's `ObjectGridProps`, not `data-table`. **There is no named user of hover-style selection, so the stop condition did not fire.** - **Runtime probe, a one-off.** The real `SchemaRenderer` and registry, varying one key. Both files were deleted after each run, leaving `git status` and `git diff HEAD` empty. - At base, a selectable `data-table` with `selectionStyle: 'hover'` differed from unset: 6651 against 6481 characters. The checkbox cell gained `relative`, and each checkbox was wrapped in a `transition-opacity opacity-0 group-hover/row:opacity-100` div. `'always'` was byte-equal to unset, and the `showRowNumbers` control moved the DOM. - At head, `'hover'` is byte-equal to unset (6481 = 6481), with no hover wrapper. - `chatbot` with against without a full `floatingConfig`: byte-equal at base and at head (2600 = 2600). The `placeholder` control moved the DOM, and the same config on `chatbot-floating` drew its title. ## Verification on head `c1b5767` Every heavy run went through `bash /home/user/objectstack/scripts/pm/os-verify-lock.sh` with `OS_VERIFY_LOCK_SLOT=issue-6152`, `NODE_OPTIONS=--max-old-space-size=4096` and a memory watchdog; the lowest available memory was 11244 MB. - **Builds.** `pnpm exec turbo run build` over `check-doc-snippet-types --build-filter` with `--concurrency=2`: "Tasks: 35 successful, 35 total". `@object-ui/types` reports "dist completeness: 1 package(s) complete (138 emitted files verified)". - **Type-checks.** `pnpm --filter X run type-check` exits 0 with 0 TS errors for `@object-ui/types` (`tsc --noEmit`, `-p tsconfig.examples.json`, `-p tsconfig.test.json`), `components`, `plugin-chatbot` and `plugin-grid`. The last three are the packages the type-checker census named. - **Tests,** all with `pnpm exec vitest run --maxWorkers=4`: - `packages/types/`: 306 passed (306), 7701 tests passed; - `packages/components/` in two runs: 192 passed and 1 skipped (193), with 1920 tests passed; then 156 passed (156), with 1560 tests passed; - the consumer set: 309 passed (309), 4939 tests passed. It covers `plugin-chatbot`, `plugin-grid`, `examples/schema-catalog`, `packages/cli`, the three app-shell files importing `@object-ui/types/zod` and `app-shell/.../metadata-admin/previews/__tests__/`. - **Gates, each exit 0.** - Changesets: `check:changeset-claims`, report-only, with 47 pending entries naming a touched file; three were re-read and corrected as above. Also `check:pending-changeset-literals`, `check-changeset-presence` ("9 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"), `changeset:check` and `check-changeset-overwrite`, report-only, whose findings are the 3 dated appends with frontmatter unchanged. - Docs: `check:doc-fences`, `check:doc-snippets` ("698 of 698 block(s) judged, 0 failed"), `docs:check-links` ("Links are valid across 17 scan roots"), `check:doc-examples`, `check:doc-types`, `check:doc-example-ids`, `check:doc-example-readers` and `check:skill-examples`. - Others: `check:control-bytes`, `check:new-line-citations` ("0 new citation(s)"), `check:handler-key-reads`, `check:component-surface-parity` (report-only), `check:designer-field-key-parity`, `check:element-data-source-declaration`, `check:prompt-keys`, `check:readme-exports`, `check:spec-symbols`, `check:test-path-roots`, `check:unreferenced-sources` and `check-type-check-coverage`. - `check-governed-queue-guard --test` over the 15 paths: NOT GOVERNED. - **Narrowed lint against the merge base `cd5b19a`.** The 9 lintable changed files ran through `eslint --format json`. - With inline config: 0 errors and 75 warnings. - With `--no-inline-config`: 1 error and 77 warnings. The one error is the `react-hooks/static-components` line in `data-table.tsx`'s row-action menu, inline-disabled at an untouched line, and it is identical at base. Per-file error and warning counts are equal at base and head; the new test file reads 0 / 0. - The output judged 9 files and ignored 0. - Invariance: `eslint.config.js` declares no `parserOptions` (so no type-aware project), and no rule in `eslint-rules/` reads the disk, so the diff cannot move an untouched file's verdict. The repo-wide lint is CI's. ## Ablations, predictions written first, each restored to blob == HEAD with `git diff HEAD` empty Each ran through `ablation-replace.mjs`, with its anchor count proven 1 → 0 on disk and restored. - **A1** deleted the `selectionStyle` zod arm. The round-5 pin file read "5 failed | 9 passed (14)", exactly the five predicted `selectionStyle` rows. `tsc -p tsconfig.test.json` exited 2 with one error, at `assertionUnmirroredMatchesLedger`. - **A2** restored `ChatbotSchema.floatingConfig` to `FloatingChatbotConfig`. vitest stayed green on the three chatbot pin files (43 passed), since the change is type-level. `tsc` exited 2 with exactly the four predicted errors: TS2578 in the round-5 pin file and in the trigger-icon tripwire, TS2344 in the 7655 pin file, and one at `assertionDriftMatchesLedger`. - **A3** deleted the `editable` allow-list row. `tsc` exited 2 with exactly one TS2344, at `assertionRuntimeOnlyIsCallbackShapedOrNamed`, and the parity file's vitest stayed 37 passed, as predicted. ## Acceptance notes - **One non-vacuity leg was reshaped, not dropped.** The leg asserted that the two unmirrored ledgers share at least one entry. This round made them disjoint for real: `DataTableSchema`, the last pair in both, left `UnmirroredDeclared`. Keeping the leg would have forced a key back onto a ledger. It is replaced by a lit control on the same reader: `KnownDrift` shares a pair with each unmirrored ledger, so the header's `0` is a reading, not a name-normalisation artefact. The reasoning is in the test's own comment. - **Merged `origin/main` twice, never rebased:** `de4459a`, then `c1b5767`. The second merge followed PR objectstack-ai#11310, which edits `complex.ts`. The ledger counts did not move with either merge. - **PR objectstack-ai#11312 (open) adds a `WiderThanDeclared` entry to the same parity file.** The two diffs touch disjoint blocks. Whichever lands second merges `main` and re-derives the totals by parser count. - The optional `with-item-count` catalog id rename was not done, because this round does not touch that file. - Commit trailers are the model-free pair AGENTS.md requires. - NOT MEASURED: - the full `pnpm test` farm and the repo-wide lint (CI's); - `apps/console` tests, because no file it owns changed; - an at-tier contract record, which the seat owes on the landing head. --- _Generated by [Claude Code](https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #11117
Clause-②: yes (narrowing): a widening on six arms (a non-empty
dataSource.objectcounts as a record source), plus one narrowing on theobject-mapbag arm (an emptydataSource.objectno longer counts)What this does. The tolerant face (
safeValidateSchema, whichobjectui validateruns) now accepts a node bound throughdataSource.objectwithout anobjectNameon every gate-wrapped arm that has a record-source requirement:object-grid,list-view,object-kanban,object-calendar,object-ganttandobject-map. The binding counts only whendataSource.objectis a non-empty string, as the spec gate'sstrNameand the runtime'sisElementDataSourceConfigread it. A node with neither is still refused, anddataSource: { object: '' }supplies nothing. Dispatched by thedomain:spec @ objectuiseat, sessionsession_01VhxTqosz7wn54ahqyxgERT.The change
requireRecordSource(type, rungs)inpackages/types/src/zod/objectql.zod.tsnow takes each arm's own rung list and counts the binding as the last rung, usingdataSourceSuppliesObject. That predicate is the oneelement:number's spec waiver already used; it is now exported frompublic-blocks.zod.tsas an internal helper, so there is one copy, and it is not re-exported fromindex.zod.ts.when: () => true, so the refusal is reported beside any other issue on the node. Onobject-gridandlist-viewthe required member used to behave that way, and the refinement keeps it.object-kanban's hand-copiedrequireKanbanRecordSourceis gone. The kanban arm now calls the shared helper withbind/data/objectName.object-gridandlist-view,objectNameis optional as a member and the refinement requires it unless a binding is present. The refusal is reported atobjectName, the path the required member used, soobjectui validatestill printsPath: objectName(validate-union-arm-selection.test.tsstays green). The issue is nowcustomwithparams.code = 'RECORD_SOURCE_REQUIRED'. The four ladder arms still report at the root.objectNamedescribe names the waiver.ObjectViewSchema.tablerebuilds the grid shape ontoBaseSchemabefore its.omit(). zod 4 throws on.omit()of a refined object, and the grid now carries a refinement. The slot is unchanged: same keys, same.passthrough(), no record-source check.content/docs/utilities/data-objectstack.mdx.filteras the spec's rule array,[{ "field": "total", "operator": "greater_than", "value": 100 }].ElementDataSourceexcerpt typesfilterasViewFilterRule[], and its fragment marker gives the new reason.content/docs/guide/data-source.md) andcontent/docs/api/schema-reference.mdsay a bound node needs noobjectName..changeset/11117-datasource-objectname-waiver.mdbumps@object-ui/typesminor (a widening).Premises, measured on
origin/main1ccb5ba7dagainst the built@object-ui/typesdistThe probe ran every documented binding through
AnyComponentSchema(tolerant) andStrictAnyComponentSchema.object-formontoObjectFormBlockSchema.{ "type": "object-form", "dataSource": { "object": "user" } }already parses on both faces, and themoderefusal belonged to the flat arm, which no longer judges authored documents.object-formrow declaresobjectNameoptional, so the bag arm has nothing to waive. A form node with neither key also parses, as the spec row allows; the renderer's inlinecustomFieldspath is one reason a node can lack both. ⛔ This change does not narrow that.object-metric,object-timelineandobject-master-detail-form: their rows require no object key.object-kanban's "has no record source" was the same gap:dataSource.objectwas missing from its ladder. Calendar, gantt and map refused their bound nodes the same way. All three registrations are gate-wrapped, and their mapping landsbinding.objectonobjectName.objectNameinvalid_type, anddataSource.filterinvalid_typefor the record form. Both are gone.dataSource.filteronly, which is the spec's own refusal.The TypeScript twin of
ObjectGridSchemakeepsobjectName: stringtranslateOptions(schema.objectName, …)read sites in@object-ui/plugin-grid'sObjectGrid.tsx. I measured this with a source-mappedtscprogram over core, plugin-grid, plugin-list, plugin-view, plugin-calendar, plugin-gantt, plugin-dashboard, plugin-designer and app-shell. Changing those sites is renderer typing, outside this card's file surface.string. The mirror's widerobjectNameis a CONCRETE entry inzod-mirror-parity.test.ts'sWiderThanDeclaredledger, with itsWIDER_ARMSrow and the header figures. The ledger reddens when the two faces agree again.ListViewSchemais derived from its mirror, so itsobjectNameis now optional. The same consumer program reported no error from that.Texts this change made false
Corrected here.
object-gridobjectNamerow and the kanban record-source note inschema-reference.md.groupBydocblock inobjectql.ts, which said the documented{ type, dataSource }fragment is still refused. Also the kanban TS docblock and theobjectql.zod.tsdocblocks that namedrequireKanbanRecordSource.**Correction, 2026-10-01**notes on four pending changesets: objectui#6939, objectui#7313, objectui#7780 and objectui#8990. Their front matter is byte-identical.Corrected in round 2 (
2666d824b).objectNameinput descriptions on theobject-map,object-ganttandobject-calendarregistrations, and onview:calendar, which shares the calendar list, now end with: "…refuses a block that declares none of them, unless the node'sdataSource.objectnames the object, which then lands on this key."requiredflag or shape moved.registry-inputs-spec-parity.test.tsran read-only and passed 230/230 untouched.plugin-ganttandplugin-kanbanREADMEs, and the comments next to the three registrations, carry the same correction..changeset/11117-registration-objectname-description.mdbumps the three plugins as a patch.CI repair (round 2).
scripts/markdown-test-inputs.mjsADJUDICATEDnow registerselement-data-source-objectname-waiver-11117.test.tsas readingcontent/docs/utilities/data-objectstack.mdxandcontent/docs/guide/data-source.md.markdown-test-inputs.test.tswas red at993a0c85c(1 failed / 24 passed) and is 25/25 at2666d824b.--auditreports 108 candidate test files, all adjudicated.Round 2 gates, at
2666d824b:check:doc-snippetsthen passed with 698 of 698 compiled blocks and 0 failed. This replaces the round-1 NOT MEASURED.check:readme-exportspassed.type-checkpassed for plugin-map, plugin-gantt and plugin-calendar.check:new-line-citations,check:control-bytes,check:changeset-claims,check-changeset-presenceandcheck-changeset-no-majorall exited 0.Round 3 (
01b9fb478), from contract record 5923725485. Two one-clause corrections:content/docs/guide/data-source.mdnow says the validator refuses one of the six blocks only when it declares no other record source and names its object in neither place. The binding is "a" record source, not "the" one.@object-ui/typeschangeset now says the base refused the documentedobject-grid,list-viewandobject-kanbanbindings, not every documented binding.Gates at
01b9fb478:markdown-test-inputs.test.ts: 25/25, and--auditis clean;check:doc-snippets: 698/698 compiled, 0 failed;check:doc-fences,check:changeset-claims,check-changeset-presence,check:new-line-citationsandcheck:control-bytes: all exit 0.Round 4: merge of
main(80ec249bc). The merge brings in objectui#10859 batch 5, under which an authoredobject-mapis judged by itsproperties-bag arm,ObjectMapBlockSchema. That arm already counted adataSourcebinding, by its presence alone.objectql.zod.ts. Batch 5'sat: 'properties'mode and this PR's rung list, binding predicate andwhen: () => trueare now ONE helper,requireRecordSource(type, rungs, at).dataSource.object, like every other arm and like the runtime. So{ "type": "object-map", "dataSource": { "object": "" } }with no bag rung is refused withRECORD_SOURCE_REQUIRED. It used to parse, and it rendered no records.plugin-map/src/index.tsx. Main's text is taken. ItsobjectNameinput description already names the node'sdataSourcebinding, so this PR no longer edits that file.object-mapis now judged on its bag arm, which keeps a record-source rule read inproperties. The flatObjectMapSchemamirror is no longer an arm of the face, and it keeps the same rule on the node.object-maprow moves toObjectMapBlockSchema, and the flat mirror gets its own case. Batch 5's flat-key pin (object-map-properties-bag-10859-b5.test.ts) now expects the rootRECORD_SOURCE_REQUIREDissue beside its flat-key refusals.plugin-mapleaves the registration changeset.80ec249bc:@object-ui/types: build, type-check, and 306 files / 7,734 tests, all passing.registry-inputs-spec-parity.test.ts(230/230).markdown-test-inputs: 25/25, and--auditis clean.check:doc-snippets: 698/698.check:changeset-claims,check-changeset-presence,check-changeset-no-major,check:new-line-citationsandcheck:control-bytes: all exit 0.Round 5 (
2a237f0ec): a CI repair. The round-4 merge resolution escaped the backticks inrequireRecordSource's twobindingstrings. Those strings are single-quoted, so the escapes are useless, andno-useless-escapefailedLintwith 6 errors.pnpm --filter @object-ui/types run lintreports 0 errors. eslint--format jsonover all 12 TS/JS files in this diff also reports 0 errors.@object-ui/typestype-check passed. The 11117 pin and the batch-5 bag pin passed 93/93.The census fixtures (24 in 18 files, from PR objectui#11286's list): judged, then handed back as a worklist
What I recommend. Convert 21 fixtures and keep 3. The conversion is separable from the validator change and spans 11 packages and 15 dom-test files. Each file asserts the merged filter the gate produces, so its expected values may need re-spelling. Nothing here converts a fixture.
Keep (3). Each pins how a renderer handles a stored legacy shape:
record-picker-malformed-filter-10789.test.tsx: the$regexrecord, and its well-formed record control.ElementDataSourceGate.malformedFilter-10789.test.tsx: the$gtrecord control.Convert (21). Each fixture's subject is the authored binding, and it reaches the gate as one; convert it to
[{ field, operator, value }]:record-picker-element-data-source(2)ObjectChart.elementDataSourceObjectPivot.elementDataSourceRecordRelatedListRenderer.elementDataSourcerecord-related-list.propsRefusal-9963: the_fullBindingtype controlEmbeddableForm.elementDataSourceLineItemsPanel.elementDataSourceMasterDetailForm.elementDataSourceObjectGantt.elementDataSourceObjectGrid.elementDataSource(2)ObjectKanban.elementDataSource(2)ListView.elementDataSourceListView.sharedGateObjectMap.elementDataSourceObjectTimeline.elementDataSource(2)ElementDataSourceGate.test(2). Once it is converted, that file should carry one explicitly named stored-shape (tuple array) case, so the gate keeps coverage of a shape a renderer may still receive.Tests and gates, at head
993a0c85c@object-ui/types.pnpm --filter @object-ui/types buildpasses, withdist completeness: 1 package(s) complete.type-checkechoestsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.jsonand passes.pnpm exec vitest run packages/types/: 302 test files / 7649 tests passed.packages/typesthat readssafeValidateSchema, the strict face, or one of the six mirrors: 96 files across cli, core, sdui-parser, the plugin packages, app-shell, apps/console, examples/schema-catalog and scripts. All passed, in two runs: 70 files / 1330 tests, and 26 files / 257 tests.check:spec-symbols,check:new-line-citations(0 new),check:control-bytescheck:doc-types,check:doc-fences,check:doc-example-idscheck:handler-key-reads,check:element-data-source-declaration,check:component-surface-parity,check:test-path-rootscheck:changeset-claims(report-only; record-source paragraphs read and corrected above),check:pending-changeset-literalscheck-changeset-presenceandcheck-changeset-no-major--no-inline-configover the 8 changed TypeScript files: 8 files in the JSON report, 0 errors. The 38 warnings are allno-explicit-anyon unchanged lines. The config has no type-aware rules, so this diff cannot move a verdict on an untouched file.ca5c505ba, each viaablation-replace.mjswith the anchor proven 1 to 0 on disk. The pins import../zod/…source, so no dist is involved. Each run was restored, and the blob matched HEAD withgit diff HEADempty.if (dataSourceSuppliesObject(node)) return;): 14 failed / 33 passed. The failures were the six arms on both faces, plus both docs pages.typeof object === 'string'): 6 failed, one EMPTY case per arm.when: () => true: 7 failed. Six were the "not a record" cases; the seventh was the grid case that reports the missing record source beside acolumnserror.NOT MEASURED:
check:doc-snippets(exit 2, precondition: its 34 packages are unbuilt). Separately, I tested the edited fragment marker against the gate's own marker and verification-claim regexes: it matches, its reason is 556 characters, and it makes no claim.Acceptance notes
object-chart. Its registration is gate-wrapped, but the strict face still refusesdataSourceby name. That is the deferral the TS twin'sdataSourcedocblock records, outside this card's population.objectNameis already optional there.ObjectViewSchemaandObjectTreeSchema. They refuse a bound node withoutobjectName, and that is correct: neither registration is gate-wrapped.ObjectGridSchemakeepsobjectName: string(seat decision B, round 2). Open PR objectui#11306, since merged as582edef1c, edited the same TS region and plugin-grid, and no TS-typed producer of a bound grid was found. The mirror's widerobjectNameis recorded as a CONCRETEWiderThanDeclaredentry inzod-mirror-parity.test.ts, which goes red when the declaration becomes optional.Generated by Claude Code