From d5fa07682dc99dc5e4786116ebcbed3d198e44ab Mon Sep 17 00:00:00 2001 From: Jiacheng Jiang Date: Thu, 5 Feb 2026 14:37:00 +0800 Subject: [PATCH 1/4] feat(kimi) fix kimi provider menu bar order --- Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift index c39e1602a..4e96e38ac 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift @@ -73,8 +73,8 @@ extension KimiUsageSnapshot { loginMethod: nil) return UsageSnapshot( - primary: weeklyWindow, - secondary: rateLimitWindow, + primary: rateLimitWindow ?? weeklyWindow, + secondary: weeklyWindow, tertiary: nil, providerCost: nil, updatedAt: self.updatedAt, From 7fbfa52b45f208ea7143d6e545a22ccb8c3f618e Mon Sep 17 00:00:00 2001 From: Jiacheng Jiang Date: Thu, 5 Feb 2026 20:09:08 +0800 Subject: [PATCH 2/4] fix(kimi): swap UI labels to match usage data order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the data swap in commit f14971a where rate limit became primary and weekly became secondary, update the UI labels to match: - sessionLabel (shown for primary): "Weekly" → "Rate Limit" - weeklyLabel (shown for secondary): "Rate Limit" → "Weekly" This ensures the UI displays: - "Rate Limit" label with rate limit data - "Weekly" label with weekly data Co-Authored-By: Claude Sonnet 4.5 --- .../CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift b/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift index 711c20bc8..bd7392baf 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift @@ -10,8 +10,8 @@ public enum KimiProviderDescriptor { metadata: ProviderMetadata( id: .kimi, displayName: "Kimi", - sessionLabel: "Weekly", - weeklyLabel: "Rate Limit", + sessionLabel: "Rate Limit", + weeklyLabel: "Weekly", opusLabel: nil, supportsOpus: false, supportsCredits: false, From c49b6919e80c4f7c74a842beed1f801cc26a3d96 Mon Sep 17 00:00:00 2001 From: Jiacheng Jiang Date: Thu, 5 Feb 2026 22:37:30 +0800 Subject: [PATCH 3/4] fix(kimi): avoid duplicate weekly usage when rate limit is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When rateLimitWindow is nil, set secondary to nil instead of weeklyWindow to prevent showing the same weekly metric twice under different labels. Before: - No rate limit → primary: weekly, secondary: weekly (duplicate) After: - No rate limit → primary: weekly, secondary: nil (correct) - Has rate limit → primary: rate limit, secondary: weekly (correct) Co-Authored-By: Claude Sonnet 4.5 --- Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift index 4e96e38ac..70ccca408 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift @@ -74,7 +74,7 @@ extension KimiUsageSnapshot { return UsageSnapshot( primary: rateLimitWindow ?? weeklyWindow, - secondary: weeklyWindow, + secondary: rateLimitWindow != nil ? weeklyWindow : nil, tertiary: nil, providerCost: nil, updatedAt: self.updatedAt, From ad6c7518bcabceb4eb3d09b2348c93f3fcee1037 Mon Sep 17 00:00:00 2001 From: Ratul Sarna Date: Tue, 17 Feb 2026 22:17:40 +0530 Subject: [PATCH 4/4] Scope Kimi short-window priority to automatic metric selection --- Sources/CodexBar/StatusItemController.swift | 2 +- Sources/CodexBar/UsageStore.swift | 4 +- .../Kimi/KimiProviderDescriptor.swift | 4 +- .../Providers/Kimi/KimiUsageSnapshot.swift | 4 +- .../StatusItemAnimationTests.swift | 39 +++++++++++++++++++ .../UsageStoreCoverageTests.swift | 28 +++++++++++++ 6 files changed, 74 insertions(+), 7 deletions(-) diff --git a/Sources/CodexBar/StatusItemController.swift b/Sources/CodexBar/StatusItemController.swift index dce63bf76..5a0a8a044 100644 --- a/Sources/CodexBar/StatusItemController.swift +++ b/Sources/CodexBar/StatusItemController.swift @@ -125,7 +125,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2 return RateWindow(usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil) case .automatic: - if provider == .factory { + if provider == .factory || provider == .kimi { return snapshot?.secondary ?? snapshot?.primary } return snapshot?.primary ?? snapshot?.secondary diff --git a/Sources/CodexBar/UsageStore.swift b/Sources/CodexBar/UsageStore.swift index b42856c71..5164328a1 100644 --- a/Sources/CodexBar/UsageStore.swift +++ b/Sources/CodexBar/UsageStore.swift @@ -314,8 +314,8 @@ final class UsageStore { for provider in self.enabledProviders() { guard let snapshot = self.snapshots[provider] else { continue } // Use the same window selection logic as menuBarPercentWindow: - // Factory uses secondary (premium) first, others use primary (session) first. - let window: RateWindow? = if provider == .factory { + // Factory and Kimi use secondary first, others use primary first. + let window: RateWindow? = if provider == .factory || provider == .kimi { snapshot.secondary ?? snapshot.primary } else { snapshot.primary ?? snapshot.secondary diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift b/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift index bd7392baf..711c20bc8 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift @@ -10,8 +10,8 @@ public enum KimiProviderDescriptor { metadata: ProviderMetadata( id: .kimi, displayName: "Kimi", - sessionLabel: "Rate Limit", - weeklyLabel: "Weekly", + sessionLabel: "Weekly", + weeklyLabel: "Rate Limit", opusLabel: nil, supportsOpus: false, supportsCredits: false, diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift index 70ccca408..c39e1602a 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift @@ -73,8 +73,8 @@ extension KimiUsageSnapshot { loginMethod: nil) return UsageSnapshot( - primary: rateLimitWindow ?? weeklyWindow, - secondary: rateLimitWindow != nil ? weeklyWindow : nil, + primary: weeklyWindow, + secondary: rateLimitWindow, tertiary: nil, providerCost: nil, updatedAt: self.updatedAt, diff --git a/Tests/CodexBarTests/StatusItemAnimationTests.swift b/Tests/CodexBarTests/StatusItemAnimationTests.swift index e4c8a5407..de7f4708e 100644 --- a/Tests/CodexBarTests/StatusItemAnimationTests.swift +++ b/Tests/CodexBarTests/StatusItemAnimationTests.swift @@ -269,6 +269,45 @@ struct StatusItemAnimationTests { #expect(window?.usedPercent == 42) } + @Test + func menuBarPercentAutomaticPrefersRateLimitForKimi() { + let settings = SettingsStore( + configStore: testConfigStore(suiteName: "StatusItemAnimationTests-kimi-automatic"), + zaiTokenStore: NoopZaiTokenStore()) + settings.statusChecksEnabled = false + settings.refreshFrequency = .manual + settings.mergeIcons = true + settings.selectedMenuProvider = .kimi + settings.setMenuBarMetricPreference(.automatic, for: .kimi) + + let registry = ProviderRegistry.shared + if let kimiMeta = registry.metadata[.kimi] { + settings.setProviderEnabled(provider: .kimi, metadata: kimiMeta, enabled: true) + } + + let fetcher = UsageFetcher() + let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) + let controller = StatusItemController( + store: store, + settings: settings, + account: fetcher.loadAccountInfo(), + updater: DisabledUpdaterController(), + preferencesSelection: PreferencesSelection(), + statusBar: self.makeStatusBarForTesting()) + + let snapshot = UsageSnapshot( + primary: RateWindow(usedPercent: 12, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 42, windowMinutes: 300, resetsAt: nil, resetDescription: nil), + updatedAt: Date()) + + store._setSnapshotForTesting(snapshot, provider: .kimi) + store._setErrorForTesting(nil, provider: .kimi) + + let window = controller.menuBarMetricWindow(for: .kimi, snapshot: snapshot) + + #expect(window?.usedPercent == 42) + } + @Test func menuBarPercentUsesAverageForGemini() { let settings = SettingsStore( diff --git a/Tests/CodexBarTests/UsageStoreCoverageTests.swift b/Tests/CodexBarTests/UsageStoreCoverageTests.swift index 889ff4f20..32ea42851 100644 --- a/Tests/CodexBarTests/UsageStoreCoverageTests.swift +++ b/Tests/CodexBarTests/UsageStoreCoverageTests.swift @@ -71,6 +71,34 @@ struct UsageStoreCoverageTests { #expect(label.contains("openai-web")) } + @Test + func providerWithHighestUsagePrefersKimiRateLimitWindow() throws { + let settings = Self.makeSettingsStore(suite: "UsageStoreCoverageTests-kimi-highest") + let store = Self.makeUsageStore(settings: settings) + let metadata = ProviderRegistry.shared.metadata + + try settings.setProviderEnabled(provider: .codex, metadata: #require(metadata[.codex]), enabled: true) + try settings.setProviderEnabled(provider: .kimi, metadata: #require(metadata[.kimi]), enabled: true) + + let now = Date() + store._setSnapshotForTesting( + UsageSnapshot( + primary: RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: nil, + updatedAt: now), + provider: .codex) + store._setSnapshotForTesting( + UsageSnapshot( + primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 80, windowMinutes: 300, resetsAt: nil, resetDescription: nil), + updatedAt: now), + provider: .kimi) + + let highest = store.providerWithHighestUsage() + #expect(highest?.provider == .kimi) + #expect(highest?.usedPercent == 80) + } + @Test func providerAvailabilityAndSubscriptionDetection() { let zaiStore = InMemoryZaiTokenStore(value: "zai-token")