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
1 change: 1 addition & 0 deletions apps/desktop-tauri/src-tauri/src/auto_refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ mod tests {
id: "claude-routines".to_string(),
title: "Daily Routines".to_string(),
window: window_resetting_at(Some("2026-07-21T03:00:00Z")),
amount: None,
});

let mut times = snapshot_reset_times(&snapshot);
Expand Down
58 changes: 58 additions & 0 deletions apps/desktop-tauri/src-tauri/src/capacity_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,10 +1000,68 @@ mod tests {
id: id.into(),
title: title.into(),
window: window(used, reset),
amount: None,
});
snapshot
}

#[test]
fn third_and_model_windows_are_observed_without_displacing_core_slots() {
// Neither slot reached this map before, so their resets and exhaustions
// were invisible to events. Both share a cadence with a core window in
// the wild (Claude's model pool is 7 days like its weekly), and the map
// is keyed by id — a shared key silently drops one of the two.
let now = Utc::now();
let reset = now + Duration::hours(3);
let mut snapshot = snapshot(now, 10.0, reset);
snapshot.secondary = Some(RateWindowSnapshot {
window_minutes: Some(10_080),
..window(20.0, reset)
});
snapshot.secondary_label = Some("Weekly".into());
snapshot.model_specific = Some(RateWindowSnapshot {
window_minutes: Some(10_080),
..window(96.0, reset)
});
snapshot.tertiary = Some(RateWindowSnapshot {
window_minutes: Some(43_200),
..window(57.0, reset)
});
snapshot.tertiary_label = Some("Monthly".into());

let (windows, extra_ids) = observed_windows(&snapshot);

assert_eq!(windows["weekly"].used_percent, 20.0);
assert_eq!(windows["model"].used_percent, 96.0);
assert_eq!(windows["monthly"].used_percent, 57.0);
assert_eq!(windows["monthly"].label, "Monthly");
// Not "extras": an extra id appearing for the first time announces a
// granted allowance, and these windows have been there all along.
assert!(!extra_ids.contains("model"));
assert!(!extra_ids.contains("monthly"));
}

#[test]
fn a_third_window_sharing_a_cadence_falls_back_to_its_own_id() {
let now = Utc::now();
let reset = now + Duration::hours(3);
let mut snapshot = snapshot(now, 10.0, reset);
snapshot.secondary = Some(RateWindowSnapshot {
window_minutes: Some(10_080),
..window(20.0, reset)
});
snapshot.secondary_label = Some("Weekly".into());
snapshot.tertiary = Some(RateWindowSnapshot {
window_minutes: Some(10_080),
..window(80.0, reset)
});

let (windows, _) = observed_windows(&snapshot);

assert_eq!(windows["weekly"].used_percent, 20.0);
assert_eq!(windows["tertiary"].used_percent, 80.0);
}

fn with_inactive(
mut snapshot: ProviderUsageSnapshot,
id: &str,
Expand Down
25 changes: 25 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ pub struct NamedRateWindowSnapshot {
pub id: String,
pub title: String,
pub window: RateWindowSnapshot,
/// Money behind this lane, for providers that denominate it that way.
pub amount: Option<WindowAmountBridge>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WindowAmountBridge {
pub used: f64,
pub limit: Option<f64>,
pub currency_code: String,
pub formatted_used: String,
pub formatted_limit: Option<String>,
}

impl WindowAmountBridge {
fn from_amount(amount: &codexbar::core::WindowAmount) -> Self {
Self {
used: amount.used,
limit: amount.limit,
currency_code: amount.currency_code.clone(),
formatted_used: amount.format_used(),
formatted_limit: amount.format_limit(),
}
}
}

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -283,6 +307,7 @@ impl ProviderUsageSnapshot {
id: extra.id.clone(),
title: extra.title.clone(),
window: RateWindowSnapshot::from_rate_window(&extra.window),
amount: extra.amount.as_ref().map(WindowAmountBridge::from_amount),
})
.collect(),
inactive_rate_windows: usage
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -993,11 +993,13 @@ fn hiding_codex_spark_rows_preserves_other_extra_usage() {
id: "codex-spark".to_string(),
title: "Codex Spark 5-hour".to_string(),
window: snapshot.primary.clone(),
amount: None,
},
NamedRateWindowSnapshot {
id: "credits".to_string(),
title: "Credits".to_string(),
window: snapshot.primary.clone(),
amount: None,
},
];

Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/enforcement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ mod tests {
id: "codex-spark-weekly".into(),
title: "Codex Spark Weekly".into(),
window: window(Some(10_080)),
amount: None,
});
tracker.annotate(&mut with_spark);

