Found while implementing objectui#8497 (the sections[].group reference form). Filed unassigned; not fixed there — #8497's PR moves only the two members its own change needs (fields to optional, group added), because the rest is a pre-existing divergence that predates it and deserves its own decision.
The divergence
@object-ui/types packages/types/src/record-components.ts declares the record:details section entry as:
sections?: Array<{
name?: string;
label?: string;
fields?: string[]; // optional as of #8497
group?: string; // added by #8497
collapsible?: boolean;
collapsed?: boolean;
}>;
@objectstack/spec 17.3.0 (packages/spec/src/ui/component.zod.ts, RecordDetailsProps.sections) declares these members on the same entry:
name · label · columns · group · fields · hideEmpty · collapsible · showBorder · defaultCollapsed · icon · description · headerColor
Set difference, both directions:
| direction |
keys |
| spec declares, this type omits |
columns, icon, description, showBorder, defaultCollapsed, headerColor |
| this type declares, spec refuses |
collapsed |
Why it is not cosmetic
RecordDetailsRenderer honours the omitted keys — it spreads every authored member through to DetailSection, which reads columns, icon, description, showBorder, defaultCollapsed and headerColor. So the TypeScript face is the only layer that says no.
Measured while writing #8497's type pin, and it is how this was found: the literal
const props: RecordDetailsComponentProps = { sections: [{ group: 'terms', columns: 2 }] };
is accepted by @objectstack/spec, rendered correctly by this repo's renderer, and refused by tsc:
error TS2353: Object literal may only specify known properties,
and 'columns' does not exist in type '{ name?: ...; label?: ...; fields?: ...; group?: ...; collapsible?: ...; collapsed?: ... }'
⚠️ Worth recording separately: vitest was green on that same literal — it strips types — and only type-check saw it. A pin for a type-level claim that runs under the test project alone measures nothing.
collapsed runs the other way: it is declared here, no producer can emit it through a spec parse (the section object answers unrecognized_keys for it), and the spec's spelling for that state is defaultCollapsed, which this type does not have. So the one collapse-state key an author can write in TypeScript is the one the contract rejects, and the one the contract accepts does not compile.
Not a duplicate of
Suggested shape (not a ruling)
Converge the entry on the spec's member set: add the six, and decide collapsed deliberately — retire it, or keep it as a recorded legacy alias with the reason written down. ⛔ Retiring a published member narrows a shipped type, so it is a contract change owed its own decision rather than a rider; that is exactly why it is filed rather than folded into #8497.
Refs: objectui#8497 (where this was measured) · objectstack#13855 (the group-reference form) · objectui#7716 · objectui#6955
Filed by the domain:ui dev seat while working objectui#8497, session session_01YBWFb5YgMU5dw8p2VKj16S. Generated with Claude Code.
Found while implementing objectui#8497 (the
sections[].groupreference form). Filed unassigned; not fixed there — #8497's PR moves only the two members its own change needs (fieldsto optional,groupadded), because the rest is a pre-existing divergence that predates it and deserves its own decision.The divergence
@object-ui/typespackages/types/src/record-components.tsdeclares therecord:detailssection entry as:@objectstack/spec17.3.0 (packages/spec/src/ui/component.zod.ts,RecordDetailsProps.sections) declares these members on the same entry:name·label·columns·group·fields·hideEmpty·collapsible·showBorder·defaultCollapsed·icon·description·headerColorSet difference, both directions:
columns,icon,description,showBorder,defaultCollapsed,headerColorcollapsedWhy it is not cosmetic
RecordDetailsRendererhonours the omitted keys — it spreads every authored member through toDetailSection, which readscolumns,icon,description,showBorder,defaultCollapsedandheaderColor. So the TypeScript face is the only layer that says no.Measured while writing #8497's type pin, and it is how this was found: the literal
is accepted by
@objectstack/spec, rendered correctly by this repo's renderer, and refused bytsc:vitestwas green on that same literal — it strips types — and onlytype-checksaw it. A pin for a type-level claim that runs under the test project alone measures nothing.collapsedruns the other way: it is declared here, no producer can emit it through a spec parse (the section object answersunrecognized_keysfor it), and the spec's spelling for that state isdefaultCollapsed, which this type does not have. So the one collapse-state key an author can write in TypeScript is the one the contract rejects, and the one the contract accepts does not compile.Not a duplicate of
record:detailssection entries gained eight authorable keys in@objectstack/spec17.3.0 with no designer control — deferred feature card (#7122 ruling, item 5) #7716's absence means a key cannot be authored in Studio, this one means a key cannot be authored in TypeScript at all.detail-section'sheaderColorregistry input being free-formstring.Suggested shape (not a ruling)
Converge the entry on the spec's member set: add the six, and decide
collapseddeliberately — retire it, or keep it as a recorded legacy alias with the reason written down. ⛔ Retiring a published member narrows a shipped type, so it is a contract change owed its own decision rather than a rider; that is exactly why it is filed rather than folded into #8497.Refs: objectui#8497 (where this was measured) · objectstack#13855 (the group-reference form) · objectui#7716 · objectui#6955
Filed by the
domain:uidev seat while working objectui#8497, sessionsession_01YBWFb5YgMU5dw8p2VKj16S. Generated with Claude Code.