#23135: feat(macos): menubar UI consistency overhaul — sections, hover, icons, toggles
app: macos
size: XL
Cluster:
macOS Notification and Menu Fixes
## Summary
Complete redesign of the macOS menubar menu for visual consistency and stability. Every row across every section now uses the same custom `NSMenuItem.view` rendering pipeline, eliminating the mix of native AppKit rendering and custom SwiftUI views that caused misaligned indents, inconsistent row heights, and blue selection highlights in some sections but not others.
## What changed
### Selection / hover colour
- **Before:** mixed — some rows showed AppKit's bold blue selection highlight, others nothing
- **After:** all selectable rows use a soft grey rounded rect (`unemphasizedSelectedContentBackgroundColor`), matching macOS system menus (Spotlight, Control Centre, etc.)
- Toggle rows (OpenClaw on/off header, all Quick Settings toggles) show **no** selection background — clicking fires the action without any visual highlight
### Sections & labels
- **Before:** no section labels for Actions or App; sections had inconsistent leading indents
- **After:** four clearly labelled sections — **Status**, **Activity**, **Quick Settings**, **Actions**, **App** — all with the same leading indent and 11 pt semibold grey headers
- Separator between Context and Usage subsections removed (they belong to the same Activity section)
### Row layout & padding
- **Before:** Quick Settings rows were ~22 pt tall (3 pt vertical padding); node/session rows were ~28–30 pt; native Actions rows used AppKit's state-column indent (~38 px from left edge)
- **After:** all single-line rows are uniform height (5 pt vertical padding); multi-line rows keep their own sizing; every row starts at the same 12 pt leading indent
### Icons
- **Before:** Quick Settings and Connected Devices icons were oversized (16 pt), creating visual inconsistency against the 13 pt icons in Actions; Connected Devices used `laptopcomputer.and.iphone` which rendered very large
- **After:** all icons normalised to 13 pt inside 16×16 frames; Connected Devices uses `network`
### Section headers (Context / Usage)
- **Before:** plain greyed-out text labels with no icon
- **After:** leading SF Symbol icon (`list.bullet` / `bolt`) + primary-colour label + secondary count subtitle; wrapped in `HighlightedMenuItemHostView` so they get the same grey hover as other rows
### Exec Approvals dropdown
- **Before:** native NSMenuItem items with AppKit checkmarks and blue selection
- **After:** each option wrapped in `HighlightedMenuItemHostView` + `MenuPickerRow` (leading checkmark, grey hover, consistent indent)
### Active header (OpenClaw toggle)
- **Before:** native SwiftUI `Toggle` with AppKit checkmark rendering
- **After:** custom `MenuActiveHeaderView` with a proper capsule toggle switch (macOS-style); `ClickableMenuItemHostView` intercepts `mouseDown` to fire the action since `NSMenuItem.target/action` is ignored for custom views
### Cost usage submenu
- **Before:** used `NSHostingController` + separate `NSHostingView` for the same view (two independent render trees causing blank/wrong chart)
- **After:** single `NSHostingView` sized via `fittingSize`
### New Swift files
- `MenuActiveHeaderView.swift` — `MenuActiveHeaderView` (capsule toggle header) + `ClickableMenuItemHostView` subclass
- `MenuToggleViews.swift` — `CapsuleToggle`, `QuickSettingsRow`, `MenuSubMenuRow`, `MenuNativeItemRow`, `MenuPickerRow`, `MenuSectionLabelView`
### gitignore
- Added `.vscode/` and `.claude/` to `.gitignore`
## After
<img width="667" height="785" alt="after" src="https://github.com/user-attachments/assets/9d726bbf-9b3d-464d-9f2b-3e841177a899" />
## Before
<img width="339" height="701" alt="before" src="https://github.com/user-attachments/assets/d11aa1d1-1b54-4db7-af62-c7cffe1471e9" />
AI-assisted:
- [x] AI-assisted change
- [x] Tested locally (targeted)
<h3>Greptile Summary</h3>
Confidence Score: 5/5
This PR is safe to merge with minimal risk only UI changes.
Replaces the mixed native-AppKit/SwiftUI rendering pipeline in the macOS menubar with a unified NSMenuItem.view-based system across all sections.
Core infrastructure (MenuHighlightedHostView.swift, MenuActiveHeaderView.swift):
- HighlightedMenuItemHostView changed from final to open class; adds showsHighlight: Bool flag to gate hover/selection drawing
- Selection color changed from selectedContentBackgroundColor (blue) to unemphasizedSelectedContentBackgroundColor (soft grey) with tighter inset
- New ClickableMenuItemHostView subclass intercepts mouseDown to fire actions (required because NSMenuItem.target/action is ignored for custom views); sets
showsHighlight = false for toggle rows
New views (MenuToggleViews.swift):
- CapsuleToggle — animated macOS-style switch glyph
- QuickSettingsRow / MenuSubMenuRow — toggle and picker rows with consistent 13 pt icons, 5 pt vertical padding, 12 pt leading indent
- MenuNativeItemRow — wraps native SwiftUI Button/Menu items so Actions and App sections share the same indent and grey hover
- MenuPickerRow — selection list row with leading checkmark for Exec Approvals submenu
- MenuSectionLabelView — 11 pt semibold grey section headers
Injection pipeline (MenuSessionsInjector.swift):
- Adds injectActiveHeader, injectQuickSettings, injectActionsLabel, injectAppLabel, injectNativeItemViews — each idempotent via numeric tags (9_415_559 through
9_415_564)
- injectNativeItemViews walks items after the Actions label, wraps submenu items with HighlightedMenuItemHostView and button items with ClickableMenuItemHostView +
NSApp.sendAction forwarding
- Voice Wake moved above Exec Approvals; separator between Context/Usage removed
- buildExecApprovalsSubmenu replaced with HighlightedMenuItemHostView-wrapped MenuPickerRow items
- buildCostUsageSubmenu simplified to single NSHostingView (was double-allocating via NSHostingController + NSHostingView)
View cleanup (MenuContentView.swift, session/usage label views):
- Removed Toggle, settingToggleRow, and related bindings from MenuContent.body — all now owned by the injector
- Removed @Environment(\.menuItemHighlighted) color-inversion logic from SessionMenuLabelView, UsageMenuLabelView, SessionMenuPreviewView, NodesMenu views — all
text now uses static .primary/.secondary
Most Similar PRs
#9126: fix(macOS): prevent recursive menu nesting in status bar
by YuriNachos · 2026-02-04
70.5%
#19828: feat: reply notifications for macOS and web UI
by fal3 · 2026-02-18
69.9%
#15909: Guard notifications on macOS; fix focus issue and build fixes
by jasonkneen · 2026-02-14
68.2%
#22458: Codex/macos chat corner clip
by apethree · 2026-02-21
67.2%
#9017: feat(ui): Premium polish with enhanced components and animations
by thejustinfagan · 2026-02-04
66.6%
#22271: feat(watchos): implement liquid glass UI and voice-first orb
by Rocuts · 2026-02-20
66.5%
#17920: macOS: fix WebChat panel corner clipping
by agisilaos · 2026-02-16
66.4%
#17448: ui: make tool cards collapsible with inline expansion
by karimStekelenburg · 2026-02-15
65.8%
#12168: feat: integrate Mission Control dashboard into Control UI
by riftagent-git · 2026-02-08
65.7%
#3474: fix(macos): menu bar activity badge not showing during agent work
by elektricM · 2026-01-28
65.5%