Expand All @@ -399,6 +400,7 @@ mod tests {
id: "codex-spark-weekly".into(),
title: "Codex Spark Weekly".into(),
window: window(Some(10_080)),
amount: None,
});
tracker.annotate(&mut with_spark);

Expand Down
5 changes: 5 additions & 0 deletions apps/desktop-tauri/src-tauri/src/taskbar_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2228,6 +2228,7 @@ mod tests {
id: "cursor-api".into(),
title: "API".into(),
window: rate_window(100.0, Some(10_080)),
amount: None,
});

let readout = constraining_readout(&snapshot);
Expand All @@ -2247,6 +2248,7 @@ mod tests {
id: "cursor-api".into(),
title: "API".into(),
window: rate_window(40.0, Some(10_080)),
amount: None,
});

let readout = constraining_readout(&snapshot);
Expand All @@ -2265,6 +2267,7 @@ mod tests {
id: "cursor-api".into(),
title: " ".into(),
window: rate_window(70.0, Some(10_080)),
amount: None,
});

let readout = constraining_readout(&snapshot);
Expand All @@ -2291,6 +2294,7 @@ mod tests {
id: "cursor-api".into(),
title: "API".into(),
window: rate_window(30.0, Some(10_080)),
amount: None,
});

let readout = constraining_readout(&snapshot);
Expand All @@ -2315,6 +2319,7 @@ mod tests {
id: "cursor-api".into(),
title: "API".into(),
window: soon,
amount: None,
});

let readout = constraining_readout(&snapshot);
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src-tauri/src/tray_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,7 @@ mod tests {
reserve_will_last_to_reset: false,
reserve_eta_seconds: None,
},
amount: None,
}
}

