Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/7072-catalog-menu-value-keys.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
---
---

Internal only, no release: the four overlay-menu fixtures in
`examples/schema-catalog` no longer author a `value` key on their menu items.

Twenty-one menu items across `components-overlay-menubar/application-menubar`
(11), `components-overlay-context-menu/basic-context-menu` (4),
`components-overlay-dropdown-menu/basic-dropdown-menu` (3) and
`components-overlay-dropdown-menu/with-icons` (3) carried `"value"`. No arm of
the shipped `MenuItem` union declares it — `MenuCommandItem`
(`packages/types/src/overlay.ts:363-401`) declares `label`, `icon`, `disabled`,
`onClick`, `shortcut`, `children`, `separator?: false` and a `type?: never`
tombstone; `MenuDividerItem` (`:409-419`) declares `separator: true` and the
same tombstone. Neither declares `value`, and none of the three menu renderers
under `packages/components/src/renderers/overlay/` reads one.

Nothing rendered differently before or after, which is the point: `MenuItemSchema`
(`packages/types/src/zod/overlay.zod.ts:147-168`) builds its union from bare,
non-strict `z.object`s, so zod stripped the key and reported success. The key
had been inert and invisible since it was authored, and the catalog is a
declared AI few-shot retrieval source, so an inert key there is a spelling the
next author copies.

The keys are deleted rather than declared: nothing reads `value`, and
objectui#6523 deliberately narrowed this union rather than widening it. No
published package source, no schema, no renderer and no type was touched — the
diff is four JSON fixtures in a `private: true` example package.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,25 +8,21 @@
"items": [
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Cut",
"value": "cut",
"icon": "scissors"
},
{
"label": "Paste",
"value": "paste",
"icon": "clipboard"
},
{
"separator": true
},
{
"label": "Delete",
"value": "delete",
"icon": "trash"
}
]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,19 +6,16 @@
},
"items": [
{
"label": "Profile",
"value": "profile"
"label": "Profile"
},
{
"label": "Settings",
"value": "settings"
"label": "Settings"
},
{
"separator": true
},
{
"label": "Logout",
"value": "logout"
"label": "Logout"
}
]
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,17 +8,14 @@
"items": [
{
"label": "Edit",
"value": "edit",
"icon": "square-pen"
},
{
"label": "Copy",
"value": "copy",
"icon": "copy"
},
{
"label": "Delete",
"value": "delete",
"icon": "trash",
"variant": "destructive"
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,64 +5,53 @@
"label": "File",
"items": [
{
"label": "New Tab",
"value": "new"
"label": "New Tab"
},
{
"label": "New Window",
"value": "window"
"label": "New Window"
},
{
"separator": true
},
{
"label": "Exit",
"value": "exit"
"label": "Exit"
}
]
},
{
"label": "Edit",
"items": [
{
"label": "Undo",
"value": "undo"
"label": "Undo"
},
{
"label": "Redo",
"value": "redo"
"label": "Redo"
},
{
"separator": true
},
{
"label": "Cut",
"value": "cut"
"label": "Cut"
},
{
"label": "Copy",
"value": "copy"
"label": "Copy"
},
{
"label": "Paste",
"value": "paste"
"label": "Paste"
}
]
},
{
"label": "View",
"items": [
{
"label": "Zoom In",
"value": "zoom-in"
"label": "Zoom In"
},
{
"label": "Zoom Out",
"value": "zoom-out"
"label": "Zoom Out"
},
{
"label": "Reset Zoom",
"value": "reset"
"label": "Reset Zoom"
}
]
}
Expand Down
146 changes: 146 additions & 0 deletions examples/schema-catalog/test/component-fixture-declared-keys.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -661,3 +661,149 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling
expect(MenuItemSchema.safeParse({ label: 'Section', type: 'label' }).success).toBe(false);
});
});

