You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(react): SchemaRenderer carries the #4422 prop erasure in a spelling every sweep and both guards are blind to — Record< string, any > instead of [key: string]: any #4548
This is the identical defect #4422 measured and PR #4438 fixed in packages/components, and that #4528 swept out of plugin-dashboard / plugin-list. Record< string, any > puts string into keyof Props exactly as [key: string]: any does, so 'ref' extends keyof Props is always true, React's PropsWithoutRef takes its Omit branch, and Omit over a type carrying a string index signature keeps only the index signature. Every declared prop is erased — here that is schema itself, so the render function reads schema as any.
Measured on current main, running #4438's collector over every package src (220 forwardRef sites, 18 of them schema-reading):
A offenders — index signature on type arg:
(none)
A offenders — unannotated param:
packages/react/src/SchemaRenderer.tsx:204
So the site is invisible to the assertion that would name the cause, and shows up only on the annotation assertion — which reads as a style nit rather than as "every declared prop of this component is erased".
Why this matters more here than at the other 18 sites
SchemaRenderer is the renderer loop: every registered SDUI component is rendered through it, and it is the thing that hands widgets their props. Its own props being erased is the least visible and most central instance of the pattern.
#4528's third direction — one guard over every package src, so "the third survivor" cannot arrive the way these two did — is blocked on this. A naive widening of the guard goes red on packages/react immediately (the unannotated-parameter assertion above), which is why #4528 shipped per-package siblings instead and said so in both guard headers. Widening should be sequenced after this site is decided, and the widening should also teach hasStringIndexSignature about Record< string, ... > (and any other mapped-type spelling) or it will keep passing on the shape it most needs to catch.
Observation-class finding, surfaced while fixing #4528 (the plugin-dashboard / plugin-list sweep). Nothing a user meets today; no fix proposed here.
The site
packages/react/src/SchemaRenderer.tsx:204This is the identical defect #4422 measured and PR #4438 fixed in
packages/components, and that #4528 swept out ofplugin-dashboard/plugin-list.Record< string, any >putsstringintokeyof Propsexactly as[key: string]: anydoes, so'ref' extends keyof Propsis always true, React'sPropsWithoutReftakes itsOmitbranch, andOmitover a type carrying a string index signature keeps only the index signature. Every declared prop is erased — here that isschemaitself, so the render function readsschemaasany.Why nothing has caught it
Two independent blind spots, and they compound:
The sweeps grep for the wrong string. finding(plugin-dashboard, plugin-list): the two packages #4422 left unswept still erase every declared prop — and the #4438 guard cannot see them #4528's sweep method was "every non-test source under a package
srcthat mentionsforwardRefand declares[key: string]: any" — 18 files.Record< string, any >does not match that grep, so this site was never in any candidate list.The guards' detector is syntactic.
hasStringIndexSignaturein both refactor(components): the action keys publish UIActionSchema, and every forwardRef renderer annotates its props (#4418, #4422) #4438's guard and the two finding(plugin-dashboard, plugin-list): the two packages #4422 left unswept still erase every declared prop — and the #4438 guard cannot see them #4528 siblings walks for ats.isIndexSignatureDeclarationmember, resolving type references only through types declared in the same file.Recordis a global mapped type, solocalTypes.get('Record')misses and the function returnsfalse. The guard reports this site as CLEAN.Measured on current
main, running #4438's collector over every packagesrc(220forwardRefsites, 18 of themschema-reading):So the site is invisible to the assertion that would name the cause, and shows up only on the annotation assertion — which reads as a style nit rather than as "every declared prop of this component is erased".
Why this matters more here than at the other 18 sites
SchemaRendereris the renderer loop: every registered SDUI component is rendered through it, and it is the thing that hands widgets their props. Its own props being erased is the least visible and most central instance of the pattern.Consequence for #4528 direction 3
#4528's third direction — one guard over every package
src, so "the third survivor" cannot arrive the way these two did — is blocked on this. A naive widening of the guard goes red onpackages/reactimmediately (the unannotated-parameter assertion above), which is why #4528 shipped per-package siblings instead and said so in both guard headers. Widening should be sequenced after this site is decided, and the widening should also teachhasStringIndexSignatureaboutRecord< string, ... >(and any other mapped-type spelling) or it will keep passing on the shape it most needs to catch.Refs #4422, #4438, #4528, #4426.
Generated by Claude Code