Repository navigation
fix(plugin-form,plugin-list,plugin-view,react): read SchemaRendererContext as declared, not through a cast to any (objectui#7209) - #10350
Conversation
…ntext as declared, not through a cast to any Five reads of the renderer context erased SchemaRendererContextType: four `useContext(SchemaRendererContext as React.Context<any>)` casts (the embeddable-form and object-master-detail-form bridges, ListViewBlock, useResolvedDataSource) and plugin-view's module-level re-declaration of the imported context as `React.Context<any>`. Through them a read of a member the context does not declare compiled clean - the mechanism behind the phantom `ctx.formValues ?? ctx.data` channel objectui#7206 retired. Every cast is gone. Dropping them surfaced no undeclared-member read; it surfaced two places where the declared `null` (no adapter bound) met a prop that does not admit it: - embeddable-form handed it to EmbeddableForm's optional `dataSource`; it now collapses to `undefined`, as object-master-detail-form already did. EmbeddableForm only tests the adapter for truthiness. - object-view hands it to ObjectViewProps.dataSource, declared REQUIRED and kept so (objectui#7842). That single value now carries a narrow, commented assertion instead of the whole context being erased. No runtime value changes. useResolvedDataSource's emitted declaration is unchanged. Each site is pinned by a ts.Program leg in its own package that reads the hook call's type, calibrated by planting the cast back in memory. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ema-renderer-context-casts
|
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
|
…ema-renderer-context-casts
The sixth type-erasing cast of this class: `useElementDataSource` read the context through `useContext(SchemaRendererContext as Context<any>)`, spelled with the bare `Context` type imported from react, which the first census (keyed on `React.Context`) did not match. The cast and the now-unused `Context` type import are gone; the hook call now carries `SchemaRendererContextType | null`. Nothing else in the react program moves (0 diagnostics), and no runtime value changes. Pinned beside it by the same ts.Program harness as the other five sites; the plugin-form pin's sibling list and the changeset now name it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ 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
|
Fixes #7209
Clause-②: no
Every
SchemaRendererContexttype-erasing cast in the claim's four files is gone, so each of those reads seesSchemaRendererContextTypeexactly as@object-ui/reactdeclares it. A read of a member the context does not declare (the objectui#7206ctx.formValues ?? ctx.datashape) is now a compile error at each of them. No runtime value changes and no published type moves.Dispatched by the
domain:ui#1seat (claim comment 5819439599). Implementing session:https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC.React.Contextofany"), because GitHub strips tag-shaped fragments from PR bodies, even inside backticks.Census at
origin/main(dispatch Zone 2, step 1)Base
7616d893. Widened to everyuseContextofSchemaRendererContextunder any alias or cast, tests excluded:git grep -n -E "useContext\(\s*[A-Za-z]*SchemaRendererContext"overpackages/*/src/*,apps/*andexamples/*, plus everySchemaRendererContext asspelling and everyContextofany. Control: the same pathspec returns 114useContext(lines in 86 files, so the instrument is live.That census finds six casts, not five:
plugin-form/src/index.tsx,EmbeddableFormRendereruseContext(SchemaRendererContext as React.Contextofany)plugin-form/src/index.tsx,MasterDetailFormRendererplugin-list/src/ListViewBlock.tsx,ListViewBlockplugin-view/src/index.tsx, module scopeconst SchemaRendererContext: React.Contextofany= ImportedSchemaRendererContextreact/src/element-data-source/ElementDataSourceGate.tsx,useResolvedDataSourceReact.useContext(SchemaRendererContext as React.Contextofany)react/src/hooks/useElementDataSource.ts,useElementDataSourceuseContext(SchemaRendererContext as Contextofany), spelled with the bare importedContexta879ffe6), together with the now-unusedContexttype importThe card's other two sites are already gone at base.
LookupFieldwas repaired byedea22a5, anduseCascadingOptions.tshas 0 hits.What dropping each cast surfaced (step 2)
tsc --noEmit -p tsconfig.jsonper package, with every cast dropped and nothing else changed:@object-ui/react: exit 0.@object-ui/plugin-list: exit 0.@object-ui/plugin-form, exit 2.src/index.tsxTS2322: "Type 'DataSource of any | null | undefined' is not assignable to type 'DataSource of any | undefined'." This is theEmbeddableForm dataSource={ctx?.dataSource}handoff.@object-ui/plugin-view, exit 2.src/index.tsxTS2322: "Type 'DataSource of any | null' is not assignable to type 'DataSource of any'." This is theObjectView dataSourcehandoff.⭐ The Zone 2 assumption that "each error is a read of a member the context does not declare" is falsified at every site: 0 of 2. Both errors come from the DECLARED member
dataSource: DataSource | null | undefinedhanding itsnull("no adapter bound") to a prop that does not admitnull. No undeclared-member read was hiding behind any of the five casts, because objectui#7206's retirement already removed the only one. So no provider search was needed, and there is no dead channel to remove or re-derive.The fix at each surfaced error:
EmbeddableFormRenderer: thenullcollapses toundefined(ctx?.dataSource ?? undefined), exactly asMasterDetailFormRendererin the same file already did. The runtime result is identical.EmbeddableFormonly ever tests the adapter for truthiness.ElementDataSourceGateresolves its explicit value first and then the context through??, so anulland anundefinedexplicit value are the same input.ObjectViewRenderer:ObjectViewProps.dataSourceis declared REQUIREDDataSource, and objectui#7842 ruled that widening it is a maintainer's call. That file is also fenced (open objectui#10283). The renderer passesnullwhen no provider is mounted, andObjectView's prop doc names this renderer as thatnullcaller. Neither removal nor re-derivation is possible without that contract change, so under step 2's third branch a cast stays on this site. It is narrowed from the whole context to the one value:(ctx?.dataSource ?? null) as DataSource, with a comment. The context read itself now uses the declared type and is pinned. The passed value is byte-for-byte what it was. This is a judgement call, and it is raised for the seat in the report'sopen_questions.useResolvedDataSource's declaration is byte-identical (step 3)ElementDataSourceGate.tsxwas put back to its base bytes (confirmed withgit diff --quiet 7616d893 -- FILE, and the blob2db3dd44is base's). Declarations were then emitted fresh withtsc --emitDeclarationOnly --incremental falseinto a scratch directory.element-data-source/ElementDataSourceGate.d.tshas sha25673212725…4805305don both legs, andcmpexits 0. The whole 67-filepackages/reactdeclaration tree compares equal underdiff -r(exit 0). The file was restored with proof: blob212f6039equals HEAD, andgit diff HEADis empty.Step 4: nothing user-visible
No dead read surfaced, so there is no behaviour to pin. The runtime values handed down are unchanged at all five sites.
Pins: one type-level leg per site
There are five files, one beside each touched source file:
plugin-form/src/__tests__/schemaRendererContextRead-7209.test.ts(2 sites);plugin-list/src/__tests__/ListViewBlock.schemaRendererContextRead-7209.test.ts;plugin-view/src/__tests__/objectViewRenderer.schemaRendererContextRead-7209.test.ts;react/src/element-data-source/__tests__/useResolvedDataSource.schemaRendererContextRead-7209.test.ts;react/src/hooks/__tests__/useElementDataSource.schemaRendererContextRead-7209.test.ts(round 2).Why a
ts.Programleg rather than the@ts-expect-errorthe dispatch gave as an example. Each context value lives in a local inside its renderer. Nothing a test can import carries that value's type, so a directive in a test file stays satisfied when the cast is restored at the site.getTypeAtLocationon the hook call itself is the instrument that sees the cast. This follows the shaperecord-details.hideFieldsUncast-9965.test.tsestablished. No per-site pin followed theLookupFieldrepair inedea22a5: that commit pinned the context type itself (SchemaRendererContext.dataSourceType.pin.test.ts), not the read sites.For each site, the pin checks:
@object-ui/react's ownSchemaRendererContextexport in the same program and never written down. It must not beany.formValues, the member objectui#7206 retired, is not readable off the value: no property, no index signature, notany.dataSourcereads at its declared type.any, so every verdict above is one this instrument can reach.The program resolves
@object-ui/react,@object-ui/typesand@object-ui/coreto source, because test shards build nothing. It is built at module scope (import phase, not bounded by a test timeout), with parsed files shared between the real and the planted program. Run alone, one file imports in about 5 s. 29 tests across the five files, all green.Ablation, with on-disk proof (committed first;
ablation-replace.mjs, wrap mode, restore by trap)A: the card's shape. The
EmbeddableFormRenderercast was restored inplugin-form/src/index.tsx(anchor 1 to 0, replacement 0 to 1, blob059806a7tof8d4d505).Tests 3 failed | 6 passed. Exactly that site's three rows went red.MasterDetailFormRendererand the calibration rows stayed green."isAny": falseexpected,truereceived, and"type": "SchemaRendererContextType | null"expected,"any"received.059806a7equals HEAD, andgit diff HEADis empty.B: the alias shape, which the in-memory calibration does not cover. The module-level alias was restored in
plugin-view/src/index.tsx(blobd70dbed3tobf3b7660).Tests 3 failed | 2 passed, with the same three rows red.d70dbed3equals HEAD, andgit diff HEADis empty.The observed red is a failing test assertion, not an unused
@ts-expect-error, because the pin is a program leg. The harness reads source directly, so no build ordist/sits between the mutation and the verdict.Verification
On
e44132f6(the implementation commit):pnpm exec vitest run packages/plugin-form/ packages/plugin-list/ apps/console/src/__tests__/public-block-binding-reach.test.tsx:Test Files 200 passed (200),Tests 2159 passed | 1 skipped.pnpm exec vitest run packages/plugin-view/: 45 files, 409 passed.pnpm exec vitest run packages/react/: 93 files, 1173 passed..ts/.tsxfiles.eslint --format jsonreports all 8 as linted, none ignored: 0 errors. Every warning belongs to a pre-existing class in the touched files (react-refresh/only-export-components,no-explicit-any).eslint.config.jsenables no type-aware linting (noparserOptions.projectorprojectService), and no local rule undereslint-rules/reads the filesystem. So this diff cannot move a verdict on an untouched file.On
49a5e86d(the head, after mergingorigin/main31938f01; the merge touched none of this PR's files):turbo run buildof the dependency closure: 15/15.type-check(both tsconfigs) exits 0 for@object-ui/react,plugin-form,plugin-list,plugin-view,plugin-detailandplugin-grid. The last two importuseResolvedDataSource.--listFilesOnlyconfirms each touched package's test tsconfig compiles its new pin.public-block-binding-reach,ObjectView.dataSourceContextFallback-7842,EmbeddableForm,ElementDataSourceGate): 8 files, 120 passed.check:control-bytes,check:new-line-citations(0 new),check-changeset-presence,check:changeset-claims,check-changeset-no-major,check-changeset-fixed,check-changeset-overwrite,check-test-path-roots,check-vi-mock-override-shape,check:phantom-deps,check:element-data-source-declaration,check:self-import,check:esm-specifiers,check:unreferenced-sources,check-type-check-coverage,check-lint-coverage.check-governed-queue-guard --testover the 9 paths reports NOT GOVERNED.49a5e86d: the full package suites above were not re-run after the merge, because they ran one44132f6and the merge touched none of this PR's files. CI runs them on this head.Round 2 (
a879ffe6, onorigin/main1f8ef0a8)The seat widened the claim surface to the sixth site (Q1 = A).
Contexttype import are gone.anytoSchemaRendererContextType | null, with 0 diagnostics in thepackages/reactprogram.type-check(both tsconfigs) exits 0 for@object-ui/reactand for@object-ui/components, its only importer outside react.react/src/hooks/__tests__/useElementDataSource.schemaRendererContextRead-7209.test.ts, 5 rows.Contextimport on disk givesTest Files 1 failed | 4 passed (5)andTests 3 failed | 26 passed (29)across all five pin files; only that site's three rows go red. After restoring, blob4a01194cequals HEAD andgit diff HEADis empty.@object-ui/reactdeclaration tree is byte-identical betweenorigin/mainand head (diff -rexit 0;useElementDataSource.d.tssha2566d4d0b30…54d653a6on both). Lit control: a planteddataSource?: stringdoes show in that diff.packages/react/suites plus the pins give 97 files, 1197 passed.Changeset
.changeset/7209-schema-renderer-context-read-as-declared.mddeclarespatchfor@object-ui/plugin-form,@object-ui/plugin-list,@object-ui/plugin-viewand@object-ui/react. It is type-only: no runtime value changes anywhere.Acceptance notes
react/src/hooks/useElementDataSource.ts, was fixed in round 2. See 「Round 2」 above.check:changeset-claimsasked for one read..changeset/object-view-unmirrored-keys-7779.mdnamesplugin-view/src/index.tsxas the file that registers theobject-viewrenderer. I re-read that paragraph: it is still true, because the registration is unchanged.useContextof this context underpackages/*/srcas declared, would also catch sites nobody has written yet. It is not built here: it lies outside the fence, and a new guard is the seat's call.Generated by Claude Code