Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +689 to +701
}()
let metrics = Self.metrics(input: input)
let usageNotes = Self.usageNotes(input: input)
let creditsText: String? = if input.provider == .openrouter {
Expand Down
57 changes: 57 additions & 0 deletions Sources/CodexBarCore/PromotionStatus.swift
Original file line number Diff line number Diff line change
@@ -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))!
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Align Claude promo end timestamp with documented PT cutoff

The Claude window currently ends at 2026-03-28 00:00 in America/New_York, but the comment and feature spec describe the promotion as lasting through March 27 11:59 PM PT (about 03:00 AM ET on March 28). As written, users will lose the badge for the final ~3 hours of the promo (9:00–11:59 PM PT on Mar 27), so the boundary should be computed in PT or shifted to the equivalent ET instant.

Useful? React with 👍 / 👎.

Comment on lines +24 to +31

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!
Comment on lines +36 to +37

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 }
Comment on lines +50 to +54
return Result(is2x: true, badge: "⚡ 2×")
}
}
Loading