Expand Down
47 changes: 47 additions & 0 deletions apps/desktop-tauri/src/components/MenuCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ describe("MenuCard", () => {
PanelOneHour: "1h",
PanelFiveHours: "5h",
PanelOnPaceBudget: "On-pace budget",
PanelAmountOf: "of",
PanelReserveSuffix: "in reserve",
PanelThirtyDayCost: "30d cost",
PanelThirtyDayTokens: "30d tokens",
Expand Down Expand Up @@ -234,6 +235,52 @@ describe("MenuCard", () => {
expect(screen.getByText("58% left")).toBeInTheDocument();
});

it("shows the money behind a lane that is denominated in currency", async () => {
const snapshot = provider(null, 20);
snapshot.providerId = "cursor";
snapshot.extraRateWindows = [
{
id: "cursor-on-demand",
title: "On-demand",
window: rateWindow(35),
amount: {
used: 3.5,
limit: 10,
currencyCode: "USD",
formattedUsed: "$3.50",
formattedLimit: "$10.00",
},
},
];

renderCard(snapshot);

expect(await screen.findByText("On-demand")).toBeInTheDocument();
expect(screen.getByText("$3.50 of $10.00")).toBeInTheDocument();
});

it("shows a lane's spend alone when it has no limit", async () => {
const snapshot = provider(null, 20);
snapshot.extraRateWindows = [
{
id: "cursor-on-demand",
title: "On-demand",
window: rateWindow(0),
amount: {
used: 12.34,
limit: null,
currencyCode: "USD",
formattedUsed: "$12.34",
formattedLimit: null,
},
},
];

renderCard(snapshot);

expect(await screen.findByText("$12.34")).toBeInTheDocument();
});

it("renders inactive windows as text without inventing a percentage", async () => {
const snapshot = provider(null, 40);
snapshot.secondary = rateWindow(55);
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop-tauri/src/components/MenuCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
ProviderLocalUsageSummary,
ProviderUsageSnapshot,
RateWindowSnapshot,
WindowAmountBridge,
} from "../types/bridge";
import { getProviderChartData } from "../lib/tauri";
import { useLocale } from "../hooks/useLocale";
Expand Down Expand Up @@ -246,6 +247,8 @@ interface MetricEntry {
id: string;
label: string;
snap: RateWindowSnapshot;
/** Money behind this lane, for providers that meter in currency. */
amount?: WindowAmountBridge | null;
}

interface InactiveMetricEntry {
Expand Down Expand Up @@ -284,6 +287,7 @@ function getMetricPaceView(snap: RateWindowSnapshot): MetricPaceView {
function MetricRow({
title,
snap,
amount,
exhaustedLabel,
resetTimeRelative,
showResetWhenExhausted,
Expand All @@ -293,6 +297,7 @@ function MetricRow({
}: {
title: string;
snap: RateWindowSnapshot;
amount?: WindowAmountBridge | null;
exhaustedLabel: string;
resetTimeRelative: boolean;
showResetWhenExhausted: boolean;
Expand Down Expand Up @@ -338,6 +343,13 @@ function MetricRow({
<span className="menu-metric__reset">{resetText}</span>
)}
</div>
{amount && (
<div className="menu-metric__amount">
{amount.formattedLimit
? `${amount.formattedUsed} ${t("PanelAmountOf")} ${amount.formattedLimit}`
: amount.formattedUsed}
</div>
)}
{snap.isExhausted && (
<div className="menu-metric__exhausted">{exhaustedLabel}</div>
)}
Expand Down Expand Up @@ -496,6 +508,7 @@ export default function MenuCard({
id: `extra-${extra.id}`,
label: extra.title,
snap: extra.window,
amount: extra.amount,
});
}
for (const inactive of provider.inactiveRateWindows ?? []) {
Expand Down Expand Up @@ -635,6 +648,7 @@ export default function MenuCard({
key={m.id}
title={m.label}
snap={m.snap}
amount={m.amount}
exhaustedLabel={t("DetailWindowExhausted")}
resetTimeRelative={resetTimeRelative}
showResetWhenExhausted={showResetWhenExhausted}
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/i18n/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ export const ALL_LOCALE_KEYS = [
"PanelUsedSuffix",
"PanelLeftSuffix",
"PanelOnPaceBudget",
"PanelAmountOf",
"PanelNow",
"PanelOneHour",
"PanelFiveHours",
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop-tauri/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -4854,6 +4854,17 @@ html:has(.menu-surface--tray) {
color: var(--provider-status-error);
}

/* Money behind a lane that is natively denominated in currency. */
.menu-metric__amount {
font-size: 11px;
font-variant-numeric: tabular-nums;
color: var(--text-secondary);
}

.menu-surface--tray .menu-metric__amount {
font-size: 10px;
}

.menu-metric--inactive {
gap: 2px;
padding: 6px 8px;
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop-tauri/src/types/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,18 @@ export interface RateWindowSnapshot {
reserveEtaSeconds?: number | null;
}

/**
* Money behind a single metered lane. Distinct from CostSnapshotBridge, which
* is one figure for the whole provider and cannot describe individual windows.
*/
export interface WindowAmountBridge {
used: number;
limit: number | null;
currencyCode: string;
formattedUsed: string;
formattedLimit: string | null;
}

export interface CostSnapshotBridge {
used: number;
limit: number | null;
Expand Down Expand Up @@ -434,6 +446,7 @@ export interface ProviderUsageSnapshot {
id: string;
title: string;
window: RateWindowSnapshot;
amount?: WindowAmountBridge | null;
}>;
inactiveRateWindows?: Array<{
id: string;
Expand Down Expand Up @@ -1006,6 +1019,7 @@ export interface ProviderDetail {
id: string;
title: string;
window: RateWindowSnapshot;
amount?: WindowAmountBridge | null;
}>;

cost: CostSnapshotBridge | null;
Expand Down
Loading
Loading