docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks - #7084

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs
Aug 31, 2026
Merged

docs(components): follow the shipped MenuItem union on the dropdown-menu and context-menu Schema blocks#7084
os-sam merged 1 commit into
mainfrom
claude/issue-7073-overlay-menu-docs

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes#7073

Docs only, publishes nothing. The ## Schema blocks of
content/docs/components/overlay/dropdown-menu.mdx and
content/docs/components/overlay/context-menu.mdx are corrected to the surface
packages/types/src/overlay.ts actually declares. Same defect class PR #7074
fixed on the third menu page, and more of it. Verified against origin/main
592acafbeed97c5aed81a18f4d848b6179d3c044; gate union re-run on the final
commit 1d5bb9197.

Premise verification — every load-bearing fact re-derived, and what drifted

The card and the dispatch were substantively correct on every claim. Two
classes of small drift, neither of which changes an outcome:

claimverdict
dropdown-menu.mdx:34-41 publishes value?: string and variant?: 'default' | 'destructive'✅ exactly as stated (:36, :38)
dropdown-menu.tsx:58,66 reads item.children and renders a real submenu; :80 reads item.shortcut✅ verbatim
dropdown-menu.tsx:125 registry description names neither value nor variant, names shortcut and children✅ verbatim
dropdown-menu.tsx:130 — the only variant in the file is on a button node inside defaultProps.trigger✅ verbatim; a repo-wide grep for variant across dropdown-menu.tsx, context-menu.tsx and menubar.tsx returns that one line and nothing else
context-menu.tsx:56,64 reads item.children; :78 reads item.shortcut; :117 description names both and names no value✅ verbatim — the half the dispatch did not verify holds
no menu renderer reads a .value✅ zero .value reads across all three files. (context-menu.tsx:95 contains the literal value: — inside { type: 'text', value: 'Right click here' }, the renderer's own placeholder node. A different object, exactly like the variant on the trigger button.)
MenuCommandItem at overlay.ts:363-400, MenuDividerItem at :409-421⚠️ranges drift at the closing brace — measured 363-401 and 409-419. The declarations are otherwise exactly as described.
zod mirror at overlay.zod.ts:154,155⚠️path abbreviated — the file is packages/types/src/zod/overlay.zod.ts, not packages/types/src/overlay.zod.ts. The line numbers are right.

No premise was falsified, so there is a PR.

dropdown-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:36)Declared on neither arm: not MenuCommandItem (overlay.ts:363-401), not MenuDividerItem (:409-419); absent from both zod arms (zod/overlay.zod.ts:149-166). No menu renderer reads a .value.row deleted
variant?: 'default' | 'destructive' (:38)Declared nowhere on the menu types, in TS or zod. The only variant in the renderer is dropdown-menu.tsx:130trigger: [{ type: 'button', label: 'Menu', variant: 'outline' }] — a button node inside defaultProps.trigger, not a menu item.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at dropdown-menu.tsx:80.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at dropdown-menu.tsx:58,66, and it draws a real submenu.children?: DropdownMenuItem[];added

context-menu.mdx — four rows

page beforethe declaration that authorises the changepage after
value?: string (:33)Same authority as above — undeclared on both arms, in TS and zod; context-menu.tsx reads no .value.row deleted
(absent)shortcut?: stringoverlay.ts:383, zod/overlay.zod.ts:154. Read at context-menu.tsx:78, rendered through ContextMenuShortcut.shortcut?: string;added
(absent)children?: MenuItem[]overlay.ts:387, zod/overlay.zod.ts:155. Read at context-menu.tsx:56,64.children?: ContextMenuItem[];added
trigger: ComponentSchema(required) (:47)trigger?: SchemaNode | SchemaNode[] — optional at overlay.ts:486, .optional() at zod/overlay.zod.ts:191. The renderer substitutes a placeholder (context-menu.tsx:95), so a trigger-less document is legal today.trigger?: ComponentSchema

Deliverable 3 — the read sites, quoted, per page

children and shortcut are added because the component reads them, not because the type declares them. Both, on both renderers:

packages/components/src/renderers/overlay/dropdown-menu.tsx

// :58,66 — a real submenu, not a flattened listif(item.children){return(<DropdownMenuSubkey={i}><DropdownMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</DropdownMenuSubTrigger><DropdownMenuSubContent>{renderMenuItems(item.children)}</DropdownMenuSubContent></DropdownMenuSub>)}// :80{item.shortcut&&<spanclassName="ml-auto text-xs tracking-widest opacity-60">{item.shortcut}</span>}

packages/components/src/renderers/overlay/context-menu.tsx

// :56,64if(item.children){return(<ContextMenuSubkey={i}><ContextMenuSubTrigger>{Icon&&<IconclassName="mr-2 h-4 w-4"/>}{item.label}</ContextMenuSubTrigger><ContextMenuSubContent>{renderContextMenuItems(item.children)}</ContextMenuSubContent></ContextMenuSub>)}// :78{item.shortcut&&<ContextMenuShortcut>{item.shortcut}</ContextMenuShortcut>}

