From 76a5dd5b26de6e9073ab3876131157404401488b Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Sun, 26 Jul 2026 09:25:37 +0200 Subject: [PATCH] Name session checkpoints by their session title Co-Authored-By: Claude Opus 5 --- src/app/generation.rs | 3 +-- src/app/view/stats.rs | 18 +++++++++++++++++- src/metrics.rs | 9 +++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/app/generation.rs b/src/app/generation.rs index a87e76d..5d715fe 100644 --- a/src/app/generation.rs +++ b/src/app/generation.rs @@ -552,8 +552,7 @@ impl App { false } - #[cfg(target_os = "macos")] - fn session_title(&self, session_id: i32) -> Option { + pub(super) fn session_title(&self, session_id: i32) -> Option { self.projects .iter() .flat_map(|project| &project.sessions) diff --git a/src/app/view/stats.rs b/src/app/view/stats.rs index acde7e0..e9248ae 100644 --- a/src/app/view/stats.rs +++ b/src/app/view/stats.rs @@ -417,9 +417,16 @@ impl App { let mut rows = column![].spacing(7); for entry in &usage.entries { let path = entry.path.clone(); + let label = entry + .session + .and_then(|id| self.session_title(id)) + .map_or_else( + || entry.name.clone(), + |title| format!("{}: {}", entry.name, shorten(&title)), + ); rows = rows.push( row![ - text(entry.name.clone()).size(12), + text(label).size(12), Space::with_width(Length::Fill), text(format!( "{} · {} old", @@ -499,6 +506,15 @@ const AGE_COLORS: [Color; 5] = [ Color::from_rgb(0.45, 0.45, 0.48), ]; +/// Keeps a long session title from crowding out the size and the action. +fn shorten(title: &str) -> String { + const LIMIT: usize = 44; + if title.chars().count() <= LIMIT { + return title.to_owned(); + } + title.chars().take(LIMIT - 1).chain(['…']).collect() +} + fn portion(bytes: u64, capacity: u64) -> u16 { ((bytes.saturating_mul(1000) / capacity.max(1)) as u16).max(1) } diff --git a/src/metrics.rs b/src/metrics.rs index c94a8a4..7fe9555 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -694,6 +694,8 @@ const CACHE_ENTRY_ROWS: usize = 12; pub(crate) struct KvCacheEntry { pub(crate) path: PathBuf, pub(crate) name: String, + /// Set for session checkpoints, so the view can name the conversation. + pub(crate) session: Option, pub(crate) bytes: u64, pub(crate) age_seconds: u64, } @@ -762,6 +764,10 @@ pub(crate) fn kv_cache_report(root: &Path, budget_bytes: u64) -> KvCacheReport { } report.entries.push(KvCacheEntry { name: entry_name(&path, transient), + session: (!transient) + .then(|| path.file_stem().and_then(|value| value.to_str())) + .flatten() + .and_then(|stem| stem.parse().ok()), bytes: bytes.saturating_add( fs::metadata(path.with_extension("meta")).map_or(0, |index| index.len()), ), @@ -924,6 +930,9 @@ mod tests { assert_eq!(report.entries.len(), 2); assert_eq!(report.entries[0].bytes, 140); assert_eq!(report.entries[1].name, "Session 7"); + // The session id survives on the row, so the view can add its title. + assert_eq!(report.entries[1].session, Some(7)); + assert_eq!(report.entries[0].session, None); fs::remove_dir_all(root).unwrap(); }