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
33 changes: 33 additions & 0 deletions .github/pr-proof/cached-menu-card-shell-swap.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
CodexBar cached menu-card shell swap - native runtime proof
Captured: 2026-08-20T12:27:38+05:30
Commit: f411a02817c20ebc7ff5f844d78925fe8f4b177c
Host: macOS 26.3 (25D125), arm64

Setup
- Ad-hoc debug app bundle using the binary built from the commit above and launched
under a disposable bundle/preferences domain.
- Keychain access and web extras were disabled. The attached frame contains no
account identity or usage values.
- MenuSwitchFlickerProbe drove the real merged NSMenu and captured its WindowServer
composite with CGWindowListCreateImage.

Run
- Two native-menu sessions, six programmatic provider/overview switches each.
- 176 + 236 = 412 captured samples; nil captures: 0 in both sessions.
- Production trace recorded 12 cached-swap paths and one reconcile path (the initial
menu construction for session two).
- The attached frame is the first cached Claude -> Codex switch, captured at
session +449.2 ms. The switch was handled at +406 ms; the cached swap ran from
process +504.6 ms to +511.3 ms.

Visual check around that cached switch
- Pre-switch frames at +409.2 ms and +429.0 ms were byte-identical.
- Post-switch frames at +449.2 ms and +469.9 ms were byte-identical.
- A system-blue pixel scan found zero blue pixels below y=140 in all four frames.
The only system-blue region was the expected selected provider segment in the
switcher header. No native blue selection layer appeared behind the card.

Limit
- This host is macOS 26.3, not the reporter's macOS 27 build. The source regression
and subclass-preservation test are deterministic; macOS 27 visual confirmation
remains useful.
Binary file added .github/pr-proof/cached-menu-card-shell-swap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion Sources/CodexBar/StatusItemController+MenuReconcile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ extension StatusItemController {
let requiresNativeImageReplacement =
self.shouldReplaceNativeImageItemDuringReconcile(liveItem) ||
self.shouldReplaceNativeImageItemDuringReconcile(newItem)
if liveItem.isSeparatorItem == newItem.isSeparatorItem, !requiresNativeImageReplacement {
let hasCompatibleItemClass =
ObjectIdentifier(type(of: liveItem)) == ObjectIdentifier(type(of: newItem))
if liveItem.isSeparatorItem == newItem.isSeparatorItem,
hasCompatibleItemClass,
!requiresNativeImageReplacement
{
if !liveItem.isSeparatorItem {
self.swapMenuItemContents(liveItem, newItem)
}
Expand Down
35 changes: 35 additions & 0 deletions Tests/CodexBarTests/MenuCardViewRecyclingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,41 @@ extension StatusMenuTests {
#expect(displacedIncoming.first === incoming)
}

@Test
func `cached provider content preserves menu item subclasses across switch back`() {
let previousRendering = StatusItemController.menuCardRenderingEnabled
StatusItemController.menuCardRenderingEnabled = true
defer { StatusItemController.menuCardRenderingEnabled = previousRendering }

let settings = self.makeSettings()
settings.statusChecksEnabled = false
let controller = self.makeRecyclingController(settings: settings)
defer { controller.releaseStatusItemsForTesting() }

let plainItem = NSMenuItem(title: "Overview", action: nil, keyEquivalent: "")
let cardItem = controller.makeMenuCardItem(Text("Codex"), id: "codex", width: 300)
let menu = NSMenu()
menu.addItem(plainItem)

let displacedPlain = controller.replaceMenuContentKeepingRowsVisible(
menu,
fromIndex: 0,
with: [cardItem])

#expect(menu.items.first === cardItem)
#expect(menu.items.first is MenuCardMenuItem)
#expect(displacedPlain.first === plainItem)

let displacedCard = controller.replaceMenuContentKeepingRowsVisible(
menu,
fromIndex: 0,
with: displacedPlain)

#expect(menu.items.first === plainItem)
#expect(!(menu.items.first is MenuCardMenuItem))
#expect(displacedCard.first === cardItem)
}

@Test
func `cached provider content swap preserves both item sets for switch back`() {
let settings = self.makeSettings()
Expand Down