Skip to content

Commit dad6e70

Browse files
perf: avoid reflecting nested cost history projects on submenu open
Replace Cost History hosted-submenu String(reflecting:) signatures with a typed visible-content fingerprint so menu-open work no longer traverses nested project/source daily data the UI never renders. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 98de978 commit dad6e70

5 files changed

Lines changed: 713 additions & 34 deletions

Sources/CodexBar/CostHistoryChartMenuView.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,75 @@ struct CostHistoryChartMenuView: View {
764764
}
765765

766766
extension CostHistoryChartMenuView {
767+
struct RenderFingerprint: Equatable {
768+
let currencyCode: String
769+
let historyDays: Int
770+
let windowLabel: String?
771+
let totalCostBitPattern: UInt64?
772+
let daily: [DailyEntry]
773+
let projects: [VisibleProjectFingerprint]
774+
}
775+
776+
struct VisibleProjectFingerprint: Equatable {
777+
let name: String
778+
let path: String?
779+
let totalTokens: Int?
780+
let totalCostBitPattern: UInt64?
781+
let visibleSourceCount: Int
782+
let sources: [VisibleSourceFingerprint]
783+
}
784+
785+
struct VisibleSourceFingerprint: Equatable {
786+
let name: String
787+
let path: String?
788+
let totalTokens: Int?
789+
let totalCostBitPattern: UInt64?
790+
}
791+
792+
static func renderFingerprint(from snapshot: CostUsageTokenSnapshot) -> RenderFingerprint {
793+
self.makeRenderFingerprint(RenderFingerprintInputs(
794+
currencyCode: snapshot.currencyCode,
795+
historyDays: snapshot.historyDays,
796+
windowLabel: snapshot.historyLabel,
797+
totalCostUSD: snapshot.last30DaysCostUSD,
798+
daily: snapshot.daily,
799+
projects: snapshot.projects))
800+
}
801+
802+
private struct RenderFingerprintInputs {
803+
let currencyCode: String
804+
let historyDays: Int
805+
let windowLabel: String?
806+
let totalCostUSD: Double?
807+
let daily: [DailyEntry]
808+
let projects: [CostUsageProjectBreakdown]
809+
}
810+
811+
private static func makeRenderFingerprint(_ inputs: RenderFingerprintInputs) -> RenderFingerprint {
812+
RenderFingerprint(
813+
currencyCode: inputs.currencyCode,
814+
historyDays: inputs.historyDays,
815+
windowLabel: inputs.windowLabel,
816+
totalCostBitPattern: inputs.totalCostUSD.map(\.bitPattern),
817+
daily: inputs.daily,
818+
projects: Array(inputs.projects.prefix(self.maxVisibleProjectRows)).map { project in
819+
let visibleSources = self.visibleProjectSources(project)
820+
return VisibleProjectFingerprint(
821+
name: project.name,
822+
path: project.path,
823+
totalTokens: project.totalTokens,
824+
totalCostBitPattern: project.totalCostUSD.map(\.bitPattern),
825+
visibleSourceCount: visibleSources.count,
826+
sources: Array(visibleSources.prefix(self.maxVisibleProjectSourceRows)).map { source in
827+
VisibleSourceFingerprint(
828+
name: source.name,
829+
path: source.path,
830+
totalTokens: source.totalTokens,
831+
totalCostBitPattern: source.totalCostUSD.map(\.bitPattern))
832+
})
833+
})
834+
}
835+
767836
static func _defaultSelectedDateKeyForTesting(provider: UsageProvider, daily: [DailyEntry]) -> String? {
768837
self.defaultSelectedDateKey(model: self.makeModel(provider: provider, daily: daily))
769838
}

Sources/CodexBar/StatusItemController+HostedSubmenus.swift

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@ import CodexBarCore
33
import QuartzCore
44
import SwiftUI
55

6+
enum HostedSubviewContentFingerprint: Equatable {
7+
case text(String)
8+
case costHistory(CostHistoryChartMenuView.RenderFingerprint)
9+
}
10+
11+
struct HostedSubviewRenderSignature: Equatable {
12+
let chartID: String
13+
let providerRawValue: String?
14+
let widthBitPattern: UInt64
15+
let content: HostedSubviewContentFingerprint
16+
}
17+
18+
final class HostedSubviewRenderSignatureBox: NSObject {
19+
let signature: HostedSubviewRenderSignature
20+
21+
init(_ signature: HostedSubviewRenderSignature) {
22+
self.signature = signature
23+
}
24+
}
25+
626
extension StatusItemController {
727
private struct HostedSubviewIdentity {
828
let chartID: String
@@ -153,7 +173,7 @@ extension StatusItemController {
153173
return
154174
}
155175
let signature = self.hostedSubviewRenderSignature(identity: identity, width: width)
156-
if self.hostedSubviewRenderSignatures.object(forKey: menu) as String? == signature {
176+
if self.hostedSubviewRenderSignatures.object(forKey: menu)?.signature == signature {
157177
if identity.chartID == Self.zaiHourlyUsageChartID {
158178
self.refreshHostedSubviewHeights(in: menu)
159179
}
@@ -210,7 +230,9 @@ extension StatusItemController {
210230
chartID: identity.chartID,
211231
providerRawValue: identity.provider?.rawValue ?? identity.providerRawValue)
212232
}
213-
self.hostedSubviewRenderSignatures.setObject(signature as NSString, forKey: menu)
233+
self.hostedSubviewRenderSignatures.setObject(
234+
HostedSubviewRenderSignatureBox(signature),
235+
forKey: menu)
214236
}
215237

216238
private func hostedSubviewIdentity(for menu: NSMenu)
@@ -240,51 +262,51 @@ extension StatusItemController {
240262
width: CGFloat)
241263
{
242264
let signature = self.hostedSubviewRenderSignature(identity: identity, width: width)
243-
self.hostedSubviewRenderSignatures.setObject(signature as NSString, forKey: menu)
265+
self.hostedSubviewRenderSignatures.setObject(
266+
HostedSubviewRenderSignatureBox(signature),
267+
forKey: menu)
244268
}
245269

246270
private func hostedSubviewRenderSignature(
247271
identity: HostedSubviewIdentity,
248-
width: CGFloat) -> String
272+
width: CGFloat) -> HostedSubviewRenderSignature
249273
{
250-
let contentSignature: String = switch identity.chartID {
274+
let contentSignature: HostedSubviewContentFingerprint = switch identity.chartID {
251275
case Self.usageBreakdownChartID:
252-
Self.dashboardBreakdownReadinessSignature(
276+
.text(Self.dashboardBreakdownReadinessSignature(
253277
OpenAIDashboardDailyBreakdown.removingSkillUsageServices(
254-
from: self.store.openAIDashboard?.usageBreakdown ?? []))
278+
from: self.store.openAIDashboard?.usageBreakdown ?? [])))
255279
case Self.creditsHistoryChartID:
256-
Self.dashboardBreakdownReadinessSignature(self.store.openAIDashboard?.dailyBreakdown ?? [])
280+
.text(Self.dashboardBreakdownReadinessSignature(self.store.openAIDashboard?.dailyBreakdown ?? []))
257281
case Self.costHistoryChartID:
258-
identity.provider.map(self.costHistoryRenderSignature(for:)) ?? "missing-provider"
282+
if let provider = identity.provider {
283+
self.costHistoryRenderFingerprint(for: provider)
284+
} else {
285+
.text("missing-provider")
286+
}
259287
case Self.usageHistoryChartID:
260-
identity.provider.map(self.usageHistoryRenderSignature(for:)) ?? "missing-provider"
288+
.text(identity.provider.map(self.usageHistoryRenderSignature(for:)) ?? "missing-provider")
261289
case Self.storageBreakdownID:
262-
identity.provider.map(self.storageBreakdownRenderSignature(for:)) ?? "missing-provider"
290+
.text(identity.provider.map(self.storageBreakdownRenderSignature(for:)) ?? "missing-provider")
263291
case Self.statusComponentsID:
264-
identity.provider.map(self.statusComponentsRenderSignature(for:)) ?? "missing-provider"
292+
.text(identity.provider.map(self.statusComponentsRenderSignature(for:)) ?? "missing-provider")
265293
case Self.zaiHourlyUsageChartID:
266-
identity.provider.map(self.zaiHourlyUsageRenderSignature(for:)) ?? "missing-provider"
294+
.text(identity.provider.map(self.zaiHourlyUsageRenderSignature(for:)) ?? "missing-provider")
267295
default:
268-
"unknown"
296+
.text("unknown")
269297
}
270-
return [
271-
identity.chartID,
272-
identity.providerRawValue ?? "",
273-
String(Double(width).bitPattern, radix: 16),
274-
contentSignature,
275-
].joined(separator: "|")
298+
return HostedSubviewRenderSignature(
299+
chartID: identity.chartID,
300+
providerRawValue: identity.providerRawValue,
301+
widthBitPattern: Double(width).bitPattern,
302+
content: contentSignature)
276303
}
277304

278-
private func costHistoryRenderSignature(for provider: UsageProvider) -> String {
279-
guard let snapshot = self.tokenSnapshotForCostHistorySubmenu(provider: provider) else { return "none" }
280-
return [
281-
snapshot.currencyCode,
282-
"\(snapshot.historyDays)",
283-
snapshot.historyLabel ?? "",
284-
snapshot.last30DaysCostUSD.map { String($0.bitPattern, radix: 16) } ?? "nil",
285-
String(reflecting: snapshot.daily),
286-
String(reflecting: snapshot.projects),
287-
].joined(separator: "|")
305+
private func costHistoryRenderFingerprint(for provider: UsageProvider) -> HostedSubviewContentFingerprint {
306+
guard let snapshot = self.tokenSnapshotForCostHistorySubmenu(provider: provider) else {
307+
return .text("none")
308+
}
309+
return .costHistory(CostHistoryChartMenuView.renderFingerprint(from: snapshot))
288310
}
289311

290312
private func usageHistoryRenderSignature(for provider: UsageProvider) -> String {
@@ -626,3 +648,16 @@ extension StatusItemController {
626648
return true
627649
}
628650
}
651+
652+
#if DEBUG
653+
extension StatusItemController {
654+
func _hostedSubviewRenderSignatureForTesting(menu: NSMenu, width: CGFloat) -> HostedSubviewRenderSignature? {
655+
guard let identity = self.hostedSubviewIdentity(for: menu) else { return nil }
656+
return self.hostedSubviewRenderSignature(identity: identity, width: width)
657+
}
658+
659+
func _storedHostedSubviewRenderSignatureForTesting(menu: NSMenu) -> HostedSubviewRenderSignature? {
660+
self.hostedSubviewRenderSignatures.object(forKey: menu)?.signature
661+
}
662+
}
663+
#endif

Sources/CodexBar/StatusItemController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin
139139
var menuProviders: [ObjectIdentifier: UsageProvider] = [:]
140140
var menuSession = MenuSessionCoordinator<ObjectIdentifier>()
141141
var menuReadinessSignatures: [ObjectIdentifier: String] = [:]
142-
let hostedSubviewRenderSignatures = NSMapTable<NSMenu, NSString>.weakToStrongObjects()
142+
let hostedSubviewRenderSignatures = NSMapTable<NSMenu, HostedSubviewRenderSignatureBox>.weakToStrongObjects()
143143
/// Persistent Refresh rows are weakly tracked so their enabled state can change during menu tracking.
144144
let persistentRefreshItems = NSHashTable<NSMenuItem>.weakObjects()
145145
var menuCardHeightCache: [MenuCardHeightCacheKey: CGFloat] = [:]

0 commit comments

Comments
 (0)