From 54c15d8f676d864e3595ff350b3746d1287561f6 Mon Sep 17 00:00:00 2001 From: John Larkin Date: Sun, 31 May 2026 10:02:22 -0400 Subject: [PATCH] fix: stop NSMenu-hosted charts from ballooning layout; cut hover vibrancy cost on macOS 26 Two menu-rendering fixes found via profiling a hover/click freeze: 1. Catastrophic freeze: the NSMenu-hosted chart views declared `.frame(minWidth: width, maxWidth: .infinity)`. NSMenu's optimal-size pass (_maximumSizeForScreen) could feed a screen-sized width into the hosted chart + its inner GeometryReader, ballooning the layout/backing- store allocation. On a busy system this spiked memory pressure hard enough to trigger a system-wide jetsam (whole-machine freeze). Bound all five charts to their explicit `width`, matching the already-safe StorageBreakdownMenuView. 2. Hover lag: MenuCardItemHostingView / MenuHostingView forced allowsVibrancy = true, so every menu highlight re-composited the hosted SwiftUI views against the Liquid Glass backdrop on macOS 26. Disable vibrancy on macOS 26+ (kept on older macOS where it is cheap). Files: PlanUtilizationHistoryChartMenuView, CostHistoryChartMenuView, CreditsHistoryChartMenuView, UsageBreakdownChartMenuView, ZaiHourlyUsageChartMenuView, StatusItemController+MenuPresentation. Note: the per-tab-switch full menu rebuild (~38ms in populateMenu, mostly NSHostingView construction) is a separate, architectural cost tracked as follow-up work. Co-Authored-By: Claude Opus 4.8 (1M context) --- Sources/CodexBar/CostHistoryChartMenuView.swift | 2 +- Sources/CodexBar/CreditsHistoryChartMenuView.swift | 2 +- Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift | 2 +- .../CodexBar/StatusItemController+MenuPresentation.swift | 6 ++++-- Sources/CodexBar/UsageBreakdownChartMenuView.swift | 2 +- Sources/CodexBar/ZaiHourlyUsageChartMenuView.swift | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/CodexBar/CostHistoryChartMenuView.swift b/Sources/CodexBar/CostHistoryChartMenuView.swift index e1a35e28a4..f679946e33 100644 --- a/Sources/CodexBar/CostHistoryChartMenuView.swift +++ b/Sources/CodexBar/CostHistoryChartMenuView.swift @@ -194,7 +194,7 @@ struct CostHistoryChartMenuView: View { } .padding(.horizontal, 16) .padding(.vertical, 10) - .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading) + .frame(width: self.width, alignment: .leading) } private struct Model { diff --git a/Sources/CodexBar/CreditsHistoryChartMenuView.swift b/Sources/CodexBar/CreditsHistoryChartMenuView.swift index e75521eb96..c9dbd4163d 100644 --- a/Sources/CodexBar/CreditsHistoryChartMenuView.swift +++ b/Sources/CodexBar/CreditsHistoryChartMenuView.swift @@ -114,7 +114,7 @@ struct CreditsHistoryChartMenuView: View { } .padding(.horizontal, 16) .padding(.vertical, 10) - .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading) + .frame(width: self.width, alignment: .leading) } private struct Model { diff --git a/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift b/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift index ae995cc1b4..8534925fcb 100644 --- a/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift +++ b/Sources/CodexBar/PlanUtilizationHistoryChartMenuView.swift @@ -171,7 +171,7 @@ struct PlanUtilizationHistoryChartMenuView: View { } .padding(.horizontal, 16) .padding(.vertical, 10) - .frame(minWidth: self.width, maxWidth: .infinity, alignment: .topLeading) + .frame(width: self.width, alignment: .topLeading) .task(id: visibleSeries.map(\.id).joined(separator: ",")) { guard let firstVisibleSeries = visibleSeries.first else { return } guard !visibleSeries.contains(where: { $0.id == self.selectedSeriesID }) else { return } diff --git a/Sources/CodexBar/StatusItemController+MenuPresentation.swift b/Sources/CodexBar/StatusItemController+MenuPresentation.swift index f1097a50b5..006af9359d 100644 --- a/Sources/CodexBar/StatusItemController+MenuPresentation.swift +++ b/Sources/CodexBar/StatusItemController+MenuPresentation.swift @@ -76,7 +76,8 @@ final class MenuCardHighlightState { final class MenuHostingView: NSHostingView { override var allowsVibrancy: Bool { - true + if #available(macOS 26, *) { return false } + return true } } @@ -86,7 +87,8 @@ final class MenuCardItemHostingView: NSHostingView, Menu private let onClick: (() -> Void)? override var allowsVibrancy: Bool { - true + if #available(macOS 26, *) { return false } + return true } override var intrinsicContentSize: NSSize { diff --git a/Sources/CodexBar/UsageBreakdownChartMenuView.swift b/Sources/CodexBar/UsageBreakdownChartMenuView.swift index 0b1ca524dc..656ae5d0bd 100644 --- a/Sources/CodexBar/UsageBreakdownChartMenuView.swift +++ b/Sources/CodexBar/UsageBreakdownChartMenuView.swift @@ -130,7 +130,7 @@ struct UsageBreakdownChartMenuView: View { } .padding(.horizontal, 16) .padding(.vertical, 10) - .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading) + .frame(width: self.width, alignment: .leading) } private struct Model { diff --git a/Sources/CodexBar/ZaiHourlyUsageChartMenuView.swift b/Sources/CodexBar/ZaiHourlyUsageChartMenuView.swift index 4e1ec437c4..bd4c5c6fa9 100644 --- a/Sources/CodexBar/ZaiHourlyUsageChartMenuView.swift +++ b/Sources/CodexBar/ZaiHourlyUsageChartMenuView.swift @@ -118,7 +118,7 @@ struct ZaiHourlyUsageChartMenuView: View { } .padding(.horizontal, 16) .padding(.vertical, 10) - .frame(minWidth: self.width, maxWidth: .infinity, alignment: .topLeading) + .frame(width: self.width, alignment: .topLeading) .animation(.easeInOut(duration: 0.2), value: self.isExpanded) }