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
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(react): mapDensity still coerces a non-string rowHeight to a density, where the core twin abstains #4459
Found while implementing #4442 (PR #4457, the hasOwnProperty guard on mapDensity). Not fixed there: #4442's ruling scoped the work to the prototype-chain index and the prototype-member spellings, and this is a different defect class — coercion, not an unguarded prototype read. Same relationship #4442 itself had to #4440.
What
After #4442 lands, packages/react/src/spec-bridge/bridges/list-view.ts reads:
The first guard rejects only falsy values. Any truthy non-string survives it, and both hasOwnProperty.call and the index then coerce the key with String(...) — so a value that is not a string at all can still select a density.
The core twin does not do this. packages/core/src/utils/normalize-list-view.ts:83 opens with a type guard, not a truthiness guard:
So the two surfaces disagree again, on a different input family:
rowHeight
core
bridge
['compact']
undefined
'compact'
new String('compact')
undefined
'compact'
{ toString: () => 'compact' }
undefined
'compact'
Measured through SpecBridge.transformListView (whose parameter is any — the untyped boundary a host's stored JSON actually crosses) while implementing #4442, and reproduced standalone against both function bodies.
Note the direction: this is the opposite of #4442. There the bridge leaked a function where core abstained; here the bridge invents a legitimate-looking density where core abstains. The second is quieter — nothing downstream can tell the value was fabricated from an array.
Why it matters beyond the two functions
packages/react/src/spec-bridge/__tests__/RowHeightDensityAgreement.test.ts is titled "core and the spec bridge give one answer", and after #4442 its off-spec lists are all strings. The invariant the file claims is therefore broader than the invariant it pins, and this is the gap. A JSON-authored view can hold an array or an object at rowHeight just as easily as a bad string.
Impact
No user hits this today, on the same evidence as #4442: the three-repo sweep in PR #4439, re-run at newer HEADs for #4440, found no off-spec rowHeight authored anywhere at all — let alone a non-string one. Filed as a defect rather than an observation because it is a live disagreement between two published surfaces that a dedicated pin file exists to prevent, but grading it is triage's call, not the filer's.
The fix, when someone takes it
Mirror core's opening guard, one line:
if(typeofrowHeight!=='string')returnundefined;
replacing the if (!rowHeight) truthiness check ('' is falsy and not an own property, so the empty-string case is unaffected either way). Worth extending the agreement pin's invariant loop to a non-string family (['compact'], new String('compact'), an object with a toString) in the same PR, since that is the assertion this currently breaks.
Ruling (delegated authority; open veto window): fix as filed. Replace mapDensity's truthiness guard with core's type guard (typeof rowHeight !== 'string' → abstain), and extend the agreement pin's invariant loop with a non-string family (['compact'], new String('compact'), { toString: () => 'compact' }) — that is the assertion the current disagreement breaks, and it closes the gap between the pin file's title claim and what it pins. Empty-string behavior unchanged either way (falsy AND not an own property).
Red-first: pre-fix transformListView({ name: 'x', rowHeight: ['compact'] }).density is 'compact' (measured in the issue body, via the any-typed boundary); post-fix undefined, agreeing with core. The direction that must NOT change: all five spec strings, and the string off-spec/prototype families #4447/#4457 pinned.
Grading: runtime-only, off-spec non-string input — changeset patch for @object-ui/react unless the emitted .d.ts moves (it should not).
Surface (mutual exclusion): packages/react/src/spec-bridge/** + one changeset. Base is current main (PR #4457 landed, so the surface is free). Disjoint from the only other in-flight card #4456 (eslint config / e2e).
Activity
yinlianghui commentedon Aug 12, 2026
CLAIM — session
session_017Qqyix2QcnpUC9XeYVDzx3(objectui whole-repo seat), branchclaude/issue-4459-bridge-nonstring-guard.Ruling (delegated authority; open veto window): fix as filed. Replace
mapDensity's truthiness guard with core's type guard (typeof rowHeight !== 'string'→ abstain), and extend the agreement pin's invariant loop with a non-string family (['compact'],new String('compact'),{ toString: () => 'compact' }) — that is the assertion the current disagreement breaks, and it closes the gap between the pin file's title claim and what it pins. Empty-string behavior unchanged either way (falsy AND not an own property).Red-first: pre-fix
transformListView({ name: 'x', rowHeight: ['compact'] }).densityis'compact'(measured in the issue body, via theany-typed boundary); post-fixundefined, agreeing with core. The direction that must NOT change: all five spec strings, and the string off-spec/prototype families #4447/#4457 pinned.Grading: runtime-only, off-spec non-string input — changeset patch for
@object-ui/reactunless the emitted.d.tsmoves (it should not).Surface (mutual exclusion):
packages/react/src/spec-bridge/**+ one changeset. Base is current main (PR #4457 landed, so the surface is free). Disjoint from the only other in-flight card #4456 (eslint config / e2e).Generated by Claude Code
docs(react): cite core's twin by file path in the mapDensity comment
fix(react): mapDensity abstains on non-string rowHeight instead of co…