Uh oh!
There was an error while loading. Please reload this page.
fix(react,plugin-detail,plugin-grid,plugin-form,docs): one data-source resolution rule for the object-bound family, and a loud report when none resolves - #5447
Merged
os-support-ai merged 1 commit intoAug 20, 2026
Conversation
…e resolution rule for the object-bound family, and a loud report when none resolves (#5378, #5377) `object-grid` / `object-form` read the adapter from `SchemaRendererProvider` context; `detail-view` was registered as the raw component and read a React `dataSource` prop. `SchemaRenderer` reads only context, so the two wirings were mutually exclusive — a page could satisfy one half of itself or the other. All three now resolve through `useResolvedDataSource` (explicit prop first, context second). Additive: `detail-view` keeps its prop form, direct `<DetailView dataSource={…} />` callers are untouched, and `object-grid` no longer throws out of `useSchemaContext()` when a page has no provider. A block that resolves no adapter renders a `No data source resolved` panel naming the block, the object and the ancestor that injects the adapter, instead of an empty shell. Opt-in per block so inline rows / `customFields` / inline records / `api` placements are untouched. `content/docs/guide/building-crud-app.md` is rewritten to the provider wiring with the declared keys, and its `setup.ts` now registers the three plugins it renders — without which every snippet resolved to `Unknown component type`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-support-ai
marked this pull request as ready for review
August 20, 2026 21:17
Uh oh!
There was an error while loading. Please reload this page.
os-support-ai
deleted the
claude/issue-5378-datasource-wiring-convergence
branch
August 20, 2026 21:18
This was referenced Aug 20, 2026
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 freeto 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#5378
Fixes#5377
Two independent axes broke the same five snippets in
content/docs/guide/building-crud-app.md, and a third turned up while measuring. Landing any one of them alone leaves the page rendering nothing while the PR reports success — which is exactly what #5378 was split out to prevent — so they are folded into one branch.What was measured first (baseline,
origin/main@32ef595f4)Every snippet rendered through the real registry with a fake adapter.
find/findOnecall counts, and whether the row text appears:object-gridfind0, nothingfind0, nothingobject-formfindOne0, nothingfindOne0, nothingdetail-viewfindOne0, nothingfindOne0, nothingAll five dead in both directions. With the keys corrected, the split #5378 reports appears:
datadroppedobject-gridfind1, row rendersfind0detail-viewfindOne0findOne1, row rendersTwo mutually exclusive wirings. Neither reported anything: no error, no warning, no empty state that explained itself.
1. Converge the wiring (ruling item 1)
detail-viewwas registered as the rawDetailView, which reads a ReactdataSourceprop. Its siblings are registered through wrappers that readSchemaRendererContext.SchemaRendereritself reads only context, so a prop never becomes context for anything below it.All three blocks now resolve the adapter through one shared hook,
useResolvedDataSource(new,@object-ui/react): an explicitdataSourceprop first, the provider context second.Precedence, stated and pinned: the explicit prop wins. It is the more specific signal — written on one placement, by someone who can see it — while the context adapter is ambient over every descendant. It is also the only precedence under which the ruling's "the prop form stays accepted" is true of the case that matters: a prop-form caller mounted inside an app that happens to have a provider would otherwise have its explicit choice silently overruled by an ancestor it never wrote.
Additive, as ruled — no prop removed this pass. Three wirings are asserted for
detail-view: context alone (new), prop alone (must not break), and both at once (prop wins).The compatibility surface, measured (the triage confidence gap)
Triage flagged that nobody had sized the existing
detail-viewprop-form caller population. Measured in this repo:packages/plugin-detail/src/renderers/record-details.tsxandpackages/plugin-detail/src/RecordDetailDrawer.tsxboth render the component directly, never through the registry. The published README documents the same direct form. The new wrapper does not sit between any of them andDetailView.detail-viewnodes carrying adataSourceprop: zero, the getting-started guide being the one that tried.So the additive wrapper changes no measured caller's behaviour and creates no ambiguous precedence. No fork to report — but the precedence is pinned by test rather than left to be discovered.
Two side effects of the convergence, both strictly widening:
object-gridused to calluseSchemaContext(), which throws without a provider — the|| {}beside it could never catch it. So the exact case this card is about surfaced as the error boundary sayingComponent "object-grid" failed to renderover a React hook message naming neither the data source nor the fix. It now reads the context.object-formgains adataSourceprop it did not have. It read context only, so nothing that worked before resolves differently.2. The silence dies with it (ruling item 2)
A block in the family that resolves no adapter now renders a No data source resolved panel —
role="alert", same shape as the existing unresolvable-view panel — naming the block, the object it was about to read, and the ancestor that injects the adapter. An address, not a symptom.The check is opt-in per block (
requiresDataSourceonElementDataSourceGate). The gate cannot compute "needs an adapter" and does not try: every block has different escape hatches, and a predicate guessed in the shared gate would paint a configuration error over a block that is working — a worse failure than the silence it replaces. Each call site states it, enumerating its own fallbacks:object-grid: inline rows (dataarray,provider: 'value', legacystaticData),bind, or a host that owns the fetch and passes the window as adataReact prop (plugin-list's ListView). Tested withArray.isArray, which is the exact testObjectGridapplies — a bare'data' in propswould also be true of the schema's owndataobject, becauseSchemaRendererspreads every unstripped schema key as a prop.object-form: inlinecustomFields, which is whathasInlineFieldsgates on inside the component.detail-view: an inlinedatarecord, or anapiendpoint it fetches without an adapter.The no-adapter answer comes before the unresolvable-view answer: with no adapter the view cannot resolve either, so the block would otherwise report "this data source cannot list the saved views" — true, and pointing at the view name instead of at the missing injection.
The other eight blocks using this gate are untouched; the flag defaults off.
3. The guide (ruling item 3, plus #5377's key axis)
SchemaRendererProviderat the top of the App, per AGENTS.md commandment Implement visual designer for Object UI schemas #1. NoSchemaRendererin the page carries adataSourceprop any more; a test asserts that over the file's text.objectbecomesobjectNamein all five snippets.recordIdbecomesresourceIdondetail-viewONLY.object-formgenuinely readsrecordId(ObjectForm.tsxcallsfindOne(schema.objectName, schema.recordId)), so the blanket rename that "fix the keys" reads like from a distance would have taken a working block to broken. Both directions are pinned: the form probe asserts theresourceIdspelling loads no record there, the detail probe assertsrecordIdloads none there.dataremoved from thedetail-viewsnippet only. Per-snippet triage, not a sweep: ondetail-view,datameans "here is the record already, do not fetch" —DetailViewreturns early on anyschema.data, so the object's metadata was being installed as the record andfindOnenever ran (measuredfindOne0 with it, 1 without). Onobject-gridandobject-formthe same key is measured inert (find1 either way), so it stays and is filed instead.A third axis, found while measuring — and fixed here with evidence
The guide's
setup.tsloaded@object-ui/componentsand@object-ui/fieldsonly.initializeComponents()registers neither the plugins nor anything else beyond its own package, and Step 1 never installed@object-ui/plugin-detailat all. Measured by rendering the three keys against exactly the guide's registration set:All five snippets resolved to the OBJUI-001 panel for anyone who followed the page literally, so no amount of wiring or key fixing could have made it render. Fixed in place rather than filed because the correct form is pinned by the page's own idiom — Step 2 already writes
import '@object-ui/fields';and explains why — and because a render probe over "the guide's snippets" that registered the plugins itself while the guide did not would have been measuring something the reader cannot reproduce.Acceptance: a render probe, not a diff review
Three probe files (
packages/plugin-{grid,form,detail}/src/__tests__/guideCrudAppRenders.test.tsx) evaluate the guide's own schema literals off disk — balanced-brace extraction plusFunction, with the identifiers the page closes over bound — and render them through the real registry. A transcription kept in the tests would drift from the page silently, which is this defect's whole failure mode. Each file asserts its snippet count first, so a lost snippet cannot make the file vacuous.Per snippet, after both axes: adapter call count 0 → 1 and the row text appears.
object-gridfind0find1,["task", …], row paintedobject-gridfind0find1, row paintedobject-gridfind0find1, row paintedobject-formgetObjectSchema0 /findOne0getObjectSchema('task'),findOne('task','42'), fields painteddetail-viewfindOne0findOne('task','42'), record paintedEach axis is also pinned as a standing contrast, so reintroducing one fails here rather than on a reader's screen: re-spell
objectNameback toobjectand the block fetches nothing; putrecordIdon the detail view and it loads nothing; hand the detail view adataandfindOnenever runs.Reverse verification — predicted, then observed
No build artifact sits between any edit and the thing under test: the root vitest config aliases every
@object-ui/*specifier topackages/*/src, and each probe imports its own plugin as../index. Leg A proves it rather than asserting it — it edits a registration that a different file's assertions depend on, and a staledist/in the path would have left it green.DetailViewagainrequiresDataSourcefrom the gridobject:back in the guide's Step 5Leg A is the interesting miss, and it is about my own tests rather than the code: two of the three no-adapter tests are absence assertions (a placement with inline rows must NOT get the panel), and a build with no wrapper satisfies those trivially. They are false-positive guards, not feature proofs, and only the presence assertion can fail when the feature is removed. Leg C under-counted for the same kind of reason — the both-wirings test reads the same snippet, so it dies with it.
A cross-package type check was reverse-verified too:
requiresDataSource="yes-please"inpackages/plugin-griddrawsTS2322: Type 'string' is not assignable to type 'boolean | undefined', which proves the new prop reachedpackages/react/dist/index.d.tsand that plugin-grid'stscis reading the rebuilt declarations, not a cached copy. (It first failed for the opposite reason — a staledistgaveTS2558: Expected 0 type argumentsafteruseResolvedDataSourcebecame generic.)Out of scope, filed
Filed as #5446: Step 7 documents a view switcher and a search box that
object-gridnever reads — top-levelviewhas zero read points, anddata: { objectSchema, queryParams }is none ofViewData's fourproviderarms. Measured inert (identicalfindparams with and without both keys). Left alone here because two legitimate correct forms exist — move it into the specdataSource: { object, view }binding, or spell the view's declaredfilter/sorton the block — and choosing between them changes what the page teaches and, for the first option, what a reader sees when their backend has no such saved view. That is a decision, not a mechanical fix.Verification
Gate union re-derived from the actual three-dot changed paths after the final commit, and run at
55c49cbb1:pnpm exec vitest run packages/plugin-detail/ packages/plugin-form/ packages/react/— 193 files, 2074 passedpnpm exec vitest run packages/plugin-grid/ packages/plugin-list/— 124 files, 1379 passed (plugin-list included deliberately: it is the host that passes the grid's rows as a React prop)pnpm exec vitest run packages/app-shell/— 456 files, 4410 passed, 1 skipped (the heaviest in-repo consumer of all three blocks)turbo run type-checkacross the workspace — 81/81, the downstream-consumer direction for the new@object-ui/reactexportsturbo run linton the four changed packages — 0 errorscheck:doc-types,check:doc-snippets(with the packages its--build-filternames built),check-doc-links,check:control-bytes,check:phantom-deps,check:self-import,check-lint-coverage,check-type-check-coverage,check:skills-paths,check:spec-symbols,check:action-forward-parity,check:i18n-keys,check:i18n-drift,check-changeset-no-major,check-changeset-fixed,check-changeset-presence— all greenChangeset:
minoron the four packages (nevermajor, per AGENTS.md §版本号策略).Generated by Claude Code
Generated by Claude Code