From 40ea89f6cebd6a59d577937e0deb047f7db5708c Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Wed, 6 May 2026 18:08:13 -0500 Subject: [PATCH] fix(tui): show tiny footer costs (#913) --- crates/tui/src/tui/ui.rs | 33 ++++++++++++++++++--------------- crates/tui/src/tui/ui/tests.rs | 14 ++++++++++++-- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 6caa3041..dd6f7758 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -6320,12 +6320,8 @@ fn render_footer_from( } else { Vec::new() }; - let displayed_cost = app.displayed_session_cost_for_currency(app.cost_currency); - let cost = if has(S::Cost) && displayed_cost > 0.001 { - vec![Span::styled( - app.format_cost_amount(displayed_cost), - Style::default().fg(palette::TEXT_MUTED), - )] + let cost = if has(S::Cost) { + footer_cost_spans(app) } else { Vec::new() }; @@ -6401,6 +6397,21 @@ fn footer_context_percent_spans(app: &App) -> Vec> { )] } +fn footer_cost_spans(app: &App) -> Vec> { + let displayed_cost = app.displayed_session_cost_for_currency(app.cost_currency); + if !should_show_footer_cost(displayed_cost) { + return Vec::new(); + } + vec![Span::styled( + app.format_cost_amount(displayed_cost), + Style::default().fg(palette::TEXT_MUTED), + )] +} + +fn should_show_footer_cost(displayed_cost: f64) -> bool { + displayed_cost.is_finite() && displayed_cost > 0.0 +} + /// Test-only helper retained as a parity reference for `FooterWidget`'s /// auxiliary-span composition. Production rendering is performed by the /// widget itself; the existing footer parity tests still exercise this @@ -6416,15 +6427,7 @@ fn footer_auxiliary_spans(app: &App, max_width: usize) -> Vec> { crate::tui::widgets::footer_agents_chip(running_agent_count(app), app.ui_locale); let replay_spans = footer_reasoning_replay_spans(app); let cache_spans = footer_cache_spans(app); - let displayed_cost = app.displayed_session_cost_for_currency(app.cost_currency); - let cost_spans = if displayed_cost > 0.001 { - vec![Span::styled( - app.format_cost_amount(displayed_cost), - Style::default().fg(palette::TEXT_MUTED), - )] - } else { - Vec::new() - }; + let cost_spans = footer_cost_spans(app); let parts: Vec<&Vec>> = [ &coherence_spans, diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index 00954ce7..c86658e2 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -1129,6 +1129,15 @@ fn footer_auxiliary_spans_show_cache_and_cost_when_roomy() { ); } +#[test] +fn footer_auxiliary_spans_show_tiny_positive_cost_when_roomy() { + let mut app = create_test_app(); + app.session.session_cost = 0.00005; + + let roomy = spans_text(&footer_auxiliary_spans(&app, 32)); + assert!(roomy.contains("<$0.0001")); +} + #[test] fn footer_auxiliary_spans_use_configured_cost_currency() { let mut app = create_test_app(); @@ -3207,13 +3216,14 @@ fn render_footer_from_with_default_items_renders_mode_and_model() { // Default footer composition should show the mode chip and model // identifier — whatever the configured default model is. let mut app = create_test_app(); - app.session.session_cost = 0.42; + app.session.session_cost = 0.00005; let items = crate::config::StatusItem::default_footer(); let props = render_footer_from(&app, &items, None); assert_eq!(props.mode_label, "agent"); assert!(!props.model.is_empty(), "footer should show a model name"); - // Cost chip is included whenever cost > 0.001. + // Tiny but real costs should render instead of disappearing as "$0.00". assert!(!props.cost.is_empty()); + assert_eq!(spans_text(&props.cost), "<$0.0001"); } #[test]