Problem
Whether a component binds via the type props = {...} + external make: React.component<props> form, or the classic @react.component external make: (~p: t=?, …) => React.element labeled-argument form, is decided entirely by recordProps = !!(ir.attrsBase && ir.attrsBase.ref) || baseSpreads.length > 0 (src/emit.mjs, around the "3. the external binding" comment). Record-props only happens when the component's props extend a recognized *HTMLAttributes interface, or share a base type with sibling components (#82/#131). A component with a plain, self-contained props object — no HTML-attrs extension, no shared base — always falls to labeled args, even though the two forms are behaviorally identical (@react.component on labeled args auto-derives the same props record internally; JSX call sites are unaffected either way).
Concretely, in @juspay/blend-design-system: Button's props extend ButtonHTMLAttributes<HTMLButtonElement> → gets type props = {...HtmlAttrs.buttonHTMLAttributesOmitClassNameDisabledOnClickStyle, ...} + React.component<props>. AccordionItem's props (value, title, subtext?, …) are a plain object type with no extends clause → gets the labeled-args form, and no nameable props type is ever emitted for it.
Impact
Medium — a plain type props = {...} is what lets a consumer write a thin wrapper component around a binding, e.g.:
module Button = {
type props = JuspayRescriptBlend.Button.props
@react.componentWithProps
let make = (props: props) => {
let handleClick = e => { /* analytics, then */ props.onClick->Option.forEach(f => f(e)) }
<JuspayRescriptBlend.Button {...props} onClick=handleClick />
}
}
This only compiles because Button.res exports a nameable props type. Components emitted via the labeled-args form (like AccordionItem) have no such type to reference, so this wrapper-and-override pattern — spread all props through, override just one (onClick, renderItem, …) — isn't possible for them today, even though nothing about their actual prop shape prevents it.
Suggested fix
Make record-props emission independent of attrsBase/baseSpreads — e.g. default to type props = {...} + React.component<props> for every component (ir.props.length > 0), keeping the labeled-args form only as an explicit opt-out (mirroring how --no-html-attrs already opts out of the HTML-attrs spread specifically). Off the top: the ~102 goldens that currently assert the labeled form for non-attrsBase components would need updating, and docs/TYPE_MAPPING.md would need a note that record-props is now unconditional (or gated behind a new default-on flag, if a transition period is wanted).
Problem
Whether a component binds via the
type props = {...}+external make: React.component<props>form, or the classic@react.component external make: (~p: t=?, …) => React.elementlabeled-argument form, is decided entirely byrecordProps = !!(ir.attrsBase && ir.attrsBase.ref) || baseSpreads.length > 0(src/emit.mjs, around the "3. the external binding" comment). Record-props only happens when the component's props extend a recognized*HTMLAttributesinterface, or share a base type with sibling components (#82/#131). A component with a plain, self-contained props object — no HTML-attrs extension, no shared base — always falls to labeled args, even though the two forms are behaviorally identical (@react.componenton labeled args auto-derives the same props record internally; JSX call sites are unaffected either way).Concretely, in
@juspay/blend-design-system:Button's props extendButtonHTMLAttributes<HTMLButtonElement>→ getstype props = {...HtmlAttrs.buttonHTMLAttributesOmitClassNameDisabledOnClickStyle, ...}+React.component<props>.AccordionItem's props (value,title,subtext?, …) are a plain object type with noextendsclause → gets the labeled-args form, and no nameablepropstype is ever emitted for it.Impact
Medium — a plain
type props = {...}is what lets a consumer write a thin wrapper component around a binding, e.g.:This only compiles because
Button.resexports a nameablepropstype. Components emitted via the labeled-args form (likeAccordionItem) have no such type to reference, so this wrapper-and-override pattern — spread all props through, override just one (onClick,renderItem, …) — isn't possible for them today, even though nothing about their actual prop shape prevents it.Suggested fix
Make record-props emission independent of
attrsBase/baseSpreads— e.g. default totype props = {...}+React.component<props>for every component (ir.props.length > 0), keeping the labeled-args form only as an explicit opt-out (mirroring how--no-html-attrsalready opts out of the HTML-attrs spread specifically). Off the top: the ~102 goldens that currently assert the labeled form for non-attrsBase components would need updating, anddocs/TYPE_MAPPING.mdwould need a note that record-props is now unconditional (or gated behind a new default-on flag, if a transition period is wanted).