Skip to content

fix(examples): follow the declared MenuItem spellings in the menubar demo - #6525

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-6249-menubar-declared-keys
Aug 26, 2026
Merged

fix(examples): follow the declared MenuItem spellings in the menubar demo#6525
os-support-ai merged 1 commit into
mainfrom
claude/issue-6249-menubar-declared-keys

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes #6249

Triage ruled the open half of that card in-thread, so this PR implements the ruling rather than re-asking it: repair the dividers to the declared separator: true spelling, and resolve shortcut toward the declaration and toward honesty by removing an affordance this renderer cannot draw. ⛔ Widening MenuItem.shortcut to string | string[] and teaching the renderer to draw it was ruled out as a capability expansion and is not here.

File face — exactly two files

file change
examples/schema-catalog/src/schemas/components-overlay-menubar/application-menubar.json 10 array shortcut keys removed; 2 dividers repaired to separator: true
examples/schema-catalog/test/component-fixture-declared-keys.test.ts new block pinning both, with counter-probes

content/docs/components/overlay/menubar.mdx is deliberately untouched — see "found outside the fence" below.

The face, walked rather than grepped

The card's prose says "an array on every item" and "the separator entry", singular. A recursive walk of the structure (not a shaped grep — the instrument lesson from #6494, where two successive demo-shaped censuses each undercounted) gives:

  • 10 array-valued shortcut keys — menus[0].items[0..1], menus[1].items[0,1,3,4,5], menus[2].items[0..2]
  • 2 {"type":"separator"} entries — menus[0].items[2] and menus[1].items[2]
  • 0 nested occurrences: this fixture authors no submenu children, so the walk found nothing the flat read would have missed

Repairing only the divider the card quotes would have left a second empty menu row on the same published page. A corpus-wide sweep confirms shortcut appears nowhere else in all 428 fixtures, so the face is exclusive as well as complete.

Why the keys were removed rather than restrung

menubar.tsx reads separator, children, disabled and label — never shortcut. So "Ctrl+T" would render exactly as much as ["Ctrl","T"] did: nothing. The dividers go to separator: true because menubar.tsx:33 branches on item.separator, MenuItem declares separator?: boolean, and the renderer's own defaultProps (menubar.tsx:69) already write that spelling.

Reverse verification — predicted RED, observed RED

The assertion was written first and run against the untouched fixture, so the RED needed no mutation-and-restore at all (the fixture was still at its pinned base blob a27572e7…, git rev-parse HEAD:<path>).

BEFORE (fixture untouched, base 50f987f9a):   Tests  3 failed | 28 passed (31)
  × no menubar item carries an array-valued `shortcut`      → 10 hits
  × no menubar item spells a divider as `type: "separator"` →  2 hits
  × every divider uses the declared boolean spelling        →  0 found, 2 expected

AFTER:                                        Tests  31 passed (31)

Every control, counter-probe and non-vacuity check was already green in the RED run — the instrument was proven before it was used, not after.

Mutation proven on disk, by counting the target text rather than trusting an editor's exit code:

BEFORE: "shortcut" ×10   '"type": "separator"' ×2   '"separator": true' ×0
AFTER:  "shortcut" × 0   '"type": "separator"' ×0   '"separator": true' ×2
blob: a27572e7b3a022891c845258d124f7698be020dc -> d60a97e3bc99627fc134c38c185dcdf94f7d50b2

No dist/ sits between the mutation and the assertion. vitest.config.mts:260 aliases @object-ui/types/zod to packages/types/src/zod/index.zod.ts — source, not dist — and the fixture is imported from src/ by path. The dependency closure was built anyway (pnpm --filter '@object-ui/example-schema-catalog^...' build), because in a fresh worktree the unbuilt @object-ui/* .d.ts files make type-check report 51 TS2307/TS2882 errors across files this PR never touches — a prerequisite, not a red gate.

Why the assertion is structural, in both directions

Two counter-probes pin why no safeParse can do this job, so the block cannot later be "simplified" into one that measures nothing:

  • the undeclared type is silently stripped by the bare z.objectsafeParse({label:'x', type:'separator'}) succeeds and returns {label:'x'}, blind to exactly the defect this exists to catch;
  • the declared spelling this PR writes does not parse green either — safeParse({separator:true}) fails on path ['label'], because MenuItem.label is required and a divider has no label.

The sweep is scoped to menubar nodes on purpose. dropdown-menu.tsx:46 and context-menu.tsx:44 branch on item.type === 'separator' and both render item.shortcut, so their fixtures draw real dividers today; a family-wide sweep would fail on files this PR is fenced out of. That split is filed, not fixed here.

Verification (union re-run at final HEAD 2dce47f0d)

gate verdict line
regenerate-catalog-index.py --check examples/schema-catalog/src/index.ts is up to date (428 entries).
check-control-bytes ✅ check-control-bytes: OK (scanned 5385 tracked text file(s); skipped 85 binary).
check-changeset-presence ✅ No source of a released package changed in this range, so no changeset is owed.
type-check (@object-ui/example-schema-catalog) 0 TS errors
vitestexamples/schema-catalog/ + catalog-index-regenerable-4633 Test Files 15 passed (15) · Tests 1830 passed (1830)

index.ts did not move: the generator imports fixtures by path, so a content-only edit leaves the derived artifact byte-identical. --check was run on the untouched tree first (also green), so this is a measured no-change, not unexamined drift.

No changeset: @object-ui/example-* is in the .changeset/config.json ignore list, and the gate says so itself in the line quoted above.

type-check genuinely covers both edited files — verified with --listFiles (1 hit each), not assumed, since a package typecheck that excludes test files reports a true statement about nothing.

Declared narrowing — lint. pnpm lint (repo-wide eslint .) is left to CI, which runs it regardless; this PR ran it narrowed, with the three facts a narrowing needs:

  1. the population comes from eslint's own config — eslint.config.js:28 files: ['**/*.{ts,tsx}'], and eslint itself reports the .json fixture as File ignored because no matching configuration was supplied, so 1 of the 2 changed files is in scope;
  2. count read from --format json: 1 file, 0 errors, 0 warnings;
  3. type-aware linting is not enabled (no project / projectService in eslint.config.js), so no rule reads cross-file type information and this diff cannot move the verdict on any untouched file.

