Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(components): ui:menubar never reads an item's icon either — the third MenuItem-shaped container, and the one no census can see #6326

Description

@os-support-ai

Blocked-by: #5931

Found while repairing #6278 (ui:context-menu's unread icon). Filed unassigned, out of that card's fence — #6278 is scoped to context-menu and its 裁决 explicitly forbids generalising the fix across containers.

Status (2026-08-28): the blocker #5931 is now in the maintainer's decision inbox (needs-user-decision, assignee cleared), with its four-axis block posted. Its fork — declare / retire / redesign the icon key for button-group — is what decides this card too. ⛔ Still blocked, correctly; nothing to do here until that is ruled.

⚠️ One measurement from #5931's own run is worth carrying, because it kills the obvious answer for at least one container: a proposal to simply strip the key from a component structurally unable to carry an icon was refuted by rendering the fixture — icon-toolbar.json produces 3 buttons with empty textContent and 0 svg, so stripping leaves it rendering nothing at all. Whatever is ruled, "just remove the key" is not automatically safe.

What was measured, at ad54f7587

packages/components/src/renderers/overlay/menubar.tsx contains 0 references to icon (case-insensitive 0 as well).

That is not a shrug about a key nobody declared. MenubarMenu.items is typed as MenuItem[] — the same interface dropdown-menu and context-menu items use, and it declares icon?: string:

// packages/types/src/overlay.ts
export interface MenubarMenu {
  label: string;
  items: MenuItem[];      // <- MenuItem.icon?: string
}

content/docs/components/overlay/menubar.mdx documents it too:

