fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@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

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures - #7103

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys
Sep 1, 2026
Merged

fix(examples): drop the 21 inert value keys from the catalog overlay-menu fixtures#7103
os-warren merged 3 commits into
mainfrom
claude/issue-7072-catalog-menu-value-keys

Conversation

@os-warren

@os-warrenos-warren commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7072

Twenty-one menu items in the four overlay-menu catalog fixtures authored a value key that no arm of the shipped MenuItem union declares and no menu renderer reads. This deletes them, and pins that they stay gone.

Re-derived on current main, because the card's numbers were days old

The card measured at 592acafb; main is now 2c3cd1b. The count has not moved — 21, distributed exactly as the card's table claims:

fixturevalue keys
components-overlay-menubar/application-menubar.json11
components-overlay-context-menu/basic-context-menu.json4
components-overlay-dropdown-menu/basic-dropdown-menu.json3
components-overlay-dropdown-menu/with-icons.json3
total21

Counted by a walker that classifies each value key by its position in the tree rather than by filename, so the neighbouring buttons[].value population (#7077, button-group) is separated by construction and cannot leak in. Catalog-wide the walker sees 288 objects carrying value before and 267 after — a delta of exactly 21, so nothing outside the four fixtures moved.

⚠️ One correction to the card: its cited line spans are slightly off. Re-derived, MenuCommandItem is packages/types/src/overlay.ts:363-401 (card said :363-400) and MenuDividerItem is :409-419 (card said :409-421).

The authority, re-verified

  • MenuCommandItem (overlay.ts:363-401) declares label, icon, disabled, onClick, shortcut, children, separator?: false, type?: never. No value.
  • MenuDividerItem (:409-419) declares separator: true, type?: never. No value.
  • The three renderers under packages/components/src/renderers/overlay/ perform zero .value reads. Stated precisely, because it is not zero occurrences of the token: context-menu.tsx:95 contains { type: 'text', value: "Right click here" } — a write constructing a text node, whose renderer genuinely reads value. No menu item's value is ever read.
  • MenuItemSchema (packages/types/src/zod/overlay.zod.ts:147-168) builds its union from bare, non-strict z.objects, so zod strips value and reports success — the finding: JSON doc snippets are checked by nothing — the ts/tsx gate cannot see them, and BaseSchema.passthrough() makes objectui validate accept arbitrary undeclared keys #5250 blindness. Nothing red covered this.

Deleted rather than declared: nothing reads value, and #6523 deliberately narrowed this union rather than widening it.

The pin — #6810's second half

#6810's ruling (2026-08-30, maintainer, verbatim「同意」) prescribes 逐例修 + 补钉 for a key this class has regenerated on: the case-by-case fix plus a named pin carrying its own counter-probe, in the existing component-fixture-declared-keys.test.ts ledger form. This PR now carries both halves.

Not a widening of #6249's menubar sweep — a new, separately named block. That sweep walks one node shape and its own header states that extending it to dropdown-menu / context-menu is "a separate verification surface this fix does not need"; it is untouched. value was authored across all three containers, so a menubar-scoped pin would have covered 11 of 21 while reading as though it covered the class, which is worse than no pin.

The new block sweeps the whole overlay-menu corpus — all three container shapes, including the deeper menus[].items[] nesting only menubar has, and submenu children:

  • non-vacuity: 25 items (menubar 13 + context-menu 5 + dropdown 4 + with-icons 3), one representative position asserted per container shape, so a walker that stops matching a shape fails loudly instead of reporting a clean zero.
  • the assertion: no overlay-menu item authors value, with label (21) and separator (4) as in-query controls, so an empty or unparsed corpus cannot masquerade as a pass.
  • the counter-probe: ⛔ deliberately corpus-level, not schema-level. MenuItemSchema strips value and returns success, so a .safeParse probe is structurally blind here — it stayed green through all 21 authored keys. The probe re-authors value into a real fixture taken from the corpus and asserts the identical sweep reports it at the right position, with the untouched fixture clean alongside.

⭐ The pre-existing declared-key controls in the menubar block already fed {label, icon, disabled, shortcut, value} to MenuItemSchema and asserted only the first four survive. That pinned the type-level fact and was green the whole time the 21 keys sat in the corpus. The gap was only ever the corpus assertion.

Ablation — the pin demonstrably goes red. With "value": "profile" re-authored into basic-dropdown-menu.json on disk (mutation confirmed: value key count 0 → 1, injected text present, blob hash 7e72503bece5381c, still valid JSON), the suite fails:

× ... > no overlay-menu item authors `value`
AssertionError: expected [ Array(1) ] to deeply equal []
× ... > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture
AssertionError: expected [ 'pristine.items[0].value' ] to deeply equal []
Tests 2 failed | 34 passed (36)

The non-vacuity test stayed green throughout, correctly — a key was added, not an item. Restore leg proven rather than assumed: git checkout HEAD -- <abs path>, value count back to 0, blob hash matches the HEAD blob exactly, git diff HEAD empty.

Evidence

The diff is only what it claims. Every changed fixture line is one of: a removed "value": … line (21), or a "label" line re-punctuated because value had been the last key on that item (14 removed / 14 added, identical but for the trailing comma). No content changed. All four files round-trip byte-identically through JSON.stringify(…, null, 2), so the edit was made by parse-and-reserialise and the diff is minimal by construction.

Zero-hit control. After the deletion, value hits 0 in all four files while the control label still hits 14 / 4 / 4 / 4 in the same query — a zero for both would have meant the files were emptied, not fixed. Structure intact: menubar still has 13 items (2 separators, 11 labelled), matching the existing expect(items).toHaveLength(13) pin.

Rendered output is unchanged — which is the correct expectation here, the opposite of a fixture-content fix. The keys were inert, so the DOM must not move. Measured rather than assumed: a scratch harness rendered each dropdown fixture at 2c3cd1b and at this branch with defaultOpen: true injected (a closed menu renders only its trigger, which never carried the key, and would have made the comparison vacuous), normalised Radix's per-instance generated ids, and asserted identical HTML, text and [role="menuitem"] counts. Its control — mutating a key the renderer does read (label) — was required to produce a difference, and did. Scratch-only; not part of this diff.

⚠️Coverage of that DOM measurement, stated honestly: it directly covers the 6 items in the two dropdown-menu fixtures. menubar (11) and context-menu (4) cannot be forced open the same way — Radix opens them via a menubar value and a real right-click — so their inertness rests on the zero-read derivation above plus the corpus suites. My first attempt at this harness was discarded outright: without registering the renderers it fell back to a raw-JSON dump, and its control caught it.

Suites, on the final commit 8595e42:

pnpm exec vitest run \
examples/schema-catalog/test/component-fixture-declared-keys.test.ts \
examples/schema-catalog/test/safe-validate-corpus-6318.test.ts \
examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx \
examples/schema-catalog/test/catalog-gallery-render.test.tsx
=> Test Files 4 passed (4) · Tests 659 passed (659) [RUN root: objectui-issue-7072]

659 = the prior 656 plus this PR's 3 new tests. Confirmed by name under --reporter=verbose rather than by an aggregate count:

✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > the sweep reaches all three container shapes and every one of their items — non-vacuity
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > no overlay-menu item authors `value`
✓ catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072) > counter-probe: the same sweep DOES flag a `value` key put back into a real fixture