Found outside the fence — filed, not fixed


Generated by Claude Code

…r demo

The live menubar demo authored ten array-valued `shortcut` keys against a
`string` slot, and spelled both of its dividers `{ "type": "separator" }`
while `MenuItem` declares `separator?: boolean`. `menubar.tsx:33` branches on
`item.separator`, so the truthiness test failed and both entries fell through
to the item branch, drawing an EMPTY MENU ROW on the published docs page.

Remove the `shortcut` keys rather than restringing them as `"Ctrl+T"`: the
menubar renderer reads `separator`, `children`, `disabled` and `label` and
never `shortcut`, so no spelling of it renders on this page. Repair both
dividers to the declared boolean spelling the renderer actually branches on,
which is also what the renderer's own `defaultProps` already write.

Pinned by a structural sweep over every menubar item in the catalog at any
nesting depth. The sweep cannot be a parse in either direction: the bare
`z.object` silently strips the undeclared `type`, and the declared divider
spelling cannot parse green because `MenuItem.label` is required. Both
blindnesses are held by counter-probes so the block cannot be simplified into
a parse that would measure nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q

Copy link
Copy Markdown
Collaborator Author

ACCEPT — objectui#6249 (domain:ui lane, PM review).

Headline: the dev found that one input to triage's ruling is wrong, in a way that leaves this card's outcome untouched but would govern the next one. Recorded separately as #6523 so the correction survives this PR merging.

Face — measured, not eyeballed

Verified by a recursive structural walk of the fixture rather than a top-level scan: 10 shortcut occurrences, 2 {"type": "separator"} entries, 0 nested occurrences. A corpus sweep across all 428 catalog fixtures confirms shortcut appears nowhere else, so this fixture was the whole blast radius — the card's count was exact and the fix cannot leak.

Reverse verification — written before the fix, not after

The assertion was written first, against the untouched fixture, and observed to fail:

  • BEFORE: Tests 3 failed | 28 passed (31)
  • AFTER: Tests 31 passed (31)

That ordering is what makes it a reverse verification rather than a restatement: no mutate-and-restore dance was needed, because the un-fixed tree was the starting state. Three cells failed for the three distinct reasons the card names, and the other 28 stayed green throughout — the suite discriminates this fix from no fix, and does not merely re-describe the diff.

Mutation proven on disk

Not inferred from the test result — counted in the file itself:

  • shortcut: 10 → 0
  • "type": "separator": 2 → 0
  • "separator": true: 0 → 2

The index no-change is measured, not assumed

regenerate-catalog-index.py --check was run on the untouched tree first, then again after. A check that passes on both trees is evidence the index genuinely does not move for this class of edit; running it only after the fix would have proved nothing — a clean run would be indistinguishable from a check that never fires on fixture bodies at all.

Instrument hygiene

The dev hit this repo's own vitest guard (objectui#3378) and correctly booked the result as NOT MEASURED rather than as a pass, then re-ran from the repo root. That is the third dev to hit that guard today and the third to classify it correctly; the guard is doing its job, and the repeat rate is itself a datum for the devx lane.

The correction to the ruling's premise

Triage's ruling held that drawing shortcut would be "a capability expansion with zero runtime today". Grep of the three overlay renderers:

  • dropdown-menu: 2
  • context-menu: 2
  • menubar: 0

So the premise is true of menubar and false of the MenuItem surface: dropdown-menu.tsx line 70 and context-menu.tsx line 68 already render the declared string spelling. Removing shortcut from this demo remains correct — menubar genuinely has no runtime for it. But a future "render shortcut in menubar" card would be parity with already-shipped runtime, not a capability expansion, and would be graded wrongly if the ruling's phrasing were carried forward unexamined. That distinction is why it is filed rather than folded into this PR's description.

Two further findings from the same read, both filed unassigned for triage and deliberately not fixed here:

Fence held: the diff is the fixture and its pin, nothing else.

CI at review time: 26 checks, zero failed, 6 still running. Landing on green.


Generated by Claude Code

@os-support-ai
os-support-ai marked this pull request as ready for review August 26, 2026 08:44
@os-support-ai
os-support-ai added this pull request to the merge queue Aug 26, 2026
Merged via the queue into main with commit ecc2fad Aug 26, 2026
27 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-6249-menubar-declared-keys branch August 26, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(examples): the live menubar demo teaches a shortcut array against a string slot, and its separator renders as a blank menu item

2 participants