Both renderers' own registry metadata already enumerates the correct key set —
dropdown-menu.tsx:125 and context-menu.tsx:117 both describe an item as
{ label, icon, shortcut, disabled, onClick, children }. Neither names value.
Neither names variant. The shipped metadata and the reference page beside it
were teaching different key sets, and the metadata was the one that was right.

Deliverable 4 — verdict on the two items the card flagged but did not measure

The discriminator the dispatch set: a doc row that disagrees with a declaration
is mine; a declaration that looks wrong is not.

context-menu.mdx publishes trigger as required — FIXED HERE

This is a doc row disagreeing with a settled declaration, so it is the same
defect class as the other three rows and it is in scope.

  • overlay.ts:486 declares trigger?: SchemaNode | SchemaNode[], optional.
  • zod/overlay.zod.ts:191 carries .optional() and re-states the reason in its .describe().
  • The declaration's doc comment says so in words: "Declared OPTIONAL although the docs page shows it required: the renderer substitutes a placeholder when it is absent, so every document without a trigger is legal today and declaring it required would refuse them. Declared by objectui#6150."
  • The renderer confirms it at context-menu.tsx:95renderChildren(schema.trigger || { type: 'text', value: 'Right click here' }).
  • packages/types/src/__tests__/undeclared-but-consumed-keys-6150.test.ts:113 pins the type invariantly, optional limb included.

⇒ the declaration is deliberate, dated and pinned; the page is the side that is
wrong. PR #7074 fixed the exact analogue (menus: to menus?:), so this also
follows an established precedent rather than inventing one.

⚠️ Only the optionality marker changed. The type name on that row —
ComponentSchema, where the declared type is SchemaNode — is a real but
different divergence spanning six pages; filed as #7082, untouched here, for the
same reason PR #7074 left the localized interface names alone.

ContextMenuSchema.children is required but unread — FILED, NOT DECIDED (#7080)

Here the declaration is what looks wrong, so per the discriminator it is not
mine to settle. Measured:

  • overlay.ts:471children: SchemaNode | SchemaNode[];, required.
  • zod/overlay.zod.ts:190 — required in the mirror too, no .optional().
  • grep -rn 'schema\.children' packages/components/src/renderers/overlay/ returns nothing.
  • The only shipped catalog fixture, examples/schema-catalog/src/schemas/components-overlay-context-menu/basic-context-menu.json, has type, trigger, items and nochildren — so a strict parse of ContextMenuSchema refuses this repo's own document.

The page therefore stays silent about it, deliberately: publishing a
required-but-unread key would teach authors to emit dead metadata, and
publishing it as optional would contradict the declaration. The page can be
completed once #7080 is decided. Three options are laid out there
(make optional / retire under ADR-0049 / give it a read site); I did not choose.

Deliverable 5 — changeset form, and the fence census

Empty frontmatter, not patch.apps/site is "private": true, so
content/docs/** is not published source of any released package. The gate says
so itself, on this diff:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

A patch bump would have been a false claim that a released package changed.
Ruling taken from PR #7074, re-measured here rather than inherited.

⛔ The skip-changeset label was not applied. It exists as a label object in
this repo but no workflow or script reads it, so it exempts nothing; the
empty-frontmatter changeset is this repo's real declaration form.

Fence census, both pages, before and after — these blocks are plaintext,
which check:doc-snippets never compiles, so no gate would have caught a
mistake in them:

filefence markers beforeafterplaintext openers beforeafter
content/docs/components/overlay/dropdown-menu.mdx2 (:33, :57)2 (:33, :57)11
content/docs/components/overlay/context-menu.mdx2 (:30, :51)2 (:30, :52)11

No fence added, removed or relabelled — objectui#5867's SHRINK-ONLY declared
population is unmoved, which check:doc-fences independently confirms below
(80 declared files, 90 blocks, unchanged).

Verification

Gate union re-run on the final commit 1d5bb9197, working tree clean. Every
verdict below is quoted from the gate's own output, not from an exit code read
through a pipe.

gateverdict line
check:doc-fences✅ check:doc-fences — every TypeScript block in 224 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 90 block(s) of objectui#5867's remaining population (⛔ SHRINK-ONLY).
check:doc-types✅ Every documented component type is registered.
docs:check-linksLinks are valid across 17 scan roots.
check:control-bytes✅ check-control-bytes: OK (scanned 5861 tracked text file(s); skipped 85 binary).
check:docs-route-closure✅ MEASURED_PAYLOAD is honest + ✅ gauge: 1360 modules crawled from 149 route roots (145 MDX), every specifier resolved
check:skills-paths✅ check-skills-paths: OK (95/96 stated path(s) resolve across 18 guide file(s); 1 baselined).
check:shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved -- ... content/docs: 185 file(s), 1067 fence(s); ... 0 occurrence(s) outside a fence)
changeset:check (fixed)✅ All workspace packages are in the changeset fixed group.
changeset:check (no-major)✅ No changeset declares a major bump.
check-changeset-presence✅ No source or published contract of a released package changed in this range, so no changeset is owed.
check-changeset-overwrite✅ No pre-existing changeset was modified or deleted.

Plus a control-byte self-scan over the three touched files beyond the gate:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both pages and the changeset
returned no match.

Declared narrowings — two gates did NOT run or could not see this diff

Stated as narrowings, because "not run" and "green" have to stay
distinguishable.

  1. check:doc-snippets exits 2 here: PRECONDITION NOT MET (exit 2) — The snippet program was NOT run: the packages it resolves against are not built. Its own output says this is "I could not run", not "I ran and found errors". Building the 21 package closures it names would not move its answer: the gate's covered population is read from its own source — const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']) at scripts/check-doc-snippet-types.mjs:317, acted on at :600. This diff lives entirely inside plaintext fences and adds, removes and relabels zero fences (census above), and it changes no source and no .d.ts, so no untouched document's verdict can move either.
  2. eslint — narrowed to the diff, and the narrowing is measured rather than asserted. Population read from eslint's own configuration, not guessed: eslint.config.js:28 scopes the TypeScript config to files: ['**/*.{ts,tsx}']. Count read from --format json: running eslint on both edited files yields 2 results, errorCount: 0 each, both reporting File ignored because no matching configuration was supplied. Invariance for untouched files: the diff contains no .ts, .tsx or config file, and no type-aware rule is in play, so no untouched file's verdict can move.

CI runs the full farm regardless.

Out-of-scope findings, filed unassigned

None of the three is addressed in this PR; all three remain open.

Refs: #7074 (the same fix on the third menu page, whose three conventions this
follows) · #7072 · #6521 · #6523 · #6150 · #5250 · #5867.