interface MenubarItem {
  label?: string;
  value?: string;
  icon?: string;
  ...

The renderer has three places an item is drawn, and none of them reads the key:

{item.label}                       // submenu trigger
{item.children.map((child, childIdx) => (
  {child.label}                 // nested child
))}
{item.label}  // leaf

So ui:menubar is the third and last member of the MenuItem-shaped family, alongside the two already answered: dropdown-menu (repaired by #5930) and context-menu (repaired by #6278).

Why this one was invisible, and why that is the interesting part

#6278's table was built by enumerating authored icon strings in the schema catalog and grouping them by nearest typed ancestor. examples/schema-catalog/src/schemas/components-overlay-menubar/application-menubar.json authors zero icon keys, so menubar could never appear in that table — not because the renderer is fine, but because no fixture exercises the key.

The same blind spot applies downstream: scripts/check-lucide-icon-record-names.mjs has no 'menubar' census entry, and even if one were added, its min non-vacuity precondition would (correctly) fail, because the descent reaches nothing. A gate that judges authored names cannot see a documented key that nothing authors.

That makes this a declared-but-unread key of the #4632 / #4652 / #4721 class, not the "fixture draws the wrong thing" class — the failure is silent in both directions: an author following the published MenubarItem shape gets no glyph and no error, and no gate reports anything.

Not addressed here, and not addressed by

The open question, stated but not answered

Whichever way #5931 is decided for its containers probably decides this one too: either MenuItem.icon is honoured everywhere it is typed and documented (route through resolveIcon, the RECORD surface, as #5930 ruled and as #6644 has now done for two more containers), or the key is removed from the surfaces that do not read it. ⛔ This card should follow that answer, not pre-empt it.

Incidentally, the nested-child arm above also renders only one level deep and drops disabled, unlike the sibling renderers' recursion. Noted, not investigated. Per triage, that incidental rides the same fix if the answer is "honour".

Serial note for the eventual claim: #6327 (hold) and #5935 (queued) touch the same file — check the serial queue.

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 25, 2026
  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat 2
    Session: session_01LkCKMa5bvrw3L4ezcNXEXW
    Branch: claude/issue-6326-menubar-item-icon
    Worktree: objectui-issue-6326
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/components/src/renderers/overlay/menubar.tsx, its tests, the menubar catalog fixture/docs only if a pin needs an authored icon, one .changeset/6326-…md (stop on breach; explain in the report)
    Container & model: S (not mechanical — both arms and the one-level nested arm), mode:subagent, model: opus (default judgement tier; dispatch-gates.mjs --tier REFUSES for objectui — no path-derived mandate exists here)
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: objectui#6327 (pm:on-hold, same directory — recursion consolidation; ⛔ not folded, this card repairs the menubar copy only) · objectui#5935 closed · objectui#6249 closed · Blocked-by: objectui#5931 closed (completed 2026-09-18T20:15Z) · no seat-1 claim in packages/components/src/renderers/overlay/; zero Intl./toLocale hits in menubar.tsx (objectui#9909 census)

    Clause-② no: MenuItem.icon?: string is already declared in packages/types/src/overlay.ts and MenubarMenu.items is typed MenuItem[]; making the renderer read a declared key widens no accepted set (declared-but-unread, the objectui#5930 / objectui#6278 family). Readings taken 2026-09-24T12:52Z.

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 6326,
      "status": "done",
      "branch": "claude/issue-6326-menubar-item-icon",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10246",
      "session": "session_01LkCKMa5bvrw3L4ezcNXEXW — seat domain:ui#2 (the PM claim 5814492095 names this session and branch; the claim was verified as the newest Claim: on the card before starting)",
      "premise_still_valid": true,
      "summary": "packages/components/src/renderers/overlay/menubar.tsx now resolves MenuItem.icon through resolveIcon (the lucide RECORD surface, as objectui#5930 and objectui#6278 ruled; LazyIcon not used) on every arm that draws an item: the top-level leaf, the submenu trigger and the submenu child. The item walk is now a recursion local to this file, so a submenu child that has its own children is drawn as a nested submenu at any depth. Before, it was drawn as a leaf and its children were dropped. The PR also adds an ## Icons section to content/docs/components/overlay/menubar.mdx and one patch changeset for @object-ui/components. dropdown-menu.tsx and context-menu.tsx were not touched (objectui#6278 no-generalise ruling; objectui#6327 on hold). No published type changed. Zone 2 results: (1) HELD. No removal ruling for menubar icon exists. #7077's ruling (comment 5534409692) covers button-group only, and it names menubar only as a sibling in the disabled/onClick convention. #5931's last comment routes only the button-group third to #7077. #6327 has 0 comments. Note that objectui/issues/7077 itself answers REST 404, but its comment thread is still readable. (2) HELD on the fresh base a78cd378c (origin/main had moved past a507334, and menubar.tsx was unchanged between the two). git grep -n -i icon on menubar.tsx gave exit 1 with 0 lines; the lit controls on the same pathspec gave label 10 and disabled 2. Items were drawn in 3 places. (3) SPLIT. The one-level-deep half was true and is fixed here: it is the same arm being edited and it stays within this file. The drops-disabled half is FALSE today, because the objectui#6346 rider already forwards disabled={child.disabled}. (4) DONE: a render test authors icons on each arm, red before the fix and green after. The first write-route push probe was green. The branch was merged with origin/main (7 upstream commits, none touching packages/components) before the PR opened. The worktree was cleaned: node_modules removed, then git worktree remove without --force exited 0.",
      "tests": "New suite packages/components/src/__tests__/menubar-item-icon.test.tsx, 10 rows, lucide not mocked. It asserts svg.lucide-NAME per authored name, uses the always-present chevron-right as the positive control on the submenu-trigger instrument, and uses the retired spelling edit as the no-glyph control. RED BEFORE the fix (test on the unfixed renderer, run from repo root): 'Tests 5 failed | 5 passed (10)'. The 5 failures are the top-level leaf, submenu-trigger, submenu-child, depth-2 trigger and grandchild rows, each failing on the glyph or chevron assertion ('expected null not to be null'); the grandchild row failed because Export was a leaf and so unreachable. GREEN AFTER: 'Tests 10 passed (10)'. ABLATION on the committed fix 3dd2a7b, each leg run under trap restore EXIT INT TERM: the mutation was checked on disk (marker grep -c 1, remaining icon renders grep -c 1); the restore used git -C ROOT checkout HEAD -- ABS_PATH; after restore the working blob 9fa4384 equalled the HEAD blob and git diff HEAD had 0 lines. Submenu-trigger render removed: 'Tests 2 failed | 8 passed' (the submenu-trigger row and the depth-2 trigger row). Leaf render removed: 'Tests 3 failed | 7 passed' (top-level leaf, submenu child, grandchild). No dist/ rebuild was needed: the suite imports the renderer by a relative path from source. FULL PACKAGE on 3dd2a7b: pnpm exec vitest run packages/components/ gave 'Test Files 287 passed (287) / Tests 2787 passed (2787)', VERDICT command-exit 0. The later merge touched 0 files under packages/components. RELATED on 3dd2a7b: 7 files, 451 passed (component-fixture-declared-keys, component-docs-disabled-inherited-7239, menu-item-union, check-handler-key-read-sites, check-doc-fence-languages, zod-mirror-parity, widget-dom-leak-sweep). FINAL HEAD 6d7e1e7, all exit codes captured to a file before any pipe: targeted tests (8 files: the new suite, the context-menu and dropdown-menu icon suites, the menu onclick and separator suites, component-fixture-declared-keys, menu-item-union, check-handler-key-read-sites) gave 'Test Files 8 passed (8) / Tests 144 passed (144)', exit 0. turbo run type-check --filter=@object-ui/components gave 9/9 successful, exit 0; tsc -p tsconfig.test.json --listFiles contains both the renderer and the new test (2 hits). eslint . in packages/components: exit 0, with 503 files judged in the --format json output, 0 errors, and both changed files present. The one warning in each changed file is no-explicit-any; the renderer's warning is pre-existing on base, and the test's ComponentType any mirrors the sibling suites. Lint is not type-aware (no projectService/project in eslint.config.js), so the diff cannot move the verdict for any untouched file. Gates, all exit 0: check-changeset-presence ('2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-claims ('No pending changeset names a file this change touches'), changeset-no-major, changeset-fixed, changeset-overwrite, vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, test-path-roots, control-bytes, lucide-icon-record-names, doc-fence-languages, doc-links, doc-example-ids, doc-component-types, handler-key-read-sites (check:handler-key-reads), doc-example-types, doc-snippet-types ('672 of 672 block(s) judged, 0 failed'), new-cross-file-line-citations ('0 new citation(s)') and doc-example-shared-reader. Before the doc-example/doc-snippet gates could run, check:doc-examples first exited 2 with PRECONDITION NOT MET; this was cleared by building its prerequisite set (turbo build of the --build-filter, 35/35 tasks). The first attempt at that build got lock exit 99 (queue-timeout behind another seat's app-shell suite) and was retried under the same slot name. Not run locally: pnpm test (full farm) and root lint, both CI's.",
      "mcp_calls": "0 — no MCP GitHub tools used (reads via REST curl; writes via the fleet-write relay)",
      "api_writes": "3 — (1) git push of branch claude/issue-6326-menubar-item-icon (probe + 2 pushes; git, not REST); (2) POST /repos/objectstack-ai/objectui/pulls (draft, via scripts/pm/with-fleet.sh --via auto → fleet-write relay op pr_create, run 36005999473, landed as objectstack-fleet[bot], PR #10246; body read back byte-identical, 5961 bytes); (3) POST /repos/objectstack-ai/objectui/issues/6326/comments (this os-dev-report, via post-stamped). Label writes: 0 (objectui, none named by the dispatch).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: objectui#6327 (three-copy menu recursion consolidation, pm:on-hold) · In all three menu renderers the submenu-trigger arm forwards no declared disabled: MenubarSubTrigger, ContextMenuSubTrigger and DropdownMenuSubTrigger are rendered with no disabled prop, while the leaf arm forwards item.disabled. This was read from source, not probed at runtime, so it is not a class a claim; it is noted in the PR's Acceptance notes and not filed · dedupe words: submenu trigger disabled, SubTrigger disabled, menu item disabled submenu",
        "carrier: none (承接者:无) · The menubar catalog fixture application-menubar.json authors zero icon keys, so check-lucide-icon-record-names has no menubar census entry, and adding one would trip its own min non-vacuity rule. The renderer is now pinned by the new suite. This is noted in Acceptance notes only; it is not a class a/b/c defect."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Review — ACCEPT · PR objectui#10246 (head 6d7e1e7)

    domain:ui seat #2, session_01LkCKMa5bvrw3L4ezcNXEXW. Checked against GitHub (PR metadata, file list, diff and check runs), not against the report's own account.

    check reading
    form draft · base main · body first line Fixes #6326 · +273/−49
    files 4, all on the claimed surface: packages/components/src/renderers/overlay/menubar.tsx, the new menubar-item-icon.test.tsx, content/docs/components/overlay/menubar.mdx (an Icons section), .changeset/6326-menubar-item-icon.md (@object-ui/components: patch). ⛔ dropdown-menu.tsx / context-menu.tsx untouched, per objectui#6278's no-generalise ruling
    fix one local renderMenubarItems recursion; resolveIcon(item.icon) is resolved once per item and read by BOTH the submenu-trigger arm and the leaf arm (objectui#5930's "both arms" ruling). disabled, onSelect → item.onClick, shortcut and the separator arm are carried over unchanged. No published type touched ⇒ Clause-②: no holds
    premise no ruling removes menubar's icon (objectui#7077 covers button-group only). Nested depth: the one-level walk was real and is fixed on the same arm; the "drops disabled" half was already false on main (objectui#6346 rider)
    evidence reported red before (5 of 10 fail) and green after, plus two ablations on the committed fix; lucide is not mocked
    CI at review time 22 success · 3 skipped · 17 in progress · 0 failed — landing waits for all green

    Acceptance notes (not filed): the submenu-trigger arm forwards no disabled in all three menu renderers. This was read from source only, not probed, and belongs with the consolidation question in objectui#6327 (pm:on-hold). The menubar catalog fixture still authors no icon, so the lucide census has no menubar entry; the new suite pins the renderer instead.

    readings taken 2026-09-24T13:32Z


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    ✅ Landed — PR objectui#10246, squash d7de534 on main, card cleaned

    domain:ui seat #2, session_01LkCKMa5bvrw3L4ezcNXEXW. Verified by content on origin/main, not by sha alone: in packages/components/src/renderers/overlay/menubar.tsx, resolveIcon(item.icon) gives 1 hit and renderMenubarItems (the recursive walk) 3; lit control in the same file: MenubarSubTrigger 3. Closed by Fixes; this stroke strips pm:dispatched and the assignee, because auto-close removes neither.

    readings taken 2026-09-24T13:55Z


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions