Skip to content

Commit 1f3a8b1

Browse files
committed
fix(tui): live status supersedes the unread marker
agent-of-empires#2088 review: a Running session that also carried an unread marker kept showing the solid unread dot — the color was gated to resting states (Idle/Unknown) but the icon + bold weren't, so the dot replaced the running spinner. Gate the whole unread treatment (color, dot, bold) on resting status so any live status (Running/Waiting/Starting/...) keeps its own color and spinner. Adds a render regression test. 🤖 Generated with Claude Code
1 parent e0dc247 commit 1f3a8b1

2 files changed

Lines changed: 65 additions & 11 deletions

File tree

src/tui/home/render.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,22 +1013,24 @@ impl HomeView {
10131013
Status::Deleting => ICON_DELETING,
10141014
Status::Creating => spinner_starting(&inst.created_at),
10151015
};
1016-
// Unread overlays the resting (Idle/Unknown)
1017-
// color with `theme.unread`. Auto-unread only
1018-
// ever lands on Idle; a manual flag on a live
1019-
// row keeps the live status color (more urgent
1020-
// than "unread"). Archived/snoozed/urgent/
1021-
// favorite below still override as usual.
1022-
let unread_overlay =
1023-
crate::session::unread_enabled() && inst.is_unread();
1016+
// Unread paints only on resting rows
1017+
// (Idle/Unknown): a live status (Running/Waiting/
1018+
// Starting/...) supersedes it and keeps its own
1019+
// color AND spinner. Auto-unread only ever lands
1020+
// on Idle; a manual flag on a live row defers to
1021+
// the live state. Archived/snoozed/urgent below
1022+
// still override on top.
1023+
let unread_resting = crate::session::unread_enabled()
1024+
&& inst.is_unread()
1025+
&& matches!(inst.status, Status::Idle | Status::Unknown);
10241026
let color = match inst.status {
10251027
Status::Running => theme.running,
10261028
Status::Waiting => theme.waiting,
1027-
Status::Idle if unread_overlay => theme.unread,
1029+
Status::Idle if unread_resting => theme.unread,
10281030
Status::Idle => {
10291031
theme.idle_color_at_age(idle_age, self.idle_decay_window)
10301032
}
1031-
Status::Unknown if unread_overlay => theme.unread,
1033+
Status::Unknown if unread_resting => theme.unread,
10321034
Status::Unknown => theme.waiting,
10331035
Status::Stopped => theme.dimmed,
10341036
Status::Error => theme.error,
@@ -1037,7 +1039,7 @@ impl HomeView {
10371039
Status::Creating => theme.accent,
10381040
};
10391041
let mut style = Style::default().fg(color);
1040-
if unread_overlay {
1042+
if unread_resting {
10411043
// Make unread unmistakable: a solid dot glyph
10421044
// plus bold, on top of the `theme.unread`
10431045
// color set above. A plain color swap read as

src/tui/home/tests.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,58 @@ fn preview_visible_rows_equal_output_area_with_info_shown() {
575575
);
576576
}
577577

578+
/// Precedence: unread paints only on resting (Idle/Unknown) rows. A live
579+
/// status supersedes it, keeping its own spinner — so a Running session that
580+
/// also carries an unread marker must NOT show the solid unread dot. See the
581+
/// #2088 review note about jumbled precedence.
582+
#[test]
583+
#[serial]
584+
fn unread_dot_yields_to_a_running_status() {
585+
use crate::tui::styles::load_theme;
586+
use ratatui::backend::TestBackend;
587+
use ratatui::Terminal;
588+
589+
let mut env = create_test_env_with_sessions(1);
590+
let id = env.view.instances()[0].id.clone();
591+
let theme = load_theme("empire");
592+
593+
let render = |env: &mut TestEnv| -> String {
594+
let backend = TestBackend::new(120, 40);
595+
let mut terminal = Terminal::new(backend).unwrap();
596+
terminal
597+
.draw(|f| env.view.render(f, f.area(), &theme, None, None, None))
598+
.unwrap();
599+
let buf = terminal.backend().buffer();
600+
let mut out = String::new();
601+
for y in 0..buf.area.height {
602+
for x in 0..buf.area.width {
603+
out.push_str(buf[(x, y)].symbol());
604+
}
605+
}
606+
out
607+
};
608+
609+
// Idle + unread: the row shows the solid unread dot.
610+
env.view.mutate_instance(&id, |inst| {
611+
inst.status = crate::session::Status::Idle;
612+
inst.mark_unread_manual();
613+
});
614+
env.view.flat_items = env.view.build_flat_items();
615+
assert!(
616+
render(&mut env).contains('●'),
617+
"an idle unread row should paint the unread dot"
618+
);
619+
620+
// Running + still unread: the live status wins; no unread dot.
621+
env.view
622+
.mutate_instance(&id, |inst| inst.status = crate::session::Status::Running);
623+
env.view.flat_items = env.view.build_flat_items();
624+
assert!(
625+
!render(&mut env).contains('●'),
626+
"a running row must keep its spinner, not the unread dot"
627+
);
628+
}
629+
578630
#[test]
579631
#[serial]
580632
fn test_q_returns_quit_action() {

0 commit comments

Comments
 (0)