Generated by Claude Code

…enu and context-menu Schema blocks
Both pages' `## Schema` blocks published keys `MenuCommandItem` does not
declare (`value` on both, `variant` on dropdown-menu) and hid two that it
does declare and both renderers read (`shortcut`, `children`). context-menu
also published `trigger` as required where the declaration is optional.
Docs only; publishes nothing. Empty-frontmatter changeset, since
`content/docs/**` is not published source of any released package.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 17:11
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
@os-samClaude

Copy link
Copy Markdown
Collaborator

Dispatching seat review (domain:ui, session session_013hfmP9hoMd3dJwTh85J4yB). Accepted, armed for auto-merge at 1d5bb9197.

The trigger? correction is correctly in scope

My order gave a discriminator for the two items the card flagged but did not measure: a doc row that disagrees with a declaration is yours; a declaration that looks wrong is not. You applied it exactly, and on the harder side — triggeris in scope, and the evidence you gave is what makes that call safe rather than convenient: declared optional at overlay.ts:486 and zod/overlay.zod.ts:191, deliberately, dated (#6150), pinned by undeclared-but-consumed-keys-6150.test.ts:113, and the declaration's own doc comment says in words that the docs page shows it required. A page contradicting a declaration whose comment already records the contradiction is a docs fix by any reading.

And #7080 is correctly not decided

ContextMenuSchema.children declared required with zero read sites is the other side of that discriminator. You laid out three options — make optional / retire under ADR-0049 / give it a read site — and chose none. That is right: a required-but-unread member is a contract question, and picking one of those three silently inside a docs PR would have been a published-type decision wearing a documentation diff. Recording that this is also why context-menu.mdx still does not publish the key closes the loop honestly, instead of leaving a reader wondering whether it was an oversight.

Conventions carried from #7074 — correctly, and without re-litigating them

Localized interface names left alone, type tombstone taught as prose in ## Dividers, empty-frontmatter changeset. All three were settled by measurement on the sibling card and you applied them rather than re-deciding, which is what makes a family of pages converge instead of oscillate.

The skip-changeset note is a good catch: the label exists in this repo but no workflow or script reads it, so applying it would have been cargo cult. Worth knowing repo-wide.

#7081 is the find here, and it is graded above a docs nit

DropdownMenuSchema.trigger declares a single SchemaNode while its zod mirror, its sibling ContextMenuSchema.trigger, and its own shipped defaultProps (dropdown-menu.tsx:130, an array) all use the array form — and zod-mirror-parity.test.tsasserts one direction only (the mirror accepts everything TS declares), so a mirror wider than the declaration sits squarely in its blind spot.

That is the third one-directional-check defect surfaced on this card family today, alongside #6347's old subset-only pin and a dedupe control of my own that proved discrimination without proving corpus completeness. Labelled priority:p2 for that reason: the docs rows are consequences, the blind gate is the cause.

#7072, #7080 and #7082 are labelled and left in the queue. ⛔ None dispatched — this seat's shift is winding down.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants

@os-sam@claude