Found while implementing #4440 (converging @object-ui/core's rowHeightToDensityMode with this bridge). Not fixed there: packages/react/src/spec-bridge/** was outside that card's surface, and this is a different defect class from the coercion #4440 retired.
What
packages/react/src/spec-bridge/bridges/list-view.ts:
function mapDensity(
rowHeight?: RowHeight,
): 'compact' | 'comfortable' | 'spacious' | undefined {
if (!rowHeight) return undefined;
return ROW_HEIGHT_TO_DENSITY[rowHeight];
}
ROW_HEIGHT_TO_DENSITY is a plain object literal, so the index reaches Object.prototype. The parameter is typed RowHeight, but the boundary a host's stored JSON actually crosses is SpecBridge.transformListView, whose parameter is any — that is the untyped entry PR #4439's own pins use.
So:
new SpecBridge().transformListView({ name: 'x', rowHeight: 'toString' }).density
// → [Function toString]
bridgeListView writes if (density) node.density = density, and a function is truthy, so the key IS written: a SchemaNode carrying a function where the renderer expects 'compact' | 'comfortable' | 'spacious'. Same for constructor, valueOf, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString.
Measured directly, on the core twin which had the identical in-based hole before #4440 fixed it:
AssertionError: expected [Function toString] to be undefined
- Expected: undefined
+ Received: [Function toString]
Why it is filed rather than fixed in #4440
#4440's ruling scoped the work to rowHeightToDensityMode and its callers, and named packages/react/src/spec-bridge/** read-only for that dispatch. #4440's agreement pin (packages/react/src/spec-bridge/__tests__/RowHeightDensityAgreement.test.ts) therefore covers the off-spec spellings the card is about and deliberately excludes prototype keys, with a comment pointing here — because on those keys the two surfaces still disagree: core now abstains, the bridge returns a function.
Impact
No user hits this today. It needs a host to author or store the literal string toString (or another Object.prototype member) as a view's rowHeight; the three-repo sweep in PR #4439, re-run at newer HEADs for #4440, found no off-spec rowHeight authored anywhere at all. Grading it is triage's call, not the filer's — an unguarded prototype-chain index is a recognized bug class and the observable result is a function leaking into a schema node, which is why this is filed as a defect rather than as an observation.
The fix, when someone takes it
One line, matching what @object-ui/core now does (packages/core/src/utils/normalize-list-view.ts) and the repo's existing convention (Object.prototype.hasOwnProperty.call(...) — used in core/src/utils/freeze-schema.ts:135, app-shell/src/views/metadata-admin/predicate.ts:305, and six other places):
if (!rowHeight) return undefined;
if (!Object.prototype.hasOwnProperty.call(ROW_HEIGHT_TO_DENSITY, rowHeight)) return undefined;
return ROW_HEIGHT_TO_DENSITY[rowHeight];
Worth extending #4440's agreement pin to the prototype keys in the same PR, since that is exactly the assertion this currently breaks.
Generated by Claude Code
Found while implementing #4440 (converging
@object-ui/core'srowHeightToDensityModewith this bridge). Not fixed there:packages/react/src/spec-bridge/**was outside that card's surface, and this is a different defect class from the coercion #4440 retired.What
packages/react/src/spec-bridge/bridges/list-view.ts:ROW_HEIGHT_TO_DENSITYis a plain object literal, so the index reachesObject.prototype. The parameter is typedRowHeight, but the boundary a host's stored JSON actually crosses isSpecBridge.transformListView, whose parameter isany— that is the untyped entry PR #4439's own pins use.So:
bridgeListViewwritesif (density) node.density = density, and a function is truthy, so the key IS written: aSchemaNodecarrying a function where the renderer expects'compact' | 'comfortable' | 'spacious'. Same forconstructor,valueOf,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString.Measured directly, on the core twin which had the identical
in-based hole before #4440 fixed it:Why it is filed rather than fixed in #4440
#4440's ruling scoped the work to
rowHeightToDensityModeand its callers, and namedpackages/react/src/spec-bridge/**read-only for that dispatch. #4440's agreement pin (packages/react/src/spec-bridge/__tests__/RowHeightDensityAgreement.test.ts) therefore covers the off-spec spellings the card is about and deliberately excludes prototype keys, with a comment pointing here — because on those keys the two surfaces still disagree: core now abstains, the bridge returns a function.Impact
No user hits this today. It needs a host to author or store the literal string
toString(or anotherObject.prototypemember) as a view'srowHeight; the three-repo sweep in PR #4439, re-run at newer HEADs for #4440, found no off-specrowHeightauthored anywhere at all. Grading it is triage's call, not the filer's — an unguarded prototype-chain index is a recognized bug class and the observable result is a function leaking into a schema node, which is why this is filed as a defect rather than as an observation.The fix, when someone takes it
One line, matching what
@object-ui/corenow does (packages/core/src/utils/normalize-list-view.ts) and the repo's existing convention (Object.prototype.hasOwnProperty.call(...)— used incore/src/utils/freeze-schema.ts:135,app-shell/src/views/metadata-admin/predicate.ts:305, and six other places):Worth extending #4440's agreement pin to the prototype keys in the same PR, since that is exactly the assertion this currently breaks.
Generated by Claude Code