Found while verifying the spec side of objectstack-ai/objectstack#4001 (bulk-action options → .passthrough()). Not a runtime bug today — the runtime already does the right thing. It is a declaration that disagrees with the code beneath it, which is the shape that makes an authoring mistake invisible later.
The three layers disagree
| Layer |
File:line |
Says |
| Type |
packages/types/src/objectql.ts:271 |
options?: Array< { label: string; value: string | number | boolean } > — closed, no index signature |
| Runtime |
packages/plugin-grid/src/components/bulkParamToField.ts:131 |
options?.map(o => ({ ...o, value: String(o.value) })) — spreads, every extra key survives |
| Destination |
packages/types/src/field-types.ts:288 |
SelectOptionMetadata declares color / icon / disabled / visibleWhen beyond the pair |
And the destination keys are genuinely read — option?.color at packages/fields/src/index.tsx:1089 and :1101 (dot appearance and badge classes).
Note the sibling key one level up gets this right: BulkActionParam itself declares [key: string]: unknown with the comment "Catch-all for extra widget-specific configuration (min/max/step/format/...). Forwarded to the underlying field renderer as-is." The option entry inside it never got the same treatment.
Why it is worth fixing rather than leaving
An author writing options: [{ label: 'In Review', value: 'in_review', color: '#8B5CF6' }] in TypeScript gets an excess-property error from a type that says the key is not allowed, while the renderer would have honoured it. So the type is currently the only thing rejecting a working configuration — the reverse of the usual failure, and the kind of signal an AI author trusts absolutely.
The framework side was just corrected in the other direction: BulkActionParamSchema's options[] entry is now .passthrough() (objectstack-ai/objectstack#4001, maintainer verdict A), because the spec was silently stripping those keys at parse. With that landed, the spec accepts them, the renderer forwards them, the widget reads them — and only this interface still says no.
Suggested fix (owner's call)
Either give the entry the same catch-all its parent has:
options?: Array< { label: string; value: string | number | boolean; [key: string]: unknown } >;
…or type it as the thing it actually becomes (SelectOptionMetadata-shaped), which additionally documents color / icon / disabled / visibleWhen at the authoring site instead of leaving them to be discovered in the renderer. The second is more informative; the first is the smaller change and matches the parent's existing idiom.
Filed unassigned per the framework repo's Prime Directive #10 (out-of-scope finding, recorded rather than fixed in the PR that found it). No objectui code was changed by that PR — it was read-only there.
Found while verifying the spec side of objectstack-ai/objectstack#4001 (bulk-action
options→.passthrough()). Not a runtime bug today — the runtime already does the right thing. It is a declaration that disagrees with the code beneath it, which is the shape that makes an authoring mistake invisible later.The three layers disagree
packages/types/src/objectql.ts:271options?: Array< { label: string; value: string | number | boolean } >— closed, no index signaturepackages/plugin-grid/src/components/bulkParamToField.ts:131options?.map(o => ({ ...o, value: String(o.value) }))— spreads, every extra key survivespackages/types/src/field-types.ts:288SelectOptionMetadatadeclarescolor/icon/disabled/visibleWhenbeyond the pairAnd the destination keys are genuinely read —
option?.coloratpackages/fields/src/index.tsx:1089and:1101(dot appearance and badge classes).Note the sibling key one level up gets this right:
BulkActionParamitself declares[key: string]: unknownwith the comment "Catch-all for extra widget-specific configuration (min/max/step/format/...). Forwarded to the underlying field renderer as-is." The option entry inside it never got the same treatment.Why it is worth fixing rather than leaving
An author writing
options: [{ label: 'In Review', value: 'in_review', color: '#8B5CF6' }]in TypeScript gets an excess-property error from a type that says the key is not allowed, while the renderer would have honoured it. So the type is currently the only thing rejecting a working configuration — the reverse of the usual failure, and the kind of signal an AI author trusts absolutely.The framework side was just corrected in the other direction:
BulkActionParamSchema'soptions[]entry is now.passthrough()(objectstack-ai/objectstack#4001, maintainer verdict A), because the spec was silently stripping those keys at parse. With that landed, the spec accepts them, the renderer forwards them, the widget reads them — and only this interface still says no.Suggested fix (owner's call)
Either give the entry the same catch-all its parent has:
…or type it as the thing it actually becomes (
SelectOptionMetadata-shaped), which additionally documentscolor/icon/disabled/visibleWhenat the authoring site instead of leaving them to be discovered in the renderer. The second is more informative; the first is the smaller change and matches the parent's existing idiom.Filed unassigned per the framework repo's Prime Directive #10 (out-of-scope finding, recorded rather than fixed in the PR that found it). No objectui code was changed by that PR — it was read-only there.