From a0fbcf34d09f0b712540a78c7de365d59cd89b8b Mon Sep 17 00:00:00 2001 From: Deveshb15 Date: Mon, 16 Mar 2026 11:29:37 +0530 Subject: [PATCH] feat: add 2x usage indicator for Claude and Codex promotions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show promotion status badge next to plan text in the menu card header. Claude Spring Break (Mar 13–27): "⚡ 2×" during off-peak, "Peak" during peak hours. Codex: "⚡ 2×" 24/7 until Apr 2, 2026. Co-Authored-By: Claude Opus 4.6 --- Sources/CodexBar/MenuCardView.swift | 19 ++++++-- Sources/CodexBarCore/PromotionStatus.swift | 57 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 Sources/CodexBarCore/PromotionStatus.swift diff --git a/Sources/CodexBar/MenuCardView.swift b/Sources/CodexBar/MenuCardView.swift index 34ac51846..3438a0af6 100644 --- a/Sources/CodexBar/MenuCardView.swift +++ b/Sources/CodexBar/MenuCardView.swift @@ -686,11 +686,20 @@ extension UsageMenuCardView.Model { } static func make(_ input: Input) -> UsageMenuCardView.Model { - let planText = Self.plan( - for: input.provider, - snapshot: input.snapshot, - account: input.account, - metadata: input.metadata) + let planText: String? = { + let base = Self.plan( + for: input.provider, + snapshot: input.snapshot, + account: input.account, + metadata: input.metadata) + guard let promo = PromotionStatus.check(provider: input.provider, now: input.now) else { + return base + } + if let plan = base { + return "\(plan) · \(promo.badge)" + } + return promo.badge + }() let metrics = Self.metrics(input: input) let usageNotes = Self.usageNotes(input: input) let creditsText: String? = if input.provider == .openrouter { diff --git a/Sources/CodexBarCore/PromotionStatus.swift b/Sources/CodexBarCore/PromotionStatus.swift new file mode 100644 index 000000000..1e9594a11 --- /dev/null +++ b/Sources/CodexBarCore/PromotionStatus.swift @@ -0,0 +1,57 @@ +import Foundation + +public enum PromotionStatus: Sendable { + public struct Result: Sendable { + public let is2x: Bool + public let badge: String + } + + /// Returns `nil` if no promotion applies to this provider at the given time. + public static func check(provider: UsageProvider, now: Date = Date()) -> Result? { + switch provider { + case .claude: + return checkClaude(now: now) + case .codex: + return checkCodex(now: now) + default: + return nil + } + } + + // MARK: - Claude Spring Break (March 13–27, 2026) + + private static func checkClaude(now: Date) -> Result? { + let et = TimeZone(identifier: "America/New_York")! + var cal = Calendar(identifier: .gregorian) + cal.timeZone = et + + // Promo window: March 13, 2026 00:00 ET → March 28, 2026 00:00 ET + // (March 27 11:59 PM PT ≈ March 28 ~03:00 AM ET) + let start = cal.date(from: DateComponents(year: 2026, month: 3, day: 13))! + let end = cal.date(from: DateComponents(year: 2026, month: 3, day: 28))! + + guard now >= start, now < end else { return nil } + + let comps = cal.dateComponents([.weekday, .hour], from: now) + let weekday = comps.weekday! // 1 = Sun, 7 = Sat + let hour = comps.hour! + + let isWeekend = weekday == 1 || weekday == 7 + let isPeak = !isWeekend && hour >= 8 && hour < 14 + + if isPeak { + return Result(is2x: false, badge: "Peak") + } + return Result(is2x: true, badge: "⚡ 2×") + } + + // MARK: - Codex 2× (until April 2, 2026) + + private static func checkCodex(now: Date) -> Result? { + var cal = Calendar(identifier: .gregorian) + cal.timeZone = TimeZone(identifier: "America/New_York")! + let end = cal.date(from: DateComponents(year: 2026, month: 4, day: 2))! + guard now < end else { return nil } + return Result(is2x: true, badge: "⚡ 2×") + } +}