Run root-relative from the repo root, per #3378 — the RUN v4.1.10 /home/user/objectui-issue-7072 banner confirms it did not silently re-root onto apps/console.

Type-check.pnpm --filter @object-ui/example-schema-catalog type-check (the package spells it hyphenated) => EXIT 0, 0 errors, with the script body echoed (tsc --noEmit && tsc -p tsconfig.test.json) so a zero-match filter cannot pass for a green. ⚠️ The package's main tsconfig.jsonexcludes test/, so that green would say nothing about the new pin on its own; --listFiles confirms component-fixture-declared-keys.test.ts is really in the tsconfig.test.json program (889 files). The dependency closure was built first — before it, every test file in the package reported Cannot find module '@object-ui/types/zod', including ones this PR never touches.

Gates, on 8595e42, exit codes captured before any pipe:

gateexit
check:control-bytes0
changeset:check0
check:doc-types0
check:icon-record-names0
check:docs-route-closure0
check-changeset-presence.mjs0

Gate family derived by hand from this repo's own package.json and .github/workflows/scripts/pm/dispatch-gates.mjs lives only in objectstack and would have answered about that tree.

Changeset. The presence gate's verdict line, verbatim:

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

examples/schema-catalog is private: true, so the artifact is an empty-frontmatter changeset (.changeset/7072-catalog-menu-value-keys.md) — ⛔ not a patch bump, which would falsely claim a released package changed. Adding a test does not change that. ⛔ No skip-changeset label: in this repo that label is a historical mis-mint that no workflow reads.

Lint.pnpm exec eslint examples/schema-catalog/test/component-fixture-declared-keys.test.ts => EXIT 0, and --format json confirms the file was actually linted (0 errors, 0 warnings, no "File ignored" message) rather than silently skipped. The four JSON fixtures and the changeset carry no eslint configuration and report as ignored. Plain form; ⛔ no --no-inline-config.

Absorber check — #6810 is ruled, and it rules for this shape

The dispatch flagged #6810 as a possible absorber, expected to be undecided. It is not — it was ruled twice, and the second ruling adopted the middle state: narrow ratchet, ⛔ no general extractor, with option A (the family-wide sweep) 明确不采 and never-before-seen keys taking option C — 人手撞见、逐例修 + 补钉. So this card is not a row of a sweep; it is exactly the hand fix that ruling prescribes, and the pin above is its second half.

Filed, not fixed

#7077 is not addressed here; its buttons[].value population is a different component family.


Generated by Claude Code

…ixtures
The four overlay-menu catalog fixtures authored a `value` key on 21 menu
items. No arm of the shipped `MenuItem` union declares it (`MenuCommandItem`
at packages/types/src/overlay.ts:363-401, `MenuDividerItem` at :409-419), and
none of the three menu renderers reads it. `MenuItemSchema` builds its union
from bare, non-strict `z.object`s, so zod strips the key and reports success —
the key has been inert and invisible since it was authored.
Delete the keys rather than declare them: nothing reads `value`, and #6523
deliberately narrowed this union instead of widening it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…ed `value` key
The objectui#6810 ruling (2026-08-30) prescribes `逐例修 + 补钉` for a key this
class has regenerated on: the case-by-case fix PLUS a named pin carrying its own
counter-probe. The fixture deletions landed without the pin half; this adds it.
Deliberately a SEPARATE block from objectui#6249's menubar sweep rather than a
widening of it. That sweep walks one node shape and its header states on purpose
that extending it to dropdown-menu/context-menu is a separate verification
surface. `value` was authored across all three containers, so a menubar-scoped
pin would have covered 11 of 21 while reading as though it covered the class.
The counter-probe is corpus-level, not schema-level: MenuItemSchema strips
`value` and returns success, so a .safeParse probe stayed green through all 21
authored keys and is structurally blind to this defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 00:34
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 6c1d95dSep 1, 2026
31 checks passed
@os-warren
os-warren deleted the claude/issue-7072-catalog-menu-value-keys branch September 1, 2026 00:47
Sign up for freeto 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): 21 catalog menu items author value, a key no arm of the shipped MenuItem union declares and no menu renderer reads

2 participants

@os-warren@claude