Found by the os-dev seat while implementing batch 3 of objectui#6349 (the ComboboxOption convergence). Out of that card's scope: it is a behaviour defect, and #6349 only moves which file declares the name. Grading and domain:* are the triage seat's.
The fact
options[].disabled on a combobox node is declared, validated, and never read.
- Declared:
packages/types/src/form.ts — ComboboxOption.disabled?: boolean ("Whether option is disabled").
- Validated:
packages/types/src/zod/form.zod.ts — ComboboxOptionSchema carries disabled: z.boolean().optional(), and packages/types/src/__tests__/disabled-twin-symmetry-7087.test.ts pins it as boolean on both faces.
- Passed through:
packages/components/src/renderers/form/combobox.tsx hands schema.options straight to the Combobox component.
- Never read:
packages/components/src/custom/combobox.tsx maps each option to a CommandItem with key, value, onSelect, the check icon and the label. option.disabled has zero read sites in the component (measured: grep -c 'option.disabled' is 0; the only disabled the component reads is its own whole-control prop, forwarded to the trigger button).
The underlying primitive would honour it: CommandItem (packages/components/src/ui/command.tsx) forwards props to cmdk's Command.Item, which takes a disabled prop, and the wrapper's className already styles data-[disabled=true] with reduced opacity and no pointer events.
Consequence
An authored option { value, label, disabled: true } passes safeValidateSchema, type-checks against the published ComboboxSchema, and renders as an ordinary selectable option. The key is a confident phantom — declared on the authorable surface with no read site behind it — the class the enforce-or-remove ledgers exist to close.
After #6349 batch 3 the ComboboxOption that @object-ui/components exports IS the @object-ui/types declaration (a re-export), so the member is now visible on the component's own face too. That PR deliberately changes no behaviour; this card is where the behaviour question lives.
Two remedies, neither local to this card
- Honour it —
CommandItem disabled={option.disabled} and refuse the select in onSelect for a disabled option; pin with a render test that a disabled option carries data-disabled and does not fire onValueChange. Check the sibling SelectOption.disabled read path in the select renderer for the house precedent before choosing the spelling.
- Retire it — remove
disabled from ComboboxOption in types and from the zod mirror. This narrows a published surface (clause 2 content) and would need the twin-symmetry pin updated.
Reproduce
grep -n 'disabled' packages/components/src/custom/combobox.tsx
grep -n 'disabled' packages/types/src/zod/form.zod.ts | grep -i combobox
The first shows only the whole-control disabled; the second shows the option-level key the mirror accepts.
Related
- objectui#6349 — batch 3 converges the
ComboboxOption NAME; this card is about the MEMBER.
- objectui#7087 — the
disabled twin-symmetry ruling that keeps this member boolean on both faces.
Filed by the os-dev seat for objectui#6349 batch 3, session session_01KbJQ1y1J12nZxYzFWhP8Q3 (Claude Code).
Generated by Claude Code
Found by the os-dev seat while implementing batch 3 of objectui#6349 (the
ComboboxOptionconvergence). Out of that card's scope: it is a behaviour defect, and #6349 only moves which file declares the name. Grading anddomain:*are the triage seat's.The fact
options[].disabledon acomboboxnode is declared, validated, and never read.packages/types/src/form.ts—ComboboxOption.disabled?: boolean("Whether option is disabled").packages/types/src/zod/form.zod.ts—ComboboxOptionSchemacarriesdisabled: z.boolean().optional(), andpackages/types/src/__tests__/disabled-twin-symmetry-7087.test.tspins it asbooleanon both faces.packages/components/src/renderers/form/combobox.tsxhandsschema.optionsstraight to theComboboxcomponent.packages/components/src/custom/combobox.tsxmaps each option to aCommandItemwithkey,value,onSelect, the check icon and the label.option.disabledhas zero read sites in the component (measured:grep -c 'option.disabled'is 0; the onlydisabledthe component reads is its own whole-control prop, forwarded to the trigger button).The underlying primitive would honour it:
CommandItem(packages/components/src/ui/command.tsx) forwards props to cmdk'sCommand.Item, which takes adisabledprop, and the wrapper's className already stylesdata-[disabled=true]with reduced opacity and no pointer events.Consequence
An authored option
{ value, label, disabled: true }passessafeValidateSchema, type-checks against the publishedComboboxSchema, and renders as an ordinary selectable option. The key is a confident phantom — declared on the authorable surface with no read site behind it — the class the enforce-or-remove ledgers exist to close.After #6349 batch 3 the
ComboboxOptionthat@object-ui/componentsexports IS the@object-ui/typesdeclaration (a re-export), so the member is now visible on the component's own face too. That PR deliberately changes no behaviour; this card is where the behaviour question lives.Two remedies, neither local to this card
CommandItem disabled={option.disabled}and refuse the select inonSelectfor a disabled option; pin with a render test that a disabled option carriesdata-disabledand does not fireonValueChange. Check the siblingSelectOption.disabledread path in the select renderer for the house precedent before choosing the spelling.disabledfromComboboxOptionin types and from the zod mirror. This narrows a published surface (clause 2 content) and would need the twin-symmetry pin updated.Reproduce
The first shows only the whole-control
disabled; the second shows the option-level key the mirror accepts.Related
ComboboxOptionNAME; this card is about the MEMBER.disabledtwin-symmetry ruling that keeps this memberbooleanon both faces.Filed by the os-dev seat for objectui#6349 batch 3, session
session_01KbJQ1y1J12nZxYzFWhP8Q3(Claude Code).Generated by Claude Code