Skip to content

BulkActionParam.options is typed closed while its own runtime path and destination type are open #3309

Description

@xuyushun441-sys

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions