Skip to content

fix(menubar): restore menu icons hidden by macOS 27#60

Merged
BlackHole1 merged 1 commit into
mainfrom
fix/macos27-menu-icons
Jul 20, 2026
Merged

fix(menubar): restore menu icons hidden by macOS 27#60
BlackHole1 merged 1 commit into
mainfrom
fix/macos27-menu-icons

Conversation

@BlackHole1

Copy link
Copy Markdown
Member

macOS 27 walks back macOS 26's icon-heavy menus: NSMenu now hides all menu-item SF Symbol images by default (for any app linked against the macOS 26 SDK or newer), leaving only non-symbol images visible. The discriminator is the image's representation, not isTemplate.

Every glyph in the tray menu came from NSImage(systemSymbolName:) via Label(_:systemImage:), so all of them stopped drawing — including the checkmark that marks the locked source. The one exception was the unlocked rows' blank placeholder, a non-symbol NSImage(size:) that stayed visible and kept claiming its column width — which is why the input-source rows ended up indented ~20pt past every other row while showing nothing there. On the current beta (26A5378j) the symptom is exactly what a user reported after upgrading.

The fix routes every glyph through a new MenuIcon enum that bakes the symbol into an NSCustomImageRep of one fixed 16pt box. A non-symbol rep opts back into drawing; the shared box keeps NSMenu's image column at a constant width so titles align and the menu doesn't jump as the lock moves between sources; and the rep re-renders at the destination scale to stay crisp on Retina. Toggle/Picker driving NSMenu's native state column was rejected — that column still collapses to zero width when nothing is checked, which is the width-jump the fixed slot was introduced to kill, and clearing the lock target is a legitimately-unchecked state here.

NSMenuItem.preferredImageVisibility is Apple's sanctioned way to force .visible, but it isn't reachable yet: it's absent from the SDK Xcode 26.6 builds against, and MenuBarExtra exposes no underlying NSMenuItem to set it on. A code comment marks it for revisiting once both land.

Verified against the real running app on 26A5378j by driving the status item via AX and measuring per-row ink columns: before, input-source titles started at 36.5pt vs 16pt elsewhere with no icons drawn at all; after, every title starts at 38.5pt with icons back, and the menu is 166pt wide whether or not a source is locked. MenuBarGuardTests keeps SF Symbols out of the menu and every glyph routed through MenuIcon.

macOS 27 walks back macOS 26's icon-heavy menus: `NSMenu` now hides all
menu-item SF Symbol images by default (for any app linked against the
macOS 26 SDK or newer), leaving only non-symbol images visible. The
discriminator is the image's representation, not `isTemplate`.

Every glyph in the tray menu came from `NSImage(systemSymbolName:)` via
`Label(_:systemImage:)`, so all of them stopped drawing — including the
checkmark that marks the locked source. The one exception was the
unlocked rows' blank placeholder, a non-symbol `NSImage(size:)` that
stayed visible and kept claiming its column width, which is why the
input-source rows ended up indented ~20pt past every other row while
showing nothing there.

Route every glyph through a new `MenuIcon` enum that bakes the symbol
into an `NSCustomImageRep` of one fixed 16pt box. A non-symbol rep opts
back into drawing, the shared box keeps NSMenu's image column at a
constant width (so titles align and the menu doesn't jump as the lock
moves between sources), and the rep re-renders at the destination scale
to stay crisp on Retina. Verified against the real app on 26A5378j:
every title now starts at 38.5pt and the menu is 166pt wide whether or
not a source is locked.

`NSMenuItem.preferredImageVisibility` is the sanctioned fix but isn't
reachable yet — absent from the SDK Xcode 26.6 builds against, and
`MenuBarExtra` exposes no underlying `NSMenuItem` to set it on.

Add `MenuBarGuardTests` to keep SF Symbols out of the menu and every
glyph routed through `MenuIcon`.

Signed-off-by: Kevin Cui <bh@bugs.cc>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • UI Improvements

    • Updated menu bar icons for consistent rendering across Settings, Updates, About, Quit, lock status, and source selections.
    • Preserved consistent spacing in menu items, including rows without a checkmark.
  • Tests

    • Added safeguards to verify that menu icons use the approved rendering approach and avoid inconsistent system icon usage.

Walkthrough

MenuBarView now uses custom fixed-size NSImage icons generated by MenuIcon for the lock indicator, source checkmarks, Settings, Update, About, and Quit entries. Blank placeholders preserve the menu icon gutter for unchecked sources. New MenuBarGuardTests inspect the source file to prevent direct SF Symbol embedding and require menu glyphs to reference MenuIcon.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Matches the required type(scope): subject format and summarizes the menu icon fix.
Description check ✅ Passed It directly describes the menu icon visibility fix and the new guard tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/macos27-menu-icons

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Tests/LockIMEKitTests/MenuBarGuardTests.swift (1)

17-55: 📐 Maintainability & Code Quality | 🔵 Trivial

Minor duplication: extract the comment-stripping logic shared by both tests.

Lines 30 and 46 both re-implement "strip everything from the first // onward" independently. Since these two tests are guarding the same regression, keeping the stripping rule in one helper avoids drift if the heuristic ever needs to change (e.g., to handle block comments or string literals containing //).

♻️ Proposed extraction
 struct MenuBarGuardTests {
     private static let menuBarView = URL(fileURLWithPath: `#filePath`)
         .deletingLastPathComponent() // LockIMEKitTests/
         .deletingLastPathComponent() // Tests/
         .deletingLastPathComponent()
         .appending(path: "Sources/LockIME/UI/MenuBarView.swift")
+
+    /// Strips a trailing `//` comment so only code is scanned.
+    private static func codeOnly(_ line: Substring) -> Substring {
+        line.prefix(upTo: line.firstRange(of: "//")?.lowerBound ?? line.endIndex)
+    }

Then reuse Self.codeOnly(line) in both menuIconsAreBitmapBacked and menuIconsShareOneBox instead of duplicating the expression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/LockIMEKitTests/MenuBarGuardTests.swift` around lines 17 - 55, Extract
the shared comment-stripping expression into a private `Self.codeOnly(line)`
helper in `MenuBarGuardTests`. Update both `menuIconsAreBitmapBacked` and
`menuIconsShareOneBox` to call this helper, preserving the existing behavior of
removing text from the first `//` onward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Tests/LockIMEKitTests/MenuBarGuardTests.swift`:
- Around line 17-55: Extract the shared comment-stripping expression into a
private `Self.codeOnly(line)` helper in `MenuBarGuardTests`. Update both
`menuIconsAreBitmapBacked` and `menuIconsShareOneBox` to call this helper,
preserving the existing behavior of removing text from the first `//` onward.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 45620785-558a-4f1e-b616-362cbec53246

📥 Commits

Reviewing files that changed from the base of the PR and between 2b4de0b and 7125eb4.

📒 Files selected for processing (2)
  • Sources/LockIME/UI/MenuBarView.swift
  • Tests/LockIMEKitTests/MenuBarGuardTests.swift

@BlackHole1
BlackHole1 merged commit dca2e63 into main Jul 20, 2026
3 checks passed
@BlackHole1
BlackHole1 deleted the fix/macos27-menu-icons branch July 20, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant