fix(tui): bound cached tab activity

This commit is contained in:
Felipe Coury
2026-06-05 15:22:49 -03:00
parent 3b332751b2
commit a535101647
5 changed files with 37 additions and 19 deletions

View File

@@ -2,6 +2,7 @@ use std::time::Duration;
use std::time::Instant;
use crate::tab_status::TabStatus;
use crate::tab_status::sanitize_detail;
use crate::tab_status::set_tab_status;
const MIN_DETAIL_INTERVAL: Duration = Duration::from_millis(/*millis*/ 250);
@@ -79,10 +80,11 @@ impl TabStatusState {
if !self.enabled {
return;
}
let desired = (desired.0, desired.1.as_deref().map(sanitize_detail));
if self.last_status.as_ref() == Some(&desired) {
return;
}
if let Err(err) = set_tab_status(desired.0, desired.1.as_deref()) {
if let Err(err) = set_tab_status(desired.0, desired.1.clone()) {
tracing::debug!(error = %err, "failed to set tab status");
return;
}

View File

@@ -51,3 +51,19 @@ fn throttle_defers_detail_changes_but_not_status_changes() {
None
);
}
#[test]
fn caches_only_bounded_sanitized_detail() {
let mut state = TabStatusState::new();
state.refresh(
(TabStatus::Working, Some("x".repeat(/*n*/ 1_000))),
Instant::now(),
);
assert_eq!(
state.last_status(),
Some((
TabStatus::Working,
Some(format!("{}", "x".repeat(/*n*/ 200))),
))
);
}

View File

@@ -256,21 +256,8 @@ impl ChatWidget {
// Ensure the status indicator is visible while the command runs.
self.bottom_pane.ensure_status_indicator();
let parsed_cmd = self.annotate_skill_reads_in_parsed_cmd(parsed_cmd);
self.running_command_seq = self.running_command_seq.wrapping_add(1);
self.running_commands.insert(
id.clone(),
RunningCommand {
command: command.clone(),
parsed_cmd: parsed_cmd.clone(),
source,
start_order: self.running_command_seq,
},
);
let is_wait_interaction = matches!(source, ExecCommandSource::UnifiedExecInteraction);
let command_display = command.join(" ");
let activity = crate::tab_status::format_parsed_command_for_tab_status(&parsed_cmd)
.unwrap_or_else(|| crate::tab_status::format_command_for_tab_status(&command));
self.bottom_pane.set_current_activity(Some(activity));
let should_suppress_unified_wait = is_wait_interaction
&& self
.last_unified_wait
@@ -285,6 +272,19 @@ impl ChatWidget {
self.suppressed_exec_calls.insert(id);
return;
}
self.running_command_seq = self.running_command_seq.wrapping_add(/*rhs*/ 1);
self.running_commands.insert(
id.clone(),
RunningCommand {
command: command.clone(),
parsed_cmd: parsed_cmd.clone(),
source,
start_order: self.running_command_seq,
},
);
let activity = crate::tab_status::format_parsed_command_for_tab_status(&parsed_cmd)
.unwrap_or_else(|| crate::tab_status::format_command_for_tab_status(&command));
self.bottom_pane.set_current_activity(Some(activity));
if let Some(cell) = self
.transcript
.active_cell

View File

@@ -128,11 +128,11 @@ pub(crate) fn oneline_truncated(value: &str) -> String {
out
}
pub(crate) fn set_tab_status(status: TabStatus, detail: Option<&str>) -> io::Result<()> {
pub(crate) fn set_tab_status(status: TabStatus, detail: Option<String>) -> io::Result<()> {
if !stdout().is_terminal() {
return Ok(());
}
execute!(stdout(), SetTabStatus(status, detail.map(sanitize_detail)))?;
execute!(stdout(), SetTabStatus(status, detail))?;
EMITTED.store(/*val*/ true, Ordering::Relaxed);
Ok(())
}
@@ -146,7 +146,7 @@ pub(crate) fn clear_tab_status() -> io::Result<()> {
Ok(())
}
fn sanitize_detail(detail: &str) -> String {
pub(crate) fn sanitize_detail(detail: &str) -> String {
let mut out = String::with_capacity(detail.len().min(MAX_TAB_STATUS_DETAIL_CHARS * 2 + 1));
let mut chars_written = 0;
let mut pending_space = false;

View File

@@ -19,9 +19,9 @@ fn status_sequences_include_detail_and_clear_stale_fields() {
SetTabStatus(TabStatus::Working, Some("exec cargo build".to_string()))
.write_ansi(&mut working)
.expect("encode tab status");
assert_eq!(
insta::assert_snapshot!(
working,
"\x1b]21337;status=Working;indicator=#ff9500;status-color=#ff9500;detail=exec cargo build\x07"
@"\u{1b}]21337;status=Working;indicator=#ff9500;status-color=#ff9500;detail=exec cargo build\u{7}"
);
let mut idle = String::new();