fix(react,core): declare the spec dataSource binding at the gate-wrapping seam - #6767
Merged
os-sales merged 9 commits intoAug 29, 2026
Merged
Conversation
…apping seam `PageComponentSchema.dataSource` is the one spelling that resolves a saved view for an object-bound block. It works, and the html tier reported it with the same `unknown-prop` warning it gives the spellings that do nothing (`viewName`, `view`) — because `validateTree` looks a prop up in the block's declared `inputs` and no registration declared this key. On the tier built to accept AI-authored pages, where the diagnostic IS the contract, the only signal pointed away from the key that works. Adopting the maintainer ruling of 2026-08-29 — option B in the INJECTION form: - `ELEMENT_DATA_SOURCE_INPUT` is the single declaration, in `@object-ui/core` beside the binding's semantics. `Registry.register` emits it for a registration whose renderer passed through `elementDataSourceBlock()`, the new seam exported from `@object-ui/react` next to `ElementDataSourceGate`. One mechanism, one copy — not a hand-written declaration per block. - Thirteen renderers across eleven packages reach the seam. - `dataSource` on a block that does NOT read it (`flex`, `card`) still reports `unknown-prop`; `BASE_PROPS` is untouched. - New `check:element-data-source-declaration` fails any source that consumes the gate without reaching the seam, so a new block cannot forget. - The forward spec/registry parity derivation now reads the whole node contract (`PageComponentSchema`, derived — not a listed exemption), because `ComponentPropsMap[type]` is only the per-block PROPS half and `dataSource` is a node-level spec key. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…ock pin, document the seam Two follow-ons to the injection landing in the previous commit. `recordRelatedListInputs.spec-parity.test.ts` is the per-block half of the same forward-direction gate the repo-wide file carries, and it made the same assumption: that `RecordRelatedListProps` — the block's PROPS contract — is the whole contract of the node an `inputs` list describes. It is not. `PageComponentSchema` carries its own top-level keys, and the html tier validates an author's attributes against `BASE_PROPS` + `inputs`, with no third place for a node-level key to live. Widened the same way: DERIVED from the spec, never listed, and asserted to still discriminate (`dataSource` accepted, `relationshipField` and an invented key refused). `packages/react/README.md`'s worked example is what a new block author copies, and it showed the wrapper without the seam — so it taught exactly the omission `check:element-data-source-declaration` now fails the build for. The example wraps the renderer, and the surrounding prose says what the seam is for and that the hook/panel form (a renderer whose hooks must run before the panels) owes it at its registration instead. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…rivation Seventeen renderers in thirteen files across twelve packages, not thirteen across eleven — counted from `check:element-data-source-declaration --list` rather than from the card's nine. Also records that the two spec/registry parity gates were corrected (derived from `PageComponentSchema`) rather than exempted. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Contributor
✅ 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
|
… from the mocked package CI was red on all four shards. Reproduced locally with the same partition (`pnpm test --shard=2/4`): three files failed, `Tests 6694 passed (6694)` — zero failed assertions. They were COLLECTION failures: Error: [vitest] No "elementDataSourceBlock" export is defined on the "@object-ui/react" mock. Cause, and it is this PR's: a registration runs at MODULE SCOPE, and 101 suites in this repo partially mock `@object-ui/react` by hand-listing the exports they return. A module-scope read of a name absent from such a list throws while the test file is being collected, so the file dies before running one assertion. Measured blast radius over every suite that mocks the package without `importOriginal`: 17 files, which is why all four shards were red rather than a subset — an appended input breaking exact-array assertions would have hit a subset and produced failed ASSERTIONS, and there were none. The fix is not to hand-list the export in 17 mocks. That leaves the hazard in place for the next module-scope addition, and the mocks are only incomplete because this PR made widely-imported packages reach into a commonly-mocked one at module scope. So the seam moves to the package nothing mocks: - `@object-ui/core` owns the one function under the one name (renamed from `markElementDataSourceBlock`); `@object-ui/react` re-exports it beside the gate for discoverability rather than wrapping it, so there is still exactly one implementation. - All 17 call sites import it from `@object-ui/core`. Every one of them already imported `ComponentRegistry` from there at module scope, so this removes a module-scope dependency rather than adding one, and nothing in this repo mocks `@object-ui/core`. - `check:element-data-source-declaration` now enforces the import source, so this cannot recur. The rule was proven able to fail (one file flipped back to the react import: exit 1, naming that file; restored and proven by blob hash). No test was skipped, quarantined or edited to get green: the 17 files were never wrong, they could not load. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Prose only, no code change: the seam lives in `@object-ui/core` and is re-exported by `@object-ui/react` beside the gate, and call sites take the core import because a registration runs at module scope while this repo's suites partially mock `@object-ui/react`. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Comment-only (proven: every changed line in both files is a comment line). The react-side re-export exists for discoverability; call sites import the seam from `@object-ui/core`, and check:element-data-source-declaration enforces that — so the docblocks should not read as a suggestion. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…rces Two diagnostics still told the reader to take the seam from `@object-ui/react` — the console pin's assertion message and the gate script's own fix instruction. Following either would reproduce the collection failure this PR just fixed, which is the worst kind of stale message: it is read only by someone already stuck. Part of #6678 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Contributor
✅ 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
|
This was referenced Aug 29, 2026
…-check row `ci.yml`'s `type-check` job runs `pnpm check:element-data-source-declaration` between `check:side-effects-array` and `check:esm-specifiers`, but the job table's "What it runs" cell did not name it, so `scripts/__tests__/ci-cd-pipeline-doc.test.ts` was red on this branch. Add it to the cell in the order ci.yml runs it, with the one-sentence explanation the page gives every other gate in that row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…ssue-6678-datasource-input-declaration-injection
Contributor
✅ 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
|
os-sales
marked this pull request as ready for review
August 29, 2026 09:57
os-sales
deleted the
claude/issue-6678-datasource-input-declaration-injection
branch
August 29, 2026 10:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6678
The spec's
dataSourceelement binding is now declared by the blocks that readit, emitted from one seam, so the html tier stops reporting the one working
saved-view spelling as
unknown-prop.Reproduced first, on the base commit
Base
e321d5234, through the live path (registry-built manifest, the realpage.tsxhtml-kind construction,SchemaRenderer, the reallist-viewregistration and the real
object-grid):(the real diagnostic wraps the tag name in angle brackets; they are dropped here
because this body is sanitized)
...while the same page's saved view resolved its columns and the assertion on
those columns passed. The working spelling and the two non-working ones
(
viewName,view) were reported identically, and the tier's only signalpointed away from the one that works.
What landed — option B in the injection form (ruling, 2026-08-29)
ELEMENT_DATA_SOURCE_INPUTin@object-ui/core, beside the binding's own semanticselementDataSourceBlock()in@object-ui/core, re-exported by@object-ui/reactnext toElementDataSourceGateRegistry.register→withElementDataSourceInputcheck:element-data-source-declarationNo block hand-writes the declaration. Seventeen renderers, in thirteen files
across twelve packages, reach the seam; the key then flows to
sdui.manifest.json, the JSX authoring types (sdui-intrinsics.d.ts) andsdui-blocks.mdthrough the generators the repo already publishes with.packages/sdui-parser/src/validate.tsis untouched —BASE_PROPSmirrorsBaseSchema, and silencing the key there would make the diagnostic lie in theother direction.
Two things the work found that the card did not have
1. The population is seventeen renderers in twelve packages, not nine blocks.
The card named nine; the tree also has
plugin-grid,plugin-timeline, twofurther
plugin-formblocks andelement:record_picker. Nothing washand-listed, so the mechanism simply covered them.
element:record_pickerwas found by the render probe, not by reading sources: itconsumes the gate's hook and status panels without the
ElementDataSourceGatetag, because its object lives under
properties. A check that only knew the tagwould have called that file compliant while it published exactly this defect. The
gate's rule was widened to the family the probe actually sees, with comments
masked via
scripts/js-comment-mask.mjs.2. The spec/registry parity gate had something to say — and it was measurably
wrong, so it was corrected rather than exempted. The card predicted this. Five
blocks went red on
declares no top-level input the spec does not accept.Measured against the pinned spec:
dataSourceis a node-level spec key, a sibling oftypeandclassName.ComponentPropsMap[type]is only the per-block props half of a node'scontract, and the gate treated that half as the whole thing — an assumption that
held only while no node-level key was declared per block. The forward direction's
accepted set now also reads
PageComponentSchema's own keys, derived from thespec, not listed, and it still discriminates:
dataSourceandclassNameareaccepted,
objectName,viewNameand an invented key are not. That calibrationis asserted in the same file. No exemption entry was added. The same widening was
carried to the
record:related_listper-block pin.check:react-blocks-declaration-parityin objectstack reads the same two declarations and will have the same false
complaint about these blocks. It cannot be fixed from this repo. Reported as an
open question rather than worked around.
Acceptance — both directions
Opportunity Name,Amount, notStage)flex/card:unknown-propstill firesviewName/viewstill reportedAblation — discriminating, against the committed implementation
Injection disabled at
Registry.registeron commit6aa8de55a; mutation provenon disk by counting both the removed and the injected text (1→0 and 1), not by an
editor exit code. No rebuild leg: the root vitest config aliases every
@object-ui/*specifier topackages/*/src, so the mutated source is what ran —and the run did change, which is the assertion that would have caught a stale
build.
7 red — the diagnostic pin, the cross-package mechanism pin, its
anti-vacuity floor, and all three consumer-reach pins.
5 green, and the right five —
flexandcardstill report; the twonon-working spellings still report; the block-does-not-declare-it control holds;
and the saved view still resolves, because this is a declaration change and not a
resolution change.
Restore proven:
git diff HEADempty and the restored blob hash equal to thepath's HEAD blob (
4749cfc1a374049d24d43e48c1028cb2e5bf9eb1). Restored run:12 passed (12).
Verification
The union in the table below was run on
d3c62e07e; the reproduction was on thebase
e321d5234and the ablation on6aa8de55a. Exit codes captured byredirect-then-capture, never after a pipe.
vitest runover the derived listTest Files 75 passed (75)/Tests 1064 passed (1064), EXIT=0pnpm run --filter core/react/components/plugin-*/console type-checkcheck:element-data-source-declarationOK — 13 gate-consuming file(s) checked, 3 definition file(s) excludedregistry-inputs-spec-parity+recordRelatedListInputs.spec-parity+record-picker-inputs-spec-parityTest Files 3 passed (3)/Tests 127 passed (127)check:control-bytes/changeset-presence/changeset-fixed/changeset-no-major/changeset-overwrite/side-effects-array/readme-exports/esm-specifiers/vi-mock-specifiers/phantom-deps/self-import/lint-coverage/type-check-coverage/doc-fences/doc-types/doc-snippetsDeclared narrowings
Two, both stated so a reader can tell a measurement from a gap.
Tests. The suite is the derived population rather than every package: the
files that read a registration's
inputs/getMeta/getConfig/manifestFromConfigs, taken bygit grep, plus every*.elementDataSourceandregistrationtest of the blocks whose renderers were wrapped, plus the gate'sown suite — 75 files. CI runs the whole farm regardless, and it found something
this narrowing could not — see the CI section below, which is the honest cost of
the narrowing and is recorded rather than smoothed over.
ESLint. Ran over the 29 changed files (
--format json, 0 errors, 325warnings — this repo sets no
--max-warningsand fails only on errors). Thenarrowing is sound because
eslint.config.jsenables no type-aware linting (noparserOptions.project, noprojectService) and is not modified here, so afile's verdict depends only on its own contents.
CI was red on all four shards — diagnosed, reproduced and fixed
The failure was this PR's. It was not an appended-input assertion break and
not a resolution failure;
Type Check,Build & E2EandLintwere all green,which is exactly right, because the fault was visible only to vitest.
Reproduced locally with the same partition CI uses,
pnpm test --shard=2/4on the red head
fce8b57ca:Tests 6694 passed (6694)is the whole diagnosis: zero failed assertions.The three files failed at COLLECTION:
Root cause
A registration runs at module scope, and 101 suites in this repo partially
mock
@object-ui/reactby hand-listing the exports they return. A module-scoperead of a name absent from such a list throws while the file is being collected,
so the file dies before running one assertion.
Blast radius, measured over every suite that mocks the package without
importOriginal(36 candidates): 17 files. That is why all four shards werered rather than a subset — and why there were no failed assertions anywhere.
The fix, and why it is not "add the export to 17 mocks"
Patching 17 fixtures would leave the hazard standing for the next module-scope
addition. The mocks were only incomplete because this PR made widely-imported
packages reach into a commonly-mocked one at module scope. So the seam moved to
the package nothing mocks:
@object-ui/coreowns the one function under the one name (renamed frommarkElementDataSourceBlock).@object-ui/reactre-exports it beside thegate for discoverability rather than wrapping it — still exactly one
implementation, one name.
@object-ui/core. Every one already importedComponentRegistryfrom there at module scope, so this removes amodule-scope dependency rather than adding one.
check:element-data-source-declarationnow enforces the import source, so thiscannot recur. The new rule was proven able to fail: one file flipped back to
the react import gave exit 1 naming that file; restored, and the restore proven
by blob hash.
⛔ No test was skipped, quarantined, disabled or edited to get green. The 17 files
were never wrong — they could not load.
After the fix
pnpm test --shard=2/4— the partition that was red — ond019ee61aTest Files 549 passed (549)/Tests 6727 passed (6727), seam-mock errors 0Test Files 36 passed (36)/Tests 330 passed (330), EXIT=04f0bb02a2Test Files 75 passed (75)/Tests 1064 passed (1064), EXIT=0elementDataSourceBlockmock errorscheck:element-data-source-declarationThe test count RISING from 6694 to 6727 across the same 549 files is the
confirmation that matters: those 33 tests are the ones the three uncollectable
files could never run.
One honest note on shas: the shard reading was taken on
d019ee61a. The threecommits after it change a changeset
.md, comment lines in two source files(proven comment-only — every changed line in that diff begins with
*,/*or//), and two stale failure-message strings that told the reader to import theseam from the wrong package. The 75-file derived suite and every gate above were
re-run on the pushed head
4f0bb02a2and are green there.