Skip to content

finding(examples): a catalog menu item authors variant: "destructive", a key no arm of MenuItem declares and no menu renderer reads — but the repair has a real fork #7101

Description

@os-warren

Filed unassigned by the os-dev seat while implementing #7072 (session session_012wwHa4aaFybxXrfmfHioDM). Grading and domain:* are the triage seat's. ⛔ Deliberately NOT folded into #7072's PR — different key, and unlike value this one is not a mechanical delete.

The defect

Measured on origin/main 2c3cd1b:

examples/schema-catalog/src/schemas/components-overlay-dropdown-menu/with-icons.json authors one menu item as

{ "label": "Delete", "value": "delete", "icon": "trash", "variant": "destructive" }

value is #7072's population and is being deleted there. variant is a separate, still-live instance of the same class: 1 occurrence, and no card covers it.

The authority

  • MenuCommandItem (packages/types/src/overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No variant.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No variant.
  • The zod mirror MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) declares no variant on either arm, and builds both from bare non-strict z.objects, so it strips the key and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness.
  • packages/components/src/renderers/overlay/dropdown-menu.tsx:47-80 maps items reading exactly item.separator, item.icon, item.children, item.label, item.disabled, item.onClick, item.shortcut. There is no spread (no {...item}), so an undeclared key cannot reach the DOM by accident.

The only variant token in the three menu renderers is dropdown-menu.tsx:130, inside a trigger button default ({ type: 'button', label: 'Menu', variant: 'outline' }) — a button's variant, not a menu item's.

⚠️ Why this is NOT a copy of #7072's ruling

#7072 was ruled "delete, do not declare" because nothing wanted value. That reasoning does not transfer:

  • shadcn/ui's DropdownMenuItem genuinely ships a variant="destructive", which renders the row in the destructive colour. So the fixture is not spelling a nonsense key — it is asking for a real, upstream-supported affordance that this wrapper has not surfaced.
  • The demo is named with-icons and its third row is Delete. A red Delete row is very plausibly the intended teaching, and the catalog is a declared AI few-shot retrieval source.

⇒ Two defensible, opposite repairs:

This is the #6829 shape — the class rediscovering itself on a key whose fix is not uniquely determined — so it needs a decision, not a hand edit. I did not guess.

Class context

#6810's second ruling (2026-08-30, maintainer, verbatim「同意」) adopted the middle state: ⛔ no general extractor; for keys not seen before, accept option C — 人手撞见、逐例修 + 补钉. This is exactly such a hand rediscovery, and it is the seventh instance of the class on a fifth distinct key (contentchildrentypevaluevariant).

Refs: #7072 (the value half, same fixture) · #6810 (class decision + ruling) · #6523 (narrowed this union) · #6249 (declined a comparable capability expansion) · #6829 (the two-defensible-fixes precedent) · #5250 · #6902.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions