Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 0.38.1 — Unreleased

### Added
- Codex: show a conservative 1.5× pace-headroom hint in menus and CLI output when usage is safely ahead of the reset curve. Thanks @astuteprogrammer!

### Changed
- Website: redesign codexbar.app around faster download, provider discovery, feature, CLI, and widget paths with responsive dark/light and localized layouts. Thanks @vyctorbrzezowski!
- Architecture: accept a bounded opt-in adaptive refresh design with a deterministic 2–30-minute cadence and no behavioral telemetry. Thanks @hhh2210!
Expand Down
3 changes: 2 additions & 1 deletion Sources/CodexBar/HistoricalUsagePace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,8 @@ enum CodexHistoricalPaceEvaluator {
actualUsedPercent: actual,
etaSeconds: etaSeconds,
willLastToReset: willLastToReset,
runOutProbability: runOutProbability)
runOutProbability: runOutProbability,
projectedRemainingUsage: max(0, (expectedCurve.last ?? expectedNow) - expectedNow))
}

private static func firstCrossing(
Expand Down
5 changes: 4 additions & 1 deletion Sources/CodexBar/MenuCardView+ModelHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -373,13 +373,14 @@ extension UsageMenuCardView.Model {
}

static func weeklyPaceDetail(
provider: UsageProvider,
window: RateWindow,
now: Date,
pace: UsagePace?,
showUsed: Bool) -> PaceDetail?
{
guard let pace else { return nil }
let detail = UsagePaceText.weeklyDetail(pace: pace, now: now)
let detail = UsagePaceText.weeklyDetail(provider: provider, pace: pace, now: now)
let expectedUsed = detail.expectedUsedPercent
let actualUsed = window.usedPercent
let expectedPercent = showUsed ? expectedUsed : (100 - expectedUsed)
Expand Down Expand Up @@ -426,6 +427,7 @@ extension UsageMenuCardView.Model {
workDays: input.workDaysPerWeek)
guard let resolved = Self.displayableWeeklyPace(resolved) else { return nil }
return Self.weeklyPaceDetail(
provider: input.provider,
window: window,
now: input.now,
pace: resolved,
Expand Down Expand Up @@ -591,6 +593,7 @@ extension UsageMenuCardView.Model {
defaultWindowMinutes: 10080,
workDays: input.workDaysPerWeek))
return Self.weeklyPaceDetail(
provider: provider,
window: window,
now: input.now,
pace: pace,
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ extension UsageMenuCardView.Model {
}
if let pace = input.weeklyPace {
let paceDetail = Self.weeklyPaceDetail(
provider: input.provider,
window: primary,
now: input.now,
pace: pace,
Expand Down Expand Up @@ -1373,6 +1374,7 @@ extension UsageMenuCardView.Model {
zaiTimeDetail: String?) -> Metric
{
var paceDetail = Self.weeklyPaceDetail(
provider: input.provider,
window: weekly,
now: input.now,
pace: input.weeklyPace,
Expand Down Expand Up @@ -1495,6 +1497,7 @@ extension UsageMenuCardView.Model {
title = L(input.metadata.weeklyLabel)
id = "secondary"
paceDetail = Self.weeklyPaceDetail(
provider: input.provider,
window: window,
now: input.now,
pace: Self.standardWeeklyPace(input: input, window: window),
Expand Down
4 changes: 2 additions & 2 deletions Sources/CodexBar/MenuDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ struct MenuDescriptor {
if provider == .abacus,
let pace = store.weeklyPace(provider: provider, window: primary)
{
let paceSummary = UsagePaceText.weeklySummary(pace: pace)
let paceSummary = UsagePaceText.weeklySummary(provider: provider, pace: pace)
entries.append(.text(paceSummary, .secondary))
}
if let paceSummary = UsagePaceText.sessionSummary(provider: provider, window: primary) {
Expand Down Expand Up @@ -220,7 +220,7 @@ struct MenuDescriptor {
entries.append(.text(detail, .secondary))
}
if let pace = store.weeklyPace(provider: provider, window: weekly) {
let paceSummary = UsagePaceText.weeklySummary(pace: pace)
let paceSummary = UsagePaceText.weeklySummary(provider: provider, pace: pace)
entries.append(.text(paceSummary, .secondary))
}
}
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@
"Resets now" = "إعادة التعيين الآن";
"reset_tomorrow_format" = "غدًا، %@";
"Lasts until reset" = "يستمر حتى إعادة التعيين";
"1.5× headroom" = "هامش 1.5×";
"Updated %@" = "تحديث %@";
"Updated relative %@" = "تحديث %@";
"Updated absolute %@" = "تحديث %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@
"Last 30 days: %@" = "Últims 30 dies: %@";
"Last 30 days: %@ · %@ tokens" = "Últims 30 dies: %@ · %@ tokens";
"Lasts until reset" = "Dura fins al reinici";
"1.5× headroom" = "marge d’1,5×";
"Login with Google" = "Inicieu sessió amb Google";
"login_success_notification_body" = "Podeu tornar a l'app; l'autenticació ha finalitzat.";
"login_success_notification_title" = "Inici de sessió de %@ correcte";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@
"Resets now" = "Wird jetzt zurückgesetzt";
"reset_tomorrow_format" = "morgen, %@";
"Lasts until reset" = "Hält bis zum Zurücksetzen an";
"1.5× headroom" = "1,5× Spielraum";
"Updated %@" = "Aktualisiert %@";
"Updated relative %@" = "Aktualisiert %@";
"Updated absolute %@" = "Aktualisiert %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@
"Resets now" = "Resets now";
"reset_tomorrow_format" = "tomorrow, %@";
"Lasts until reset" = "Lasts until reset";
"1.5× headroom" = "1.5× headroom";
"Updated %@" = "Updated %@";
"Updated relative %@" = "Updated %@";
"Updated absolute %@" = "Updated %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@
"no_writable_bin_dirs" = "No se encontraron directorios bin con permiso de escritura.";
"show_debug_settings_title" = "Mostrar ajustes de depuración";
"show_debug_settings_subtitle" = "Muestra herramientas de diagnóstico en la pestaña Depuración.";
"1.5× headroom" = "margen de 1,5×";
"surprise_me_title" = "Sorpréndeme";
"surprise_me_subtitle" = "Actívalo si te gusta que tus agentes se diviertan ahí arriba.";
"session_limit_confetti_title" = "Confeti del límite de sesión";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/fa.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@
"Resets now" = "اکنون بازنشانی می شود";
"reset_tomorrow_format" = "فردا، %@";
"Lasts until reset" = "تا زمان ریست ادامه دارد";
"1.5× headroom" = "حاشیه ۱٫۵×";
"Updated %@" = "به روزرسانی %@";
"Updated relative %@" = "به روزرسانی %@";
"Updated absolute %@" = "به روزرسانی %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@
"Resets now" = "Réinitialise maintenant";
"reset_tomorrow_format" = "demain, %@";
"Lasts until reset" = "Dure jusqu'à la réinitialisation";
"1.5× headroom" = "marge de 1,5×";
"Updated %@" = "%@ mis à jour";
"Updated relative %@" = "%@ mis à jour";
"Updated absolute %@" = "%@ mis à jour";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/id.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@
"Resets now" = "Reset sekarang";
"reset_tomorrow_format" = "besok, %@";
"Lasts until reset" = "Bertahan hingga reset";
"1.5× headroom" = "ruang 1,5×";
"Updated %@" = "Diperbarui %@";
"Updated relative %@" = "Diperbarui %@";
"Updated absolute %@" = "Diperbarui %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@
"Resets now" = "Si resetta ora";
"reset_tomorrow_format" = "domani, %@";
"Lasts until reset" = "Valido fino al reset";
"1.5× headroom" = "margine 1,5×";
"Updated %@" = "Aggiornato %@";
"Updated relative %@" = "Aggiornato %@";
"Updated absolute %@" = "Aggiornato %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@
"Resets now" = "まもなくリセット";
"reset_tomorrow_format" = "明日 %@";
"Lasts until reset" = "リセットまで持続";
"1.5× headroom" = "1.5倍の余裕";
"Updated %@" = "%@ に更新";
"Updated relative %@" = "%@ に更新";
"Updated absolute %@" = "%@ に更新";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@
"Resets now" = "지금 재설정";
"reset_tomorrow_format" = "내일 %@";
"Lasts until reset" = "재설정까지 유지";
"1.5× headroom" = "1.5배 여유";
"Updated %@" = "%@에 업데이트됨";
"Updated relative %@" = "%@에 업데이트됨";
"Updated absolute %@" = "%@에 업데이트됨";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/nl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@
"Resets now" = "Wordt nu gereset";
"reset_tomorrow_format" = "morgen, %@";
"Lasts until reset" = "Gaat mee tot reset";
"1.5× headroom" = "1,5× speelruimte";
"Updated %@" = "Bijgewerkt %@";
"Updated relative %@" = "Bijgewerkt %@";
"Updated absolute %@" = "Bijgewerkt %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/pl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@
"Resets now" = "Reset teraz";
"reset_tomorrow_format" = "jutro, %@";
"Lasts until reset" = "Wystarcza do resetu";
"1.5× headroom" = "zapas 1,5×";
"Updated %@" = "Zaktualizowano %@";
"Updated relative %@" = "Zaktualizowano %@";
"Updated absolute %@" = "Zaktualizowano %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@
"Resets now" = "Renova agora";
"reset_tomorrow_format" = "amanhã, %@";
"Lasts until reset" = "Dura até a renovação";
"1.5× headroom" = "folga de 1,5×";
"Updated %@" = "Atualizado %@";
"Updated relative %@" = "Atualizado %@";
"Updated absolute %@" = "Atualizado %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/sv.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@
"Resets now" = "Återställs nu";
"reset_tomorrow_format" = "imorgon %@";
"Lasts until reset" = "Räcker till återställning";
"1.5× headroom" = "1,5× marginal";
"Updated %@" = "Uppdaterad %@";
"Updated relative %@" = "Uppdaterad %@";
"Updated absolute %@" = "Uppdaterad %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/th.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@
"Resets now" = "รีเซ็ตเดี๋ยวนี้";
"reset_tomorrow_format" = "พรุ่งนี้ %@";
"Lasts until reset" = "คงอยู่จนกว่าจะรีเซ็ต";
"1.5× headroom" = "เผื่อ 1.5×";
"Updated %@" = "อัพเดท %@";
"Updated relative %@" = "อัพเดท %@";
"Updated absolute %@" = "อัพเดท %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/tr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@
"Resets now" = "Şimdi sıfırlanır";
"reset_tomorrow_format" = "yarın, %@";
"Lasts until reset" = "Sıfırlamaya kadar sürer";
"1.5× headroom" = "1,5× pay";
"Updated %@" = "Güncellendi: %@";
"Updated relative %@" = "Güncellendi: %@";
"Updated absolute %@" = "Güncellendi: %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@
"Resets now" = "Скидає зараз";
"reset_tomorrow_format" = "завтра, %@";
"Lasts until reset" = "Триває до скидання";
"1.5× headroom" = "запас 1,5×";
"Updated %@" = "Оновлено %@";
"Updated relative %@" = "Оновлено %@";
"Updated absolute %@" = "Оновлено %@";
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/vi.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@
"Resets now" = "Đặt lại ngay";
"reset_tomorrow_format" = "ngày mai, %@";
"Lasts until reset" = "Kéo dài cho đến Đặt lại";
"1.5× headroom" = "dư địa 1,5×";
"Updated %@" = "Đã cập nhật %@";
"Updated relative %@" = "Đã cập nhật %@";
"Updated absolute %@" = "Đã cập nhật %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@
"Resets now" = "立即重置";
"reset_tomorrow_format" = "明天 %@";
"Lasts until reset" = "持续到重置";
"1.5× headroom" = "1.5 倍余量";
"Updated %@" = "更新于 %@";
"Updated relative %@" = "%@已更新";
"Updated absolute %@" = "更新于 %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@
"no_writable_bin_dirs" = "找不到可寫的 bin 目錄。";
"show_debug_settings_title" = "顯示除錯設定";
"show_debug_settings_subtitle" = "在「除錯」標籤中顯示疑難排解工具。";
"1.5× headroom" = "1.5 倍餘裕";
"surprise_me_title" = "給我驚喜";
"surprise_me_subtitle" = "讓選單列上的 Agent 多一點變化。";
"session_limit_confetti_title" = "工作階段限制彩帶";
Expand Down
35 changes: 28 additions & 7 deletions Sources/CodexBar/UsagePaceText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ enum UsagePaceText {
case weekly
}

static func weeklySummary(pace: UsagePace, now: Date = .init()) -> String {
let detail = self.weeklyDetail(pace: pace, now: now)
static func weeklySummary(provider: UsageProvider, pace: UsagePace, now: Date = .init()) -> String {
let detail = self.weeklyDetail(provider: provider, pace: pace, now: now)
if let rightLabel = detail.rightLabel {
return L("Pace: %@ · %@", detail.leftLabel, rightLabel)
}
return L("Pace: %@", detail.leftLabel)
}

static func weeklyDetail(pace: UsagePace, now: Date = .init()) -> WeeklyDetail {
static func weeklyDetail(provider: UsageProvider, pace: UsagePace, now: Date = .init()) -> WeeklyDetail {
WeeklyDetail(
leftLabel: self.detailLeftLabel(for: pace),
rightLabel: self.detailRightLabel(for: pace, context: .weekly, now: now),
rightLabel: self.detailRightLabel(for: pace, provider: provider, context: .weekly, now: now),
expectedUsedPercent: pace.expectedUsedPercent,
stage: pace.stage)
}
Expand All @@ -45,10 +45,15 @@ enum UsagePaceText {
}
}

private static func detailRightLabel(for pace: UsagePace, context: DetailContext, now: Date) -> String? {
private static func detailRightLabel(
for pace: UsagePace,
provider: UsageProvider,
context: DetailContext,
now: Date) -> String?
{
let etaLabel: String?
if pace.willLastToReset {
etaLabel = L("Lasts until reset")
etaLabel = self.combinedLastsLabel(for: pace, provider: provider)
} else if let etaSeconds = pace.etaSeconds {
let etaText = Self.durationText(seconds: etaSeconds, now: now)
if context == .session {
Expand All @@ -72,6 +77,22 @@ enum UsagePaceText {
return riskLabel
}

private static func combinedLastsLabel(for pace: UsagePace, provider: UsageProvider) -> String {
guard provider == .codex else { return L("Lasts until reset") }
guard let speedLabel = self.speedHintLabel(for: pace) else {
return L("Lasts until reset")
}
return L("%@ · %@", L("Lasts until reset"), speedLabel)
}

private static func speedHintLabel(for pace: UsagePace) -> String? {
guard pace.deltaPercent < -15,
let multiplier = pace.speedMultiplierToReset,
multiplier >= 1.5
else { return nil }
return L("1.5× headroom")
}

private static func durationText(seconds: TimeInterval, now: Date) -> String {
let date = now.addingTimeInterval(seconds)
let countdown = UsageFormatter.resetCountdownDescription(from: date, now: now)
Expand Down Expand Up @@ -99,7 +120,7 @@ enum UsagePaceText {
guard let pace = sessionPace(provider: provider, window: window, now: now) else { return nil }
return WeeklyDetail(
leftLabel: Self.detailLeftLabel(for: pace),
rightLabel: Self.detailRightLabel(for: pace, context: .session, now: now),
rightLabel: Self.detailRightLabel(for: pace, provider: provider, context: .session, now: now),
expectedUsedPercent: pace.expectedUsedPercent,
stage: pace.stage)
}
Expand Down
Loading