diff --git a/Sources/CodexBar/MenuCardView.swift b/Sources/CodexBar/MenuCardView.swift index 9da4f9e04..d6f316e04 100644 --- a/Sources/CodexBar/MenuCardView.swift +++ b/Sources/CodexBar/MenuCardView.swift @@ -230,10 +230,12 @@ private struct UsageMenuCardHeaderView: View { Text(self.model.providerName) .font(.headline) .fontWeight(.semibold) - Spacer() - Text(self.model.email) - .font(.subheadline) - .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) + if !self.model.email.isEmpty { + Spacer() + Text(self.model.email) + .font(.subheadline) + .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted)) + } } let subtitleAlignment: VerticalAlignment = self.model.subtitleStyle == .error ? .top : .firstTextBaseline HStack(alignment: subtitleAlignment) { @@ -666,6 +668,7 @@ extension UsageMenuCardView.Model { let resetTimeDisplayStyle: ResetTimeDisplayStyle let tokenCostUsageEnabled: Bool let showOptionalCreditsAndExtraUsage: Bool + let showsHeaderEmail: Bool let sourceLabel: String? let kiloAutoMode: Bool let hidePersonalInfo: Bool @@ -689,6 +692,7 @@ extension UsageMenuCardView.Model { resetTimeDisplayStyle: ResetTimeDisplayStyle, tokenCostUsageEnabled: Bool, showOptionalCreditsAndExtraUsage: Bool, + showsHeaderEmail: Bool = true, sourceLabel: String? = nil, kiloAutoMode: Bool = false, hidePersonalInfo: Bool, @@ -711,6 +715,7 @@ extension UsageMenuCardView.Model { self.resetTimeDisplayStyle = resetTimeDisplayStyle self.tokenCostUsageEnabled = tokenCostUsageEnabled self.showOptionalCreditsAndExtraUsage = showOptionalCreditsAndExtraUsage + self.showsHeaderEmail = showsHeaderEmail self.sourceLabel = sourceLabel self.kiloAutoMode = kiloAutoMode self.hidePersonalInfo = hidePersonalInfo @@ -754,7 +759,7 @@ extension UsageMenuCardView.Model { return UsageMenuCardView.Model( provider: input.provider, providerName: input.metadata.displayName, - email: redacted.email, + email: input.showsHeaderEmail ? redacted.email : "", subtitleText: redacted.subtitleText, subtitleStyle: subtitle.style, planText: planText, diff --git a/Sources/CodexBar/StatusItemController+Menu.swift b/Sources/CodexBar/StatusItemController+Menu.swift index 7e608ea33..c20caa5f8 100644 --- a/Sources/CodexBar/StatusItemController+Menu.swift +++ b/Sources/CodexBar/StatusItemController+Menu.swift @@ -48,26 +48,6 @@ extension StatusItemController { private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit private static let overviewRowIdentifierPrefix = "overviewRow-" private static let menuOpenRefreshDelay: Duration = .seconds(1.2) - private struct OpenAIWebMenuItems { - let hasUsageBreakdown: Bool - let hasCreditsHistory: Bool - let hasCostHistory: Bool - } - - private struct TokenAccountMenuDisplay { - let provider: UsageProvider - let accounts: [ProviderTokenAccount] - let snapshots: [TokenAccountUsageSnapshot] - let activeIndex: Int - let showAll: Bool - let showSwitcher: Bool - } - - private struct CodexAccountMenuDisplay { - let accounts: [CodexVisibleAccount] - let activeVisibleAccountID: String? - } - private func menuCardWidth(for providers: [UsageProvider], menu: NSMenu? = nil) -> CGFloat { _ = menu return Self.menuCardBaseWidth @@ -232,6 +212,7 @@ extension StatusItemController { currentProvider: currentProvider, selectedProvider: selectedProvider, menuWidth: menuWidth, + showsHeaderEmail: codexAccountDisplay == nil, tokenAccountDisplay: tokenAccountDisplay, openAIContext: openAIContext) if isOverviewSelected { @@ -296,6 +277,7 @@ extension StatusItemController { currentProvider: currentProvider, selectedProvider: provider, menuWidth: menuWidth, + showsHeaderEmail: true, tokenAccountDisplay: nil, openAIContext: openAIContext) let addedOpenAIWebItems = self.addMenuCards(to: menu, context: menuContext) @@ -310,21 +292,6 @@ extension StatusItemController { self.addActionableSections(descriptor.sections, to: menu, width: menuWidth) } - private struct OpenAIWebContext { - let hasUsageBreakdown: Bool - let hasCreditsHistory: Bool - let hasCostHistory: Bool - let hasOpenAIWebMenuItems: Bool - } - - private struct MenuCardContext { - let currentProvider: UsageProvider - let selectedProvider: UsageProvider? - let menuWidth: CGFloat - let tokenAccountDisplay: TokenAccountMenuDisplay? - let openAIContext: OpenAIWebContext - } - private func openAIWebContext( currentProvider: UsageProvider, showAllTokenAccounts: Bool) -> OpenAIWebContext @@ -436,9 +403,12 @@ extension StatusItemController { self.menuCardModel( for: context.currentProvider, snapshotOverride: accountSnapshot.snapshot, - errorOverride: accountSnapshot.error) + errorOverride: accountSnapshot.error, + showsHeaderEmail: context.showsHeaderEmail) } - if cards.isEmpty, let model = self.menuCardModel(for: context.selectedProvider) { + if cards.isEmpty, + let model = self.menuCardModel(for: context.selectedProvider, showsHeaderEmail: context.showsHeaderEmail) + { menu.addItem(self.makeMenuCardItem( UsageMenuCardView(model: model, width: context.menuWidth), id: "menuCard", @@ -461,7 +431,10 @@ extension StatusItemController { return false } - guard let model = self.menuCardModel(for: context.selectedProvider) else { return false } + guard let model = self.menuCardModel(for: context.selectedProvider, showsHeaderEmail: context.showsHeaderEmail) + else { + return false + } if context.openAIContext.hasOpenAIWebMenuItems { let webItems = OpenAIWebMenuItems( hasUsageBreakdown: context.openAIContext.hasUsageBreakdown, @@ -1525,7 +1498,8 @@ extension StatusItemController { func menuCardModel( for provider: UsageProvider?, snapshotOverride: UsageSnapshot? = nil, - errorOverride: String? = nil) -> UsageMenuCardView.Model? + errorOverride: String? = nil, + showsHeaderEmail: Bool = true) -> UsageMenuCardView.Model? { let target = provider ?? self.store.enabledProvidersForDisplay().first ?? .codex let metadata = self.store.metadata(for: target) @@ -1583,6 +1557,7 @@ extension StatusItemController { resetTimeDisplayStyle: self.settings.resetTimeDisplayStyle, tokenCostUsageEnabled: self.settings.isCostUsageEffectivelyEnabled(for: target), showOptionalCreditsAndExtraUsage: self.settings.showOptionalCreditsAndExtraUsage, + showsHeaderEmail: showsHeaderEmail, sourceLabel: sourceLabel, kiloAutoMode: kiloAutoMode, hidePersonalInfo: self.settings.hidePersonalInfo, diff --git a/Sources/CodexBar/StatusItemController+MenuTypes.swift b/Sources/CodexBar/StatusItemController+MenuTypes.swift new file mode 100644 index 000000000..31494ccdc --- /dev/null +++ b/Sources/CodexBar/StatusItemController+MenuTypes.swift @@ -0,0 +1,38 @@ +import AppKit +import CodexBarCore + +struct OpenAIWebMenuItems { + let hasUsageBreakdown: Bool + let hasCreditsHistory: Bool + let hasCostHistory: Bool +} + +struct TokenAccountMenuDisplay { + let provider: UsageProvider + let accounts: [ProviderTokenAccount] + let snapshots: [TokenAccountUsageSnapshot] + let activeIndex: Int + let showAll: Bool + let showSwitcher: Bool +} + +struct CodexAccountMenuDisplay { + let accounts: [CodexVisibleAccount] + let activeVisibleAccountID: String? +} + +struct OpenAIWebContext { + let hasUsageBreakdown: Bool + let hasCreditsHistory: Bool + let hasCostHistory: Bool + let hasOpenAIWebMenuItems: Bool +} + +struct MenuCardContext { + let currentProvider: UsageProvider + let selectedProvider: UsageProvider? + let menuWidth: CGFloat + let showsHeaderEmail: Bool + let tokenAccountDisplay: TokenAccountMenuDisplay? + let openAIContext: OpenAIWebContext +} diff --git a/Tests/CodexBarTests/MenuCardHeaderVisibilityTests.swift b/Tests/CodexBarTests/MenuCardHeaderVisibilityTests.swift new file mode 100644 index 000000000..1da26d89e --- /dev/null +++ b/Tests/CodexBarTests/MenuCardHeaderVisibilityTests.swift @@ -0,0 +1,51 @@ +import CodexBarCore +import Foundation +import Testing +@testable import CodexBar + +struct MenuCardHeaderVisibilityTests { + @Test + func `suppresses header email when requested`() throws { + let now = Date() + let identity = ProviderIdentitySnapshot( + providerID: .codex, + accountEmail: "codex@example.com", + accountOrganization: nil, + loginMethod: "Pro") + let snapshot = UsageSnapshot( + primary: RateWindow( + usedPercent: 0, + windowMinutes: 300, + resetsAt: now.addingTimeInterval(3000), + resetDescription: nil), + secondary: nil, + tertiary: nil, + updatedAt: now, + identity: identity) + let metadata = try #require(ProviderDefaults.metadata[.codex]) + + let model = UsageMenuCardView.Model.make(.init( + provider: .codex, + metadata: metadata, + snapshot: snapshot, + credits: nil, + creditsError: nil, + dashboard: nil, + dashboardError: nil, + tokenSnapshot: nil, + tokenError: nil, + account: AccountInfo(email: "codex@example.com", plan: "Pro"), + isRefreshing: false, + lastError: nil, + usageBarsShowUsed: false, + resetTimeDisplayStyle: .countdown, + tokenCostUsageEnabled: false, + showOptionalCreditsAndExtraUsage: true, + showsHeaderEmail: false, + hidePersonalInfo: false, + now: now)) + + #expect(model.email.isEmpty) + #expect(model.planText == "Pro") + } +}