diff --git a/CHANGELOG.md b/CHANGELOG.md index 2969a5a519..66618b38c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ - Memory: release idle OpenAI WebViews under system pressure without blocking the main thread. Thanks @ProspectOre! - Memory: trim rebuildable menu and OpenAI debug caches under system pressure. Thanks @ProspectOre! - Provider plans: keep Claude and Kiro plan matching on one rendered line to avoid bogus labels from adjacent usage hints. Thanks @elijahfriedman! +- Antigravity: use current Gemini 5-hour and weekly quota-summary lanes for the compact menu bar icon. Thanks @Zihao-Qi! +- Usage bars: render values rounded to 0% or 100% as fully empty or full. Thanks @Zihao-Qi! - Codex web: keep cookie-import deadlines responsive when browser cookie work blocks the shared worker pool. - Codex pace: extrapolate historically exhausted weeks for run-out forecasts and avoid contradictory reset headlines. Thanks @Yuxin-Qiao! - Localization: correct the German in-progress refresh label. Thanks @ChrisLauinger77! diff --git a/Sources/CodexBar/IconRemainingResolver.swift b/Sources/CodexBar/IconRemainingResolver.swift index 58bf810b3a..fe62830d16 100644 --- a/Sources/CodexBar/IconRemainingResolver.swift +++ b/Sources/CodexBar/IconRemainingResolver.swift @@ -2,6 +2,11 @@ import CodexBarCore enum IconRemainingResolver { private static let visibleZeroPercent = 0.0001 + private static let antigravityQuotaSummaryWindowIDPrefix = "antigravity-quota-summary-" + private static let antigravityGeminiQuotaBucketIDPrefix = "gemini-" + // Antigravity quota summaries currently expose exact 5-hour session and weekly buckets for the compact icon. + private static let sessionWindowMinutes = 5 * 60 + private static let weeklyWindowMinutes = 7 * 24 * 60 private static func codexProjection(snapshot: UsageSnapshot) -> CodexConsumerProjection { CodexConsumerProjection.make( @@ -23,13 +28,57 @@ enum IconRemainingResolver { return projection.visibleRateLanes.compactMap { projection.rateWindow(for: $0) } } - private static func antigravityVisibleWindows(snapshot: UsageSnapshot) -> [RateWindow] { - var windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self) - let compactFallbacks = snapshot.extraRateWindows? - .filter { $0.usageKnown && $0.id.hasPrefix("antigravity-compact-fallback-") } - .map(\.window) ?? [] - windows.append(contentsOf: compactFallbacks) - return windows + private static func antigravityQuotaSummaryWindows( + snapshot: UsageSnapshot) + -> (primary: RateWindow?, secondary: RateWindow?)? + { + let quotaSummaryWindows = snapshot.extraRateWindows? + .filter { + $0.id.hasPrefix(Self.antigravityQuotaSummaryWindowIDPrefix) + } ?? [] + guard !quotaSummaryWindows.isEmpty else { return nil } + + let geminiWindows = quotaSummaryWindows.filter(Self.isAntigravityGeminiQuotaSummaryWindow) + // The Antigravity menu-bar icon represents Gemini quotas. If any Gemini cadence is present, + // keep missing Gemini lanes empty instead of silently borrowing Claude + GPT quota. + if !geminiWindows.isEmpty { + return self.antigravityQuotaSummaryPair(in: geminiWindows.filter(\.usageKnown)) + ?? (primary: nil, secondary: nil) + } + return self.antigravityQuotaSummaryPair(in: quotaSummaryWindows.filter(\.usageKnown)) + } + + private static func antigravityQuotaSummaryPair( + in windows: [NamedRateWindow]) + -> (primary: RateWindow?, secondary: RateWindow?)? + { + let session = self.mostConstrainedWindow(in: windows, windowMinutes: Self.sessionWindowMinutes) + let weekly = self.mostConstrainedWindow(in: windows, windowMinutes: Self.weeklyWindowMinutes) + guard session != nil || weekly != nil else { return nil } + return (primary: session, secondary: weekly) + } + + private static func isAntigravityGeminiQuotaSummaryWindow(_ window: NamedRateWindow) -> Bool { + self.antigravityQuotaSummaryBucketID(for: window)?.hasPrefix(self.antigravityGeminiQuotaBucketIDPrefix) == true + } + + private static func antigravityQuotaSummaryBucketID(for window: NamedRateWindow) -> String? { + guard window.id.hasPrefix(self.antigravityQuotaSummaryWindowIDPrefix) else { return nil } + return String(window.id.dropFirst(self.antigravityQuotaSummaryWindowIDPrefix.count)) + } + + /// Returns the highest-usage window for an exact Antigravity compact-icon cadence. + private static func mostConstrainedWindow(in windows: [NamedRateWindow], windowMinutes: Int) -> RateWindow? { + windows + .filter { $0.window.windowMinutes == windowMinutes } + .max { lhs, rhs in + if lhs.window.usedPercent != rhs.window.usedPercent { + return lhs.window.usedPercent < rhs.window.usedPercent + } + // max(by:) keeps the right-hand element when this returns true; use `>` so the smallest id wins ties. + return lhs.id > rhs.id + }? + .window } static func resolvedWindows( @@ -45,10 +94,9 @@ enum IconRemainingResolver { secondary: windows.dropFirst().first) } if style == .antigravity { - let windows = self.antigravityVisibleWindows(snapshot: snapshot) - return ( - primary: windows.first, - secondary: windows.dropFirst().first) + // Only current quota-summary buckets define the fixed session/weekly icon lanes. + return self.antigravityQuotaSummaryWindows(snapshot: snapshot) + ?? (primary: nil, secondary: nil) } if style == .codex { let windows = self.codexVisibleWindows(snapshot: snapshot) @@ -88,6 +136,7 @@ enum IconRemainingResolver { snapshot: UsageSnapshot, style: IconStyle, showUsed: Bool, + renderingStyle: IconStyle? = nil, secondaryOverrideWindowID: String? = nil) -> (primary: Double?, secondary: Double?) { @@ -98,7 +147,9 @@ enum IconRemainingResolver { var percents = ( primary: showUsed ? windows.primary?.usedPercent : windows.primary?.remainingPercent, secondary: showUsed ? windows.secondary?.usedPercent : windows.secondary?.remainingPercent) - if showUsed, style == .warp, let secondary = windows.secondary { + // Provider style chooses the usage lanes; rendering style controls renderer-specific layout sentinels. + // Merged icons still resolve Warp's lanes, but render as `.combined` and must keep the real percentage. + if showUsed, style == .warp, (renderingStyle ?? style) == .warp, let secondary = windows.secondary { if secondary.remainingPercent <= 0 { // Preserve Warp's exhausted/no-bonus layout even though used percent is 100. percents.secondary = 0 diff --git a/Sources/CodexBar/StatusItemController+Animation.swift b/Sources/CodexBar/StatusItemController+Animation.swift index 1cbd29ca5e..2c22f592fa 100644 --- a/Sources/CodexBar/StatusItemController+Animation.swift +++ b/Sources/CodexBar/StatusItemController+Animation.swift @@ -244,6 +244,7 @@ extension StatusItemController { let showUsed = self.settings.usageBarsShowUsed let showBrandPercent = self.settings.menuBarShowsBrandIconWithPercent let primaryProvider = self.primaryProviderForUnifiedIcon() + let resolverStyle = self.store.style(for: primaryProvider) let snapshot = self.store.snapshot(for: primaryProvider) let warningFlash = self.quotaWarningFlashActive(provider: primaryProvider) @@ -252,8 +253,9 @@ extension StatusItemController { let resolved = snapshot.map { IconRemainingResolver.resolvedPercents( snapshot: $0, - style: style, + style: resolverStyle, showUsed: showUsed, + renderingStyle: style, secondaryOverrideWindowID: self.settings.copilotIconSecondaryWindowOverrideID(snapshot: $0)) } var primary = resolved?.primary diff --git a/Sources/CodexBar/UsageProgressBar.swift b/Sources/CodexBar/UsageProgressBar.swift index 63bdc68264..f8dcd559bc 100644 --- a/Sources/CodexBar/UsageProgressBar.swift +++ b/Sources/CodexBar/UsageProgressBar.swift @@ -48,7 +48,8 @@ struct UsageProgressBar: View { // which caused the status item icon to disappear (issue #805). Canvas { context, size in let scale = max(self.displayScale, 1) - let fillWidth = size.width * self.clamped / 100 + let fillPercent = Self.renderedFillPercent(self.clamped) + let fillWidth = size.width * fillPercent / 100 let paceWidth = size.width * Self.clampedPercent(self.pacePercent) / 100 let tipWidth = max(25, size.height * 6.5) let stripeInset = 1 / scale @@ -118,7 +119,16 @@ struct UsageProgressBar: View { } .frame(height: 6) .accessibilityLabel(self.accessibilityLabel) - .accessibilityValue("\(Int(self.clamped)) percent") + .accessibilityValue("\(Self.displayPercent(self.clamped)) percent") + } + + /// Aligns edge rendering with the rounded percent label: sub-0.5% is empty and 99.5%+ is full. + nonisolated static func renderedFillPercent(_ percent: Double) -> Double { + let clamped = Self.clampedPercent(percent) + let displayPercent = Self.displayPercent(clamped) + if displayPercent <= 0 { return 0 } + if displayPercent >= 100 { return 100 } + return clamped } private static func paceStripePaths(size: CGSize, scale: CGFloat) -> (punched: Path, center: Path) { @@ -188,7 +198,11 @@ struct UsageProgressBar: View { isHighlighted ? .white.opacity(0.72) : .primary.opacity(0.32) } - private static func clampedPercent(_ value: Double?) -> Double { + private nonisolated static func displayPercent(_ percent: Double) -> Int { + Int(self.clampedPercent(percent).rounded()) + } + + private nonisolated static func clampedPercent(_ value: Double?) -> Double { guard let value else { return 0 } return min(100, max(0, value)) } diff --git a/Tests/CodexBarTests/CodexbarTests.swift b/Tests/CodexBarTests/CodexbarTests.swift index 0146d5a0ca..169eead114 100644 --- a/Tests/CodexBarTests/CodexbarTests.swift +++ b/Tests/CodexBarTests/CodexbarTests.swift @@ -49,53 +49,230 @@ struct CodexBarTests { } @Test - func `antigravity icon falls back to tertiary when leading lanes are missing`() { + func `antigravity icon ignores legacy model quota lanes`() { let snapshot = UsageSnapshot( - primary: nil, - secondary: nil, + primary: RateWindow(usedPercent: 30, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: nil), tertiary: RateWindow(usedPercent: 80, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-compact-fallback-model", + title: "New Model", + window: RateWindow( + usedPercent: 64, + windowMinutes: nil, + resetsAt: nil, + resetDescription: nil)), + ], updatedAt: Date()) let remaining = IconRemainingResolver.resolvedRemaining(snapshot: snapshot, style: .antigravity) - #expect(remaining.primary == 20) + + #expect(remaining.primary == nil) #expect(remaining.secondary == nil) } @Test - func `antigravity icon uses next distinct fallback lane`() { + func `antigravity quota summary icon shows session on top and weekly on bottom`() { + let snapshot = UsageSnapshot( + primary: RateWindow(usedPercent: 84, windowMinutes: 10080, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 99, windowMinutes: 10080, resetsAt: nil, resetDescription: nil), + tertiary: nil, + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-quota-summary-gemini-weekly", + title: "Gemini Weekly", + window: RateWindow(usedPercent: 84, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-gemini-5h", + title: "Gemini Session", + window: RateWindow(usedPercent: 97, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-weekly", + title: "Claude + GPT Weekly", + window: RateWindow(usedPercent: 99, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-5h", + title: "Claude + GPT Session", + window: RateWindow(usedPercent: 98, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + ], + updatedAt: Date()) + + let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity) + + #expect(windows.primary?.windowMinutes == 300) + #expect(windows.primary?.remainingPercent == 3) + #expect(windows.secondary?.windowMinutes == 10080) + #expect(windows.secondary?.remainingPercent == 16) + } + + @Test + func `antigravity renderer draws primary above secondary`() throws { + let image = IconRenderer.makeIcon( + primaryRemaining: 100, + weeklyRemaining: 10, + creditsRemaining: nil, + stale: false, + style: .antigravity) + let bitmapReps = image.representations.compactMap { $0 as? NSBitmapImageRep } + let matchingRep = bitmapReps.first { rep in + rep.pixelsWide == 36 && rep.pixelsHigh == 36 + } + let rep = try #require(matchingRep) + + func averageAlpha(xRange: ClosedRange, yRange: ClosedRange) -> CGFloat { + var total: CGFloat = 0 + var count: CGFloat = 0 + for y in yRange { + for x in xRange { + total += (rep.colorAt(x: x, y: y) ?? .clear).alphaComponent + count += 1 + } + } + return total / count + } + + let visualTopRightAlpha = averageAlpha(xRange: 24...30, yRange: 7...10) + let visualBottomRightAlpha = averageAlpha(xRange: 24...30, yRange: 22...28) + + #expect(visualTopRightAlpha > visualBottomRightAlpha + 0.2) + } + + @Test + func `antigravity quota summary icon prefers gemini ids over display titles`() { let snapshot = UsageSnapshot( primary: nil, - secondary: RateWindow(usedPercent: 30, windowMinutes: nil, resetsAt: nil, resetDescription: nil), - tertiary: RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: nil, + tertiary: nil, + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-quota-summary-gemini-weekly", + title: "Renamed Weekly", + window: RateWindow(usedPercent: 30, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-gemini-5h", + title: "Renamed Session", + window: RateWindow(usedPercent: 40, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-weekly", + title: "Gemini Weekly", + window: RateWindow(usedPercent: 99, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-5h", + title: "Gemini Session", + window: RateWindow(usedPercent: 98, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + ], updatedAt: Date()) - let remaining = IconRemainingResolver.resolvedRemaining(snapshot: snapshot, style: .antigravity) - #expect(remaining.primary == 70) - #expect(remaining.secondary == 40) + let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity) + + #expect(windows.primary?.remainingPercent == 60) + #expect(windows.secondary?.remainingPercent == 70) } @Test - func `antigravity icon uses compact fallback model quota`() { + func `antigravity quota summary icon does not borrow missing gemini weekly from claude gpt`() { let snapshot = UsageSnapshot( primary: nil, secondary: nil, tertiary: nil, extraRateWindows: [ NamedRateWindow( - id: "antigravity-compact-fallback-model", - title: "New Model", + id: "antigravity-quota-summary-gemini-5h", + title: "Gemini Session", + window: RateWindow(usedPercent: 40, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-weekly", + title: "Claude + GPT Weekly", + window: RateWindow(usedPercent: 99, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + ], + updatedAt: Date()) + + let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity) + + #expect(windows.primary?.remainingPercent == 60) + #expect(windows.secondary == nil) + } + + @Test + func `antigravity quota summary icon treats unknown gemini rows as present`() { + let snapshot = UsageSnapshot( + primary: nil, + secondary: nil, + tertiary: nil, + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-quota-summary-gemini-weekly", + title: "Gemini Weekly", + window: RateWindow(usedPercent: 100, windowMinutes: 10080, resetsAt: nil, resetDescription: nil), + usageKnown: false), + NamedRateWindow( + id: "antigravity-quota-summary-3p-weekly", + title: "Claude + GPT Weekly", + window: RateWindow(usedPercent: 99, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + ], + updatedAt: Date()) + + let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity) + + #expect(windows.primary == nil) + #expect(windows.secondary == nil) + } + + @Test + func `antigravity quota summary icon falls back when gemini rows are absent`() { + let snapshot = UsageSnapshot( + primary: nil, + secondary: nil, + tertiary: nil, + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-quota-summary-3p-5h", + title: "Claude + GPT Session", + window: RateWindow(usedPercent: 75, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-weekly", + title: "Claude + GPT Weekly", + window: RateWindow(usedPercent: 88, windowMinutes: 10080, resetsAt: nil, resetDescription: nil)), + ], + updatedAt: Date()) + + let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity) + + #expect(windows.primary?.remainingPercent == 25) + #expect(windows.secondary?.remainingPercent == 12) + } + + @Test + func `antigravity quota summary icon tie break is stable`() { + let snapshot = UsageSnapshot( + primary: nil, + secondary: nil, + tertiary: nil, + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-quota-summary-gemini-z-5h", + title: "Gemini Session", window: RateWindow( - usedPercent: 64, - windowMinutes: nil, + usedPercent: 50, + windowMinutes: 300, resetsAt: nil, - resetDescription: nil)), + resetDescription: "second-by-id")), + NamedRateWindow( + id: "antigravity-quota-summary-gemini-a-5h", + title: "Gemini Session", + window: RateWindow( + usedPercent: 50, + windowMinutes: 300, + resetsAt: nil, + resetDescription: "first-by-id")), ], updatedAt: Date()) - let remaining = IconRemainingResolver.resolvedRemaining(snapshot: snapshot, style: .antigravity) + let windows = IconRemainingResolver.resolvedWindows(snapshot: snapshot, style: .antigravity) - #expect(remaining.primary == 36) - #expect(remaining.secondary == nil) + #expect(windows.primary?.resetDescription == "first-by-id") + #expect(windows.secondary == nil) } @Test @@ -373,6 +550,23 @@ struct CodexBarTests { #expect(percents.secondary ?? 1 < 0.01) } + @Test + func `merged icon keeps exhausted warp bonus fully used`() { + let snapshot = UsageSnapshot( + primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + updatedAt: Date()) + + let percents = IconRemainingResolver.resolvedPercents( + snapshot: snapshot, + style: .warp, + showUsed: true, + renderingStyle: .combined) + + #expect(percents.primary == 10) + #expect(percents.secondary == 100) + } + @Test @MainActor func `status icon accessibility uses percentage scale`() { diff --git a/Tests/CodexBarTests/MenuCardQuotaWarningMarkerTests.swift b/Tests/CodexBarTests/MenuCardQuotaWarningMarkerTests.swift index 58c3c28a21..89c8ca9e16 100644 --- a/Tests/CodexBarTests/MenuCardQuotaWarningMarkerTests.swift +++ b/Tests/CodexBarTests/MenuCardQuotaWarningMarkerTests.swift @@ -4,6 +4,14 @@ import Testing @testable import CodexBar struct MenuCardQuotaWarningMarkerTests { + @Test + func `progress fill matches rounded edge labels`() { + #expect(UsageProgressBar.renderedFillPercent(0.4) == 0) + #expect(UsageProgressBar.renderedFillPercent(0.6) == 0.6) + #expect(UsageProgressBar.renderedFillPercent(99.4) == 99.4) + #expect(UsageProgressBar.renderedFillPercent(99.6) == 100) + } + @Test func `quota warning marker geometry is inset and hairline`() { let rect = UsageProgressBar.warningMarkerRect( diff --git a/Tests/CodexBarTests/StatusItemAnimationSignatureTests.swift b/Tests/CodexBarTests/StatusItemAnimationSignatureTests.swift index 56a5718e2e..a2451ff737 100644 --- a/Tests/CodexBarTests/StatusItemAnimationSignatureTests.swift +++ b/Tests/CodexBarTests/StatusItemAnimationSignatureTests.swift @@ -65,6 +65,83 @@ struct StatusItemAnimationSignatureTests { #expect(codexSignature?.contains("style=codex") == true) } + @Test + func `merged antigravity icon resolves quota summary with provider style`() throws { + let suite = "StatusItemAnimationSignatureTests-merged-antigravity-provider-style" + let settings = testSettingsStore(suiteName: suite) + settings.statusChecksEnabled = false + settings.refreshFrequency = .manual + settings.mergeIcons = true + settings.selectedMenuProvider = .antigravity + settings.menuBarShowsBrandIconWithPercent = false + settings.usageBarsShowUsed = false + settings.syntheticAPIToken = "synthetic-test-token" + + let registry = ProviderRegistry.shared + if let antigravityMeta = registry.metadata[.antigravity] { + settings.setProviderEnabled(provider: .antigravity, metadata: antigravityMeta, enabled: true) + } + if let syntheticMeta = registry.metadata[.synthetic] { + settings.setProviderEnabled(provider: .synthetic, metadata: syntheticMeta, 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: testStatusBar()) + defer { controller.releaseStatusItemsForTesting() } + + store._setSnapshotForTesting( + UsageSnapshot( + primary: RateWindow(usedPercent: 99, windowMinutes: 10080, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 16, windowMinutes: 10080, resetsAt: nil, resetDescription: nil), + tertiary: nil, + extraRateWindows: [ + NamedRateWindow( + id: "antigravity-quota-summary-gemini-5h", + title: "Gemini Session", + window: RateWindow(usedPercent: 1, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-gemini-weekly", + title: "Gemini Weekly", + window: RateWindow( + usedPercent: 99, + windowMinutes: 10080, + resetsAt: nil, + resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-5h", + title: "Claude + GPT Session", + window: RateWindow(usedPercent: 2, windowMinutes: 300, resetsAt: nil, resetDescription: nil)), + NamedRateWindow( + id: "antigravity-quota-summary-3p-weekly", + title: "Claude + GPT Weekly", + window: RateWindow( + usedPercent: 16, + windowMinutes: 10080, + resetsAt: nil, + resetDescription: nil)), + ], + updatedAt: Date()), + provider: .antigravity) + + #expect(store.iconStyle == .combined) + #expect(controller.primaryProviderForUnifiedIcon() == .antigravity) + + controller.applyIcon(phase: nil) + let signature = try #require(controller.lastAppliedMergedIconRenderSignature) + + #expect(signature.contains("provider=antigravity")) + #expect(signature.contains("style=combined")) + #expect(signature.contains("primary=99.000")) + #expect(signature.contains("weekly=1.000")) + } + @Test func `merged brand percent reapplies title when cached render is skipped`() throws { let suite = "StatusItemAnimationSignatureTests-merged-brand-percent-title-restore"