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
54 changes: 54 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
50 changes: 50 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/usage_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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());
Expand Down
19 changes: 8 additions & 11 deletions apps/desktop-tauri/src/components/MenuCardDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -597,17 +598,13 @@ export default function MenuCardDetails({
)}
{!provider.error && hasDisplayDetails && !compactOverview && (
<section className="menu-card__group menu-card__provider-details">
{provider.displayDetails?.map((detail, index) => (
<DisplayDetailRow key={`${detail.id}-${index}`} detail={detail} />
))}
</section>
)}

{!provider.error && hasDisplayDetails && (
<section className="menu-card__group menu-card__provider-details">
{provider.displayDetails?.map((detail, index) => (
<DisplayDetailRow key={`${detail.id}-${index}`} detail={detail} />
))}
{provider.displayDetails
?.filter((detail) =>
isDetailSectionVisible(provider.hiddenUsageItemIds, detail.title),
)
.map((detail, index) => (
<DisplayDetailRow key={`${detail.id}-${index}`} detail={detail} />
))}
</section>
)}

Expand Down
14 changes: 14 additions & 0 deletions apps/desktop-tauri/src/lib/usageItemVisibility.ts
Original file line number Diff line number Diff line change
@@ -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));
}
5 changes: 4 additions & 1 deletion rust/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -509,7 +511,8 @@ fn normalize_hidden_usage_item_ids(ids: Vec<String>) -> Vec<String> {
!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::<Vec<_>>();
Expand Down