/**
* objectui#7072 — the four overlay-menu fixtures authored a `value` key on 21
* of their 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
* one, so the key rendered nothing and cost nothing — it was simply a spelling
* the catalog taught. `MenuItemSchema` builds both arms from bare, non-strict
* `z.object`s, so zod STRIPS the key and reports success; that is the #5250
* blindness, and it is why nothing red ever covered these 21.
*
* ## Why this is a SEPARATE block from the `menubar` sweep above
*
* ⛔ This is deliberately not a widening of `objectui#6249`'s sweep, and the
* two must not be merged. That block walks ONE node shape (`{ type: 'menubar',
* menus: [{ items }] }`) and its header states on purpose that extending it to
* `dropdown-menu` / `context-menu` is "a separate verification surface this fix
* does not need". It is still scoped that way and still correct.
*
* But `value` was authored across all THREE containers, so a menubar-scoped pin
* would have covered 11 of the 21 while READING as though it covered the class
* — the failure mode that is worse than no pin at all. Hence a second, honestly
* named block whose population is the whole overlay-menu corpus.
*
* ## What this pins that the schema-level control above does NOT
*
* ⭐ The declared-key controls in the `menubar` block already feed
* `{ label, icon, disabled, shortcut, value }` to `MenuItemSchema` and assert
* only the first four survive. That pins the TYPE-LEVEL fact — `value` is not
* declared — and it was already green while all 21 keys sat in the corpus.
* A stripping schema cannot see an authored key, so the type-level probe is
* structurally incapable of catching this. The gap was only ever the CORPUS
* assertion, and that is exactly what this block adds.
*
* Per the objectui#6810 ruling (2026-08-30): no family-wide read-set extractor
* is being built here — this is the ruled `逐例修 + 补钉` shape, one named pin
* with its own counter-probe for a key the class has actually regenerated on.
*/
describe('catalog corpus: no overlay-menu item authors the undeclared `value` key (objectui#7072)', () => {
type Located = { where: string; item: Json };

/** The three containers that hold `MenuItem`s. They share one `MenuItem` type. */
const MENU_CONTAINER_TYPES = new Set(['menubar', 'dropdown-menu', 'context-menu']);

/** Depth-first over an items array, descending through submenu `children`. */
function collectFrom(items: unknown, where: string, acc: Located[]): void {
if (!Array.isArray(items)) return;
items.forEach((raw, i) => {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
const item = raw as Json;
acc.push({ where: `${where}[${i}]`, item });
collectFrom(item.children, `${where}[${i}].children`, acc);
});
}

/**
* Every `MenuItem` of every menu container in the corpus, at any depth.
* `menubar` nests its items one level deeper (`menus[].items[]`) than
* `dropdown-menu` / `context-menu` (`items[]`) — reading only one of the two
* shapes is how a sweep silently returns a short list, so both are walked
* here and the non-vacuity assertion below pins the resulting count.
*/
function collectMenuItems(node: unknown, where: string, acc: Located[] = []): Located[] {
if (Array.isArray(node)) {
node.forEach((n, i) => collectMenuItems(n, `${where}[${i}]`, acc));
return acc;
}
if (!node || typeof node !== 'object') return acc;
const record = node as Json;
if (typeof record.type === 'string' && MENU_CONTAINER_TYPES.has(record.type)) {
if (record.type === 'menubar' && Array.isArray(record.menus)) {
(record.menus as Json[]).forEach((menu, m) => {
collectFrom((menu as Json)?.items, `${where}.menus[${m}].items`, acc);
});
} else {
collectFrom(record.items, `${where}.items`, acc);
}
}
for (const [key, value] of Object.entries(record)) {
collectMenuItems(value, `${where}.${key}`, acc);
}
return acc;
}

const authoredValueKeys = (items: Located[]) =>
items.filter(({ item }) => 'value' in item).map(({ where }) => `${where}.value`);

const items = allExamples().flatMap((e) => collectMenuItems(e.schema, e.id));

it('the sweep reaches all three container shapes and every one of their items — non-vacuity', () => {
// Pinned so a walker that silently stops matching one container shape (or
// one nesting depth) fails loudly here rather than reporting a clean zero
// below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu
// 4 + with-icons 3.
expect(items).toHaveLength(25);
expect(allExamples().length).toBeGreaterThan(400);

// One representative position per container shape, including the deeper
// `menus[].items[]` nesting only `menubar` has.
expect(items.map((i) => i.where)).toEqual(
expect.arrayContaining([
'components-overlay-menubar/application-menubar.menus[0].items[0]',
'components-overlay-context-menu/basic-context-menu.items[0]',
'components-overlay-dropdown-menu/basic-dropdown-menu.items[0]',
'components-overlay-dropdown-menu/with-icons.items[0]',
]),
);
});

it('no overlay-menu item authors `value`', () => {
expect(authoredValueKeys(items)).toEqual([]);

// The zero above is only a reading if the same sweep still SEES the items
// it is judging: a corpus that stopped parsing, or a walker that returned
// nothing, would also report "no `value`". `label` is the comparable
// declared key and must stay non-zero.
const labelled = items.filter(({ item }) => 'label' in item);
expect(labelled).toHaveLength(21);
const dividers = items.filter(({ item }) => item.separator === true);
expect(dividers).toHaveLength(4);
});

it('counter-probe: the same sweep DOES flag a `value` key put back into a real fixture', () => {
// ⛔ Not a synthetic menu and NOT a `.safeParse` probe. `MenuItemSchema`
// strips `value` and returns success, so a schema-level probe is blind to
// the authored key by construction — it stayed green through all 21 of
// them. This re-authors the key into a REAL fixture, taken from the corpus
// the pin above judges, and asserts the identical sweep reports it.
const fixture = JSON.parse(
JSON.stringify(schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu')),
) as Json;
((fixture.items as Json[])[0] as Json).value = 'profile';

const reintroduced = collectMenuItems(fixture, 'counter-probe');
expect(authoredValueKeys(reintroduced)).toEqual(['counter-probe.items[0].value']);

// ...and the untouched fixture at the same position is clean, so the probe
// is reading the mutation rather than always reporting a hit.
const pristine = collectMenuItems(
schemaOf('components-overlay-dropdown-menu/basic-dropdown-menu'),
'pristine',
);
expect(authoredValueKeys(pristine)).toEqual([]);
expect(reintroduced).toHaveLength(pristine.length);
});
});
Loading