Measured while implementing objectui#6760 (the properties hoist), outside its fence — that
card is scoped to packages/react/src/ — so filed separately. Observation-class in the same
sense objectui#6752 and objectui#6760 were: nothing is known to depend on it today.
The fact, measured on c6732825d
objectui#6752 stopped a degenerate props reaching the element as indexed React props;
objectui#6760 stopped a degenerate properties being hoisted into indexed node keys. Both
sites live in packages/react/src/SchemaRenderer.tsx. A third site reads the same two
authored bags and still object-spreads them, in a different package and on a different
channel — the config bag a renderer reads, not the React props it receives.
Five copies of readProps(), all in packages/components/src/renderers/basic/:
elements.tsx
data-list.tsx
text-input.tsx
record-picker.tsx
metadata-viewer.tsx
Each is the same body:
const fromProperties = (schema?.properties ?? {}) as T;
const fromProps = (schema?.props ?? {}) as T;
return { ...fromProps, ...fromProperties };
?? only replaces null / undefined, so a degenerate value passes straight into the
spread. Measured: Object.keys({ ...'not-a-bag' }) is ["0","1","2","3","4","5","6","7","8"].
The node reaches these renderers carrying the authored value — measured through the real
SchemaRenderer in objectui#6760's probe on the same commit: for
{ type, properties: 'not-a-bag' }, a renderer reading schema sees
schema.properties === 'not-a-bag', and after objectui#6760 that value is no longer
enumerated anywhere upstream. So the config bag one of these five renderers computes for that
node is { '0': 'n', '1': 'o', … } — nine keys nobody authored, which is exactly the shape
the two react-side cards removed on the other two channels.
Why it is worth recording rather than shipping quietly
The hazard is now closed on two of three channels for both spellings of one bag, and open on
the third. That asymmetry is the same thing objectui#6760's triage flagged about the previous
one: the reason a config-bag guard exists was measured to be channel-independent
(objectui#6752), so a site that answers the question differently is drift, not a local choice.
packages/react ended that drift for itself by converging on one exported isConfigBag
(objectui#6761); packages/components has five copies of a reader that never asks it.
Blast radius, honestly bounded
Same denominator as the sibling cards and the same census (objectui#6708 walked every JSON
document, every json doc fence and every TypeScript object literal): zero authored nodes
carry a degenerate config bag. Latent shape, not a live failure.
Arms, if anyone takes it
- have the five
readProps() copies ask the same question the react side asks, and converge
them the way objectui#6761 converged its six (note @object-ui/components does not depend
on @object-ui/react, so "import isConfigBag" is not automatically the shape — where the
one definition should live is part of the card);
- or rule that a renderer-side config-bag read may reinterpret whatever it is handed, and say
so at the reader.
Not picked here — objectui#6760's fence is packages/react/src/ only.
Related: objectui#6760 (the hoist half), objectui#6752 (the props half), objectui#6761 (one
predicate in packages/react), objectui#6708 (the census).
Generated by Claude Code
Measured while implementing objectui#6760 (the
propertieshoist), outside its fence — thatcard is scoped to
packages/react/src/— so filed separately. Observation-class in the samesense objectui#6752 and objectui#6760 were: nothing is known to depend on it today.
The fact, measured on
c6732825dobjectui#6752 stopped a degenerate
propsreaching the element as indexed React props;objectui#6760 stopped a degenerate
propertiesbeing hoisted into indexed node keys. Bothsites live in
packages/react/src/SchemaRenderer.tsx. A third site reads the same twoauthored bags and still object-spreads them, in a different package and on a different
channel — the config bag a renderer reads, not the React props it receives.
Five copies of
readProps(), all inpackages/components/src/renderers/basic/:elements.tsxdata-list.tsxtext-input.tsxrecord-picker.tsxmetadata-viewer.tsxEach is the same body:
??only replacesnull/undefined, so a degenerate value passes straight into thespread. Measured:
Object.keys({ ...'not-a-bag' })is["0","1","2","3","4","5","6","7","8"].The node reaches these renderers carrying the authored value — measured through the real
SchemaRendererin objectui#6760's probe on the same commit: for{ type, properties: 'not-a-bag' }, a renderer readingschemaseesschema.properties === 'not-a-bag', and after objectui#6760 that value is no longerenumerated anywhere upstream. So the config bag one of these five renderers computes for that
node is
{ '0': 'n', '1': 'o', … }— nine keys nobody authored, which is exactly the shapethe two react-side cards removed on the other two channels.
Why it is worth recording rather than shipping quietly
The hazard is now closed on two of three channels for both spellings of one bag, and open on
the third. That asymmetry is the same thing objectui#6760's triage flagged about the previous
one: the reason a config-bag guard exists was measured to be channel-independent
(objectui#6752), so a site that answers the question differently is drift, not a local choice.
packages/reactended that drift for itself by converging on one exportedisConfigBag(objectui#6761);
packages/componentshas five copies of a reader that never asks it.Blast radius, honestly bounded
Same denominator as the sibling cards and the same census (objectui#6708 walked every JSON
document, every
jsondoc fence and every TypeScript object literal): zero authored nodescarry a degenerate config bag. Latent shape, not a live failure.
Arms, if anyone takes it
readProps()copies ask the same question the react side asks, and convergethem the way objectui#6761 converged its six (note
@object-ui/componentsdoes not dependon
@object-ui/react, so "importisConfigBag" is not automatically the shape — where theone definition should live is part of the card);
so at the reader.
Not picked here — objectui#6760's fence is
packages/react/src/only.Related: objectui#6760 (the hoist half), objectui#6752 (the
propshalf), objectui#6761 (onepredicate in
packages/react), objectui#6708 (the census).Generated by Claude Code