From 025ddcbfdea41cf7bca7ee1777395f58713d3530 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:04:06 +0700 Subject: [PATCH] Port upstream 0.62.0: detail-section visibility via detailSection IDs --- .../src-tauri/src/commands/tests.rs | 54 +++++++++++++++++++ .../src-tauri/src/commands/usage_items.rs | 50 +++++++++++++++++ .../src/components/MenuCardDetails.tsx | 19 +++---- .../src/lib/usageItemVisibility.ts | 14 +++++ rust/src/settings.rs | 5 +- 5 files changed, 130 insertions(+), 12 deletions(-) diff --git a/apps/desktop-tauri/src-tauri/src/commands/tests.rs b/apps/desktop-tauri/src-tauri/src/commands/tests.rs index 444e93da0ab..a778318982f 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/tests.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/tests.rs @@ -988,6 +988,60 @@ fn usage_item_descriptors_keep_raw_ids_and_redact_titles() { assert!(!items[2].available); } +#[test] +fn usage_item_descriptors_include_detail_sections() { + let metadata = instantiate_provider(ProviderId::Codex).metadata().clone(); + let result = ProviderFetchResult { + usage: codexbar::core::UsageSnapshot::new(codexbar::core::RateWindow::new(10.0)), + cost: None, + wayfinder_usage: None, + inventory: Vec::new(), + display_details: vec![ + codexbar::core::ProviderDisplayDetail::new("d1", "Rate limits", "5") + .expect("valid detail"), + codexbar::core::ProviderDisplayDetail::new("d2", "Rate limits", "6") + .expect("valid detail"), + codexbar::core::ProviderDisplayDetail::new("d3", "owner@example.com quota", "1") + .expect("valid detail"), + ], + source_label: "OAuth".to_string(), + has_successful_claude_cli_quota: false, + pace_authoritative: true, + account_identity: None, + }; + let snapshot = + ProviderUsageSnapshot::from_fetch_result(ProviderId::Codex, &metadata, &result, None); + + let mut settings = Settings { + hide_personal_info: true, + ..Settings::default() + }; + settings.set_hidden_usage_item_ids( + ProviderId::Codex, + vec!["detailSection:owner@example.com quota".to_string()], + ); + + let items = super::usage_item_descriptors(Some(&snapshot), &settings, ProviderId::Codex); + + // First three items are the distinct detail sections, redacted titles. + assert_eq!(items[0].id, "detailSection:Rate limits"); + assert_eq!(items[0].title, "Rate limits"); + assert!(items[0].available); + // Duplicate title is deduped to one descriptor; the email title is redacted. + assert!( + !items[1..] + .iter() + .any(|item| item.title.contains("owner@example.com")) + ); + // The hidden detail section is a placeholder, redacted in the stored ID. + let hidden = items + .iter() + .find(|item| item.id.starts_with("detailSection:") && !item.available) + .expect("hidden detail placeholder"); + assert!(hidden.id.contains("owner@example.com quota")); + assert_eq!(hidden.title, "Hidden quota"); +} + #[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 fd34eba4921..17bf96a776a 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/usage_items.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/usage_items.rs @@ -81,6 +81,53 @@ pub(crate) fn usage_item_descriptors( let mut seen = std::collections::HashSet::new(); let mut items = Vec::new(); + // Detail sections (upstream 0.62.0 #3638): one descriptor per distinct + // display-detail title, hidden independently of the metric rows. Detail + // IDs round-trip as-is (no metric prefix), so they bypass the `push` + // helper below. + if let Some(snapshot) = snapshot { + let mut detail_titles = std::collections::HashSet::new(); + for detail in &snapshot.display_details { + if detail_titles.insert(detail.title.clone()) { + let redacted = PersonalInfoRedactor::redact_emails_in_text( + Some(&detail.title), + settings.hide_personal_info, + ) + .unwrap_or_default(); + let id = format!( + "{}{}", + codexbar::settings::USAGE_ITEM_DETAIL_SECTION_PREFIX, + redacted + ); + if seen.insert(id.clone()) { + items.push(ProviderUsageItemSnapshot { + id, + title: redacted, + available: true, + }); + } + } + } + } + for id in &hidden { + if !id.starts_with(codexbar::settings::USAGE_ITEM_DETAIL_SECTION_PREFIX) { + continue; + } + // Placeholder for a hidden detail section the provider no longer + // reports: the title is recovered from the stored ID. + if seen.insert(id.clone()) { + items.push(ProviderUsageItemSnapshot { + id: id.clone(), + title: redacted_usage_item_title( + id.strip_prefix(codexbar::settings::USAGE_ITEM_DETAIL_SECTION_PREFIX) + .unwrap_or(id.as_str()), + settings, + ), + available: false, + }); + } + } + let mut push = |raw_id: &str, title: &str, available: bool| { let id = usage_item_id(raw_id); if seen.insert(id.clone()) { @@ -126,6 +173,9 @@ pub(crate) fn usage_item_descriptors( } for id in hidden { + if id.starts_with(codexbar::settings::USAGE_ITEM_DETAIL_SECTION_PREFIX) { + continue; + } let raw_id = id .strip_prefix(codexbar::settings::USAGE_ITEM_METRIC_PREFIX) .unwrap_or(id.as_str()); diff --git a/apps/desktop-tauri/src/components/MenuCardDetails.tsx b/apps/desktop-tauri/src/components/MenuCardDetails.tsx index 31634bd910e..ce5ec3f93d1 100644 --- a/apps/desktop-tauri/src/components/MenuCardDetails.tsx +++ b/apps/desktop-tauri/src/components/MenuCardDetails.tsx @@ -24,6 +24,7 @@ import { SimpleBarChart, StackedBarChart } from "./MiniBarChart"; import { InventoryItemRow } from "./InventoryRows"; import { QuotaWindowHistory } from "./QuotaWindowHistory"; import { getPaceBudget, type PaceBudget } from "../lib/paceBudget"; +import { isDetailSectionVisible } from "../lib/usageItemVisibility"; import PaceDetailsChart from "./PaceDetailsChart"; /** Format a reserve description from raw pace data at render time. */ @@ -597,17 +598,13 @@ export default function MenuCardDetails({ )} {!provider.error && hasDisplayDetails && !compactOverview && (
- {provider.displayDetails?.map((detail, index) => ( - - ))} -
- )} - - {!provider.error && hasDisplayDetails && ( -
- {provider.displayDetails?.map((detail, index) => ( - - ))} + {provider.displayDetails + ?.filter((detail) => + isDetailSectionVisible(provider.hiddenUsageItemIds, detail.title), + ) + .map((detail, index) => ( + + ))}
)} diff --git a/apps/desktop-tauri/src/lib/usageItemVisibility.ts b/apps/desktop-tauri/src/lib/usageItemVisibility.ts index 21e86589dd4..b4d9c9344f6 100644 --- a/apps/desktop-tauri/src/lib/usageItemVisibility.ts +++ b/apps/desktop-tauri/src/lib/usageItemVisibility.ts @@ -1,13 +1,27 @@ export const USAGE_ITEM_METRIC_PREFIX = "metric:"; +export const USAGE_ITEM_DETAIL_SECTION_PREFIX = "detailSection:"; /** Stable bridge ID for a metric or extra usage row. */ export function usageItemId(rawMetricId: string): string { return `${USAGE_ITEM_METRIC_PREFIX}${rawMetricId}`; } +/** Stable bridge ID for a provider detail section (upstream 0.62.0 #3638). */ +export function detailSectionItemId(rawSectionTitle: string): string { + return `${USAGE_ITEM_DETAIL_SECTION_PREFIX}${rawSectionTitle}`; +} + export function isUsageItemVisible( hiddenUsageItemIds: readonly string[] | undefined, rawMetricId: string, ): boolean { return !hiddenUsageItemIds?.includes(usageItemId(rawMetricId)); } + +/** A detail section is visible unless its title-based ID is hidden. */ +export function isDetailSectionVisible( + hiddenUsageItemIds: readonly string[] | undefined, + rawSectionTitle: string, +): boolean { + return !hiddenUsageItemIds?.includes(detailSectionItemId(rawSectionTitle)); +} diff --git a/rust/src/settings.rs b/rust/src/settings.rs index 9863f194ceb..f35f6d06835 100755 --- a/rust/src/settings.rs +++ b/rust/src/settings.rs @@ -19,6 +19,8 @@ use crate::core::ProviderId; /// Stable namespace used by the desktop bridge for quota metric rows. pub const USAGE_ITEM_METRIC_PREFIX: &str = "metric:"; +/// Stable namespace for provider detail sections (upstream 0.62.0 #3638). +pub const USAGE_ITEM_DETAIL_SECTION_PREFIX: &str = "detailSection:"; pub const CODEX_SPARK_USAGE_ITEM_IDS: [&str; 2] = [ "metric:extra-codex-spark", "metric:extra-codex-spark-weekly", @@ -509,7 +511,8 @@ fn normalize_hidden_usage_item_ids(ids: Vec) -> Vec { !id.is_empty() && id.len() <= 128 && !id.chars().any(char::is_control) - && id.starts_with(USAGE_ITEM_METRIC_PREFIX) + && (id.starts_with(USAGE_ITEM_METRIC_PREFIX) + || id.starts_with(USAGE_ITEM_DETAIL_SECTION_PREFIX)) }) .filter(|id| seen.insert(id.clone())) .collect::>();