From b6b1599b8ccd99f8aad539230c99c16e61c4cf66 Mon Sep 17 00:00:00 2001 From: lbcheng Date: Fri, 8 May 2026 19:07:48 +0800 Subject: [PATCH] fix(tui): preserve high-water mark in sidebar, add copy_cost_from helper Sidebar cost total now uses displayed_session_cost_for_currency so the high-water mark protects against cost reversal during API reconciliation (#244). The session+agents breakdown is only shown when it matches the displayed total; otherwise a single total line is rendered. Add SessionMetadata::copy_cost_from to eliminate the last remaining manual four-field cost copy (fork-session path). Co-Authored-By: Claude Opus 4.7 --- crates/tui/src/main.rs | 5 +---- crates/tui/src/session_manager.rs | 10 ++++++++++ crates/tui/src/tui/sidebar.rs | 17 +++++++++++++---- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index c5b2582c..18f784c0 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -2797,10 +2797,7 @@ fn fork_session(session_id: Option, last: bool, workspace: &Path) -> Res saved.metadata.total_tokens, system_prompt.as_ref(), ); - forked.metadata.session_cost_usd = saved.metadata.session_cost_usd; - forked.metadata.session_cost_cny = saved.metadata.session_cost_cny; - forked.metadata.subagent_cost_usd = saved.metadata.subagent_cost_usd; - forked.metadata.subagent_cost_cny = saved.metadata.subagent_cost_cny; + forked.metadata.copy_cost_from(&saved.metadata); manager.save_session(&forked)?; let source_title = saved.metadata.title.trim(); diff --git a/crates/tui/src/session_manager.rs b/crates/tui/src/session_manager.rs index 0b76f80a..0149cfd2 100644 --- a/crates/tui/src/session_manager.rs +++ b/crates/tui/src/session_manager.rs @@ -110,6 +110,16 @@ pub struct SessionMetadata { pub subagent_cost_cny: f64, } +impl SessionMetadata { + /// Copy cost fields from another metadata (used when forking a session). + pub fn copy_cost_from(&mut self, other: &SessionMetadata) { + self.session_cost_usd = other.session_cost_usd; + self.session_cost_cny = other.session_cost_cny; + self.subagent_cost_usd = other.subagent_cost_usd; + self.subagent_cost_cny = other.subagent_cost_cny; + } +} + /// A saved session containing full conversation history #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SavedSession { diff --git a/crates/tui/src/tui/sidebar.rs b/crates/tui/src/tui/sidebar.rs index 0fd68124..f09e359e 100644 --- a/crates/tui/src/tui/sidebar.rs +++ b/crates/tui/src/tui/sidebar.rs @@ -671,16 +671,25 @@ fn render_context_panel(f: &mut Frame, area: Rect, app: &App) { ))); // ── Session cost ───────────────────────────────────────────── + let displayed_total = app.displayed_session_cost_for_currency(app.cost_currency); let session_cost = app.session_cost_for_currency(app.cost_currency); let agent_cost = app.subagent_cost_for_currency(app.cost_currency); - let total_cost = session_cost + agent_cost; - lines.push(Line::from(Span::styled( + let real_total = session_cost + agent_cost; + // Only show the additive breakdown when it matches the displayed + // total; when the high-water mark is in effect (post-reconciliation), + // the breakdown would not sum to the displayed value (#244). + let cost_line = if (displayed_total - real_total).abs() < 1e-9 { format!( "cost: {} (session {} + agents {})", - app.format_cost_amount(total_cost), + app.format_cost_amount(displayed_total), app.format_cost_amount(session_cost), app.format_cost_amount(agent_cost) - ), + ) + } else { + format!("cost: {}", app.format_cost_amount(displayed_total)) + }; + lines.push(Line::from(Span::styled( + cost_line, Style::default().fg(palette::TEXT_MUTED), )));