diff --git a/apps/desktop-tauri/src-tauri/src/commands/tests.rs b/apps/desktop-tauri/src-tauri/src/commands/tests.rs index 587da2344e..5137c96a4b 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/tests.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/tests.rs @@ -1437,6 +1437,73 @@ fn usage_item_descriptors_include_detail_sections() { assert_eq!(hidden.title, "quota"); } +#[test] +fn usage_item_descriptors_skip_card_hidden_windows() { + let metadata = instantiate_provider(ProviderId::Codex).metadata().clone(); + let result = ProviderFetchResult { + usage: codexbar::core::UsageSnapshot::new( + codexbar::core::RateWindow::informational("$1.00").hidden_on_card(), + ) + .with_secondary(codexbar::core::RateWindow::new(20.0)), + cost: None, + wayfinder_usage: None, + inventory: Vec::new(), + reset_credits: None, + display_details: vec![ + codexbar::core::ProviderDisplayDetail::new("spend", "Spend", "$1.00") + .expect("valid detail"), + ], + source_label: "api".to_string(), + has_successful_claude_cli_quota: false, + pace_authoritative: true, + account_identity: None, + open_ai_api_usage: None, + last_good_owner: None, + }; + let mut snapshot = + ProviderUsageSnapshot::from_fetch_result(ProviderId::Codex, &metadata, &result, None, None); + let mut hidden_extra = snapshot.primary.clone(); + hidden_extra.hidden_on_card = true; + snapshot.extra_rate_windows = vec![NamedRateWindowSnapshot { + id: "hidden".to_string(), + title: "Hidden".to_string(), + window: hidden_extra, + fallback_lane: false, + icon_fallback: None, + }]; + + let items = + super::usage_item_descriptors(Some(&snapshot), &Settings::default(), ProviderId::Codex); + let ids: Vec<&str> = items.iter().map(|item| item.id.as_str()).collect(); + + assert_eq!(ids, ["detailSection:Spend", "metric:secondary"]); + + // A saved hidden ID for a card-hidden window gets no placeholder, while + // one for a window the snapshot no longer reports still does. + let mut settings = Settings::default(); + settings.set_hidden_usage_item_ids( + ProviderId::Codex, + vec![ + "metric:primary".to_string(), + "metric:extra-hidden".to_string(), + "metric:tertiary".to_string(), + ], + ); + let items = super::usage_item_descriptors(Some(&snapshot), &settings, ProviderId::Codex); + let ids: Vec<(&str, bool)> = items + .iter() + .map(|item| (item.id.as_str(), item.available)) + .collect(); + assert_eq!( + ids, + [ + ("detailSection:Spend", true), + ("metric:secondary", true), + ("metric:tertiary", false), + ] + ); +} + #[test] fn pace_stage_serializes_to_snake_case_string() { use codexbar::core::PaceStage; diff --git a/apps/desktop-tauri/src-tauri/src/commands/usage_items.rs b/apps/desktop-tauri/src-tauri/src/commands/usage_items.rs index e60e8c16ea..a037e5d733 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/usage_items.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/usage_items.rs @@ -133,6 +133,28 @@ pub(crate) fn usage_item_descriptors( } } + // A card-hidden window is never drawn as a row, so neither a live toggle + // nor a saved-ID placeholder for it would control anything. Marking it + // seen suppresses both; windows absent from the snapshot keep placeholders. + if let Some(snapshot) = snapshot { + let windows = [ + ("primary", Some(&snapshot.primary)), + ("secondary", snapshot.secondary.as_ref()), + ("model-specific", snapshot.model_specific.as_ref()), + ("tertiary", snapshot.tertiary.as_ref()), + ]; + for (raw_id, window) in windows { + if window.is_some_and(|w| w.hidden_on_card) { + seen.insert(usage_item_id(raw_id)); + } + } + for extra in &snapshot.extra_rate_windows { + if extra.window.hidden_on_card { + seen.insert(usage_item_id(&format!("extra-{}", extra.id))); + } + } + } + let mut push = |raw_id: &str, title: &str, available: bool| { let id = usage_item_id(raw_id); if seen.insert(id.clone()) { diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.test.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.test.tsx index 85f30d2f2d..4f226a5303 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.test.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.test.tsx @@ -96,6 +96,22 @@ describe("UsageSection", () => { expect(screen.getByText("58% left")).toBeInTheDocument(); }); + it("omits card-hidden windows, as the tray card does", async () => { + const detail = provider(); + detail.session = { ...rateWindow(0), hiddenOnCard: true }; + detail.primaryLabel = "Spend"; + + render( + + key} /> + , + ); + + expect(await screen.findByText("Additional Budget")).toBeInTheDocument(); + expect(screen.queryByText("Spend")).not.toBeInTheDocument(); + expect(screen.queryByText("100% left")).not.toBeInTheDocument(); + }); + it.each([false, true])( "renders detail-backed amounts as a detail line, not reset text (resetsAt: %s)", async (hasReset) => { diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.tsx index 68ca40cf33..399910ba20 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/UsageSection.tsx @@ -88,14 +88,17 @@ export function UsageSection({ provider, resetTimeRelative, display, accentColor }); } + // Card-hidden windows only feed the tray icon and float bar, as on MenuCard. + const shownBars = bars.filter((bar) => !bar.rate.hiddenOnCard); + // Cached snapshots outlive the pool reset; re-check blocks on the clock. const monthlyLimitBlockNow = useMonthlyLimitBlockNow( - bars.map((bar) => bar.rate.monthlyLimitBlock), + shownBars.map((bar) => bar.rate.monthlyLimitBlock), ); const inventory = provider.inventory ?? []; const displayDetails = provider.displayDetails ?? []; const displayDetailGroups = groupProviderDisplayDetails(displayDetails); - if (bars.length === 0 && inventory.length === 0 && displayDetails.length === 0) { + if (shownBars.length === 0 && inventory.length === 0 && displayDetails.length === 0) { return null; } @@ -105,7 +108,7 @@ export function UsageSection({ provider, resetTimeRelative, display, accentColor style={accentColor ? ({ "--provider-accent": accentColor } as CSSProperties) : undefined} >

{t("ProviderUsage")}

- {bars.map((b) => ( + {shownBars.map((b) => (