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
293 changes: 186 additions & 107 deletions apps/desktop-tauri/src-tauri/src/commands/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,123 +183,144 @@ pub(crate) fn build_provider_chart_data(
build_provider_chart_data_with_cancel(provider_id, account_email, None, None)
}

/// Builds the chart snapshot on the right side of the local scans. Claude's
/// cost, token and local-usage series come from its snapshot, so it is built
/// first and handed to `local_scan`. Every other snapshot is built after: the
/// Codex quota-window history reads the cost cache that the scan writes, and
/// building it first left a cold start with no current-window totals.
fn snapshot_around_local_scan<S, L>(
snapshot_feeds_scan: bool,
build_snapshot: impl FnOnce() -> Option<S>,
local_scan: impl FnOnce(Option<&S>) -> L,
) -> (Option<S>, L) {
if snapshot_feeds_scan {
let snapshot = build_snapshot();
let local = local_scan(snapshot.as_ref());
(snapshot, local)
} else {
let local = local_scan(None);
(build_snapshot(), local)
}
}

fn build_provider_chart_data_with_cancel(
provider_id: String,
account_email: Option<String>,
cancel: Option<Arc<AtomicBool>>,
weekly_window: Option<RateWindowSnapshot>,
) -> ProviderChartData {
let live_window = weekly_window.as_ref().map(rate_window_from_snapshot);
let provider_snapshot = codexbar::providers::chart::build_chart_snapshot(
&provider_id,
account_email.as_deref(),
live_window.as_ref(),
cancel.as_deref(),
);
let (cost_history, tokens_history, tokens_incomplete, local_usage) = if let Some(snapshot) =
provider_snapshot
.as_ref()
.filter(|_| provider_id == "claude")
{
let cost_history = snapshot
.daily_cost
.iter()
.cloned()
.map(|(date, value)| {
let incomplete_request_count = snapshot
.daily_incomplete
let build_snapshot = || {
codexbar::providers::chart::build_chart_snapshot(
&provider_id,
account_email.as_deref(),
live_window.as_ref(),
cancel.as_deref(),
)
};
let (provider_snapshot, (cost_history, tokens_history, tokens_incomplete, local_usage)) =
snapshot_around_local_scan(provider_id == "claude", build_snapshot, |claude_snapshot| {
if let Some(snapshot) = claude_snapshot {
let cost_history = snapshot
.daily_cost
.iter()
.cloned()
.map(|(date, value)| {
let incomplete_request_count = snapshot
.daily_incomplete
.iter()
.find(|(day, _)| *day == date)
.map(|(_, count)| *count);
DailyCostPoint {
date,
value,
incomplete_request_count,
}
})
.collect();
let tokens_history = snapshot
.daily_tokens
.iter()
.find(|(day, _)| *day == date)
.map(|(_, count)| *count);
DailyCostPoint {
date,
value,
incomplete_request_count,
.cloned()
.map(|(date, tokens)| DailyTokenPoint { date, tokens })
.collect();
let period = current_reporting_period();
let local_usage = snapshot.local_summary.as_ref().and_then(|summary| {
let period_summary =
period_summary_for(&provider_id, period, summary, cancel.as_deref());
local_usage_summary_from_cost_summary(
&provider_id,
summary,
period,
period_summary.as_ref(),
snapshot.today.as_ref(),
)
});
store_local_usage_summary(&provider_id, local_usage.clone());
(
cost_history,
tokens_history,
snapshot.tokens_incomplete,
local_usage,
)
} else if provider_id == "muse" {
if cancel
.as_ref()
.is_some_and(|flag| flag.load(Ordering::Relaxed))
{
(Vec::new(), Vec::new(), true, None)
} else {
let report = muse_local_usage::scan(30, cancel.as_deref());
let tokens_history = report
.daily
.iter()
.map(|day| DailyTokenPoint {
date: day.day.clone(),
tokens: day.total_tokens,
})
.collect();
let period = current_reporting_period();
let period_report = muse_period_report(period, &report, cancel.as_deref());
let local_usage = muse_local_usage_summary(
&report,
&period_report,
period,
locale::current_language(),
);
(
Vec::new(),
tokens_history,
!report.is_complete(),
local_usage,
)
}
})
.collect();
let tokens_history = snapshot
.daily_tokens
.iter()
.cloned()
.map(|(date, tokens)| DailyTokenPoint { date, tokens })
.collect();
let period = current_reporting_period();
let local_usage = snapshot.local_summary.as_ref().and_then(|summary| {
let period_summary =
period_summary_for(&provider_id, period, summary, cancel.as_deref());
local_usage_summary_from_cost_summary(
&provider_id,
summary,
period,
period_summary.as_ref(),
snapshot.today.as_ref(),
)
} else {
let raw_cost = get_daily_cost_history(&provider_id, 30);
let cost_history: Vec<DailyCostPoint> = raw_cost
.into_iter()
.map(|(date, value)| DailyCostPoint {
date,
value,
incomplete_request_count: None,
})
.collect();

let (raw_tokens, tokens_incomplete) = get_daily_token_history(&provider_id, 30);
let tokens_history: Vec<DailyTokenPoint> = raw_tokens
.into_iter()
.map(|(date, tokens)| DailyTokenPoint { date, tokens })
.collect();
let local_usage = if cancel
.as_deref()
.is_some_and(|flag| flag.load(Ordering::Relaxed))
{
None
} else {
load_local_usage_summary_cached(&provider_id, cancel.as_deref())
};
(cost_history, tokens_history, tokens_incomplete, local_usage)
}
});
store_local_usage_summary(&provider_id, local_usage.clone());
(
cost_history,
tokens_history,
snapshot.tokens_incomplete,
local_usage,
)
} else if provider_id == "muse" {
if cancel
.as_ref()
.is_some_and(|flag| flag.load(Ordering::Relaxed))
{
(Vec::new(), Vec::new(), true, None)
} else {
let report = muse_local_usage::scan(30, cancel.as_deref());
let tokens_history = report
.daily
.iter()
.map(|day| DailyTokenPoint {
date: day.day.clone(),
tokens: day.total_tokens,
})
.collect();
let period = current_reporting_period();
let period_report = muse_period_report(period, &report, cancel.as_deref());
let local_usage = muse_local_usage_summary(
&report,
&period_report,
period,
locale::current_language(),
);
(
Vec::new(),
tokens_history,
!report.is_complete(),
local_usage,
)
}
} else {
let raw_cost = get_daily_cost_history(&provider_id, 30);
let cost_history: Vec<DailyCostPoint> = raw_cost
.into_iter()
.map(|(date, value)| DailyCostPoint {
date,
value,
incomplete_request_count: None,
})
.collect();

let (raw_tokens, tokens_incomplete) = get_daily_token_history(&provider_id, 30);
let tokens_history: Vec<DailyTokenPoint> = raw_tokens
.into_iter()
.map(|(date, tokens)| DailyTokenPoint { date, tokens })
.collect();
let local_usage = if cancel
.as_deref()
.is_some_and(|flag| flag.load(Ordering::Relaxed))
{
None
} else {
load_local_usage_summary_cached(&provider_id, cancel.as_deref())
};
(cost_history, tokens_history, tokens_incomplete, local_usage)
};

let (credits_history, usage_breakdown) =
load_openai_dashboard_chart_data(&provider_id, account_email.as_deref());
Expand Down Expand Up @@ -902,3 +923,61 @@ pub(crate) fn load_openai_dashboard_chart_data_for_test(

#[cfg(test)]
mod tests;

#[cfg(test)]
mod snapshot_order_tests {
use super::snapshot_around_local_scan;
use std::cell::RefCell;

#[test]
fn codex_snapshot_reads_the_cost_cache_the_scan_just_wrote() {
let cost_cache: RefCell<Option<&str>> = RefCell::new(None);
let calls = RefCell::new(Vec::new());
let (snapshot, local) = snapshot_around_local_scan(
false,
|| {
calls.borrow_mut().push("snapshot");
Some(format!(
"current window {}",
cost_cache.borrow().unwrap_or("—")
))
},
|seen: Option<&String>| {
calls.borrow_mut().push("scan");
assert_eq!(seen, None);
*cost_cache.borrow_mut() = Some("$2.65 · 1.8M");
"30d $24.12"
},
);
assert_eq!(*calls.borrow(), vec!["scan", "snapshot"]);
assert_eq!(snapshot.as_deref(), Some("current window $2.65 · 1.8M"));
assert_eq!(local, "30d $24.12");
}

#[test]
fn claude_snapshot_is_built_first_and_feeds_the_scan() {
let calls = RefCell::new(Vec::new());
let (snapshot, local) = snapshot_around_local_scan(
true,
|| {
calls.borrow_mut().push("snapshot");
Some(1.99_f64)
},
|seen: Option<&f64>| {
calls.borrow_mut().push("scan");
seen.copied()
},
);
assert_eq!(*calls.borrow(), vec!["snapshot", "scan"]);
assert_eq!(snapshot, Some(1.99));
assert_eq!(local, Some(1.99));
}

#[test]
fn a_provider_without_a_snapshot_still_gets_its_scan() {
let (snapshot, local) =
snapshot_around_local_scan(false, || None::<u8>, |_seen: Option<&u8>| 30_usize);
assert_eq!(snapshot, None);
assert_eq!(local, 30);
}
}
41 changes: 41 additions & 0 deletions apps/desktop-tauri/src-tauri/src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,47 @@ mod tests {
assert_eq!(SurfaceMode::parse("bogus"), None);
}

/// Reads `const NAME = <integer>;` from a TypeScript source.
fn ts_integer_const(source: &str, name: &str) -> u32 {
let prefix = format!("const {name} = ");
let line = source
.lines()
.find_map(|line| {
line.trim_start()
.strip_prefix("export ")
.unwrap_or(line.trim_start())
.strip_prefix(&prefix)
})
.unwrap_or_else(|| panic!("{name} not found"));
line.trim_end_matches(';')
.trim()
.parse()
.unwrap_or_else(|_| panic!("{name} is not an integer: {line}"))
}

#[test]
fn tray_panel_width_matches_the_frontend_layout_base_width() {
let layout = include_str!("../../src/hooks/useTrayPanelLayout.ts");
let width = SurfaceMode::TrayPanel.window_properties().width;
assert_eq!(
f64::from(ts_integer_const(layout, "TRAY_BASE_WIDTH")),
width
);
}

#[test]
fn tray_scale_bounds_match_the_frontend_scale_bounds() {
let scale = include_str!("../../src/lib/trayScale.ts");
assert_eq!(
ts_integer_const(scale, "TRAY_SCALE_MIN"),
u32::from(codexbar::settings::TRAY_SCALE_PERCENT_MIN)
);
assert_eq!(
ts_integer_const(scale, "TRAY_SCALE_MAX"),
u32::from(codexbar::settings::TRAY_SCALE_PERCENT_MAX)
);
}

#[test]
fn tray_panel_properties() {
let props = SurfaceMode::TrayPanel.window_properties();
Expand Down
9 changes: 5 additions & 4 deletions apps/desktop-tauri/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,14 @@ function SettingsLayout({ state }: { state: BootstrapState }) {
}

function DetachedSettingsApp({ state }: { state: BootstrapState }) {
const [tab, setTab] = useState(initialSettingsTab);
const [request, setRequest] = useState(() => ({ tab: initialSettingsTab(), id: 0 }));

useEffect(() => {
// Listen for tab-change events from Rust (when the window is re-focused
// with a different tab request).
// with a different tab request). Each event gets a new id so repeating
// the same `providers:<id>` deep link selects that provider again.
const unlisten = listen<string>("settings-change-tab", (event) => {
setTab(event.payload);
setRequest((current) => ({ tab: event.payload, id: current.id + 1 }));
});
return () => {
unlisten.then((fn) => fn());
Expand All @@ -236,7 +237,7 @@ function DetachedSettingsApp({ state }: { state: BootstrapState }) {
return (
<Suspense fallback={<SurfaceFallback />}>
<main className="settings-surface settings-surface--full">
<Settings state={state} initialTab={tab} />
<Settings state={state} initialTab={request.tab} tabRequestId={request.id} />
</main>
</Suspense>
);
Expand Down
Loading