Skip to content

Commit a554ffb

Browse files
committed
fix(cli): keep TUI open until explicit quit
1 parent 431c4c3 commit a554ffb

3 files changed

Lines changed: 72 additions & 13 deletions

File tree

langcodec-cli/src/tui/render.rs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,38 @@ fn focused_block(title: &str, focused: bool) -> Block<'static> {
4242
fn key_hints(completed: bool) -> Line<'static> {
4343
let base = "Up/Down move Tab focus PgUp/PgDn scroll g/G jump ? help";
4444
if completed {
45-
Line::from(format!("{base} q close"))
45+
Line::from(format!("Press q to close {base}"))
4646
} else {
47-
Line::from(format!("{base} Ctrl-C interrupt"))
47+
Line::from(format!("{base} q closes when finished Ctrl-C interrupt"))
48+
}
49+
}
50+
51+
fn completion_hint_line(completed: bool) -> Line<'static> {
52+
if completed {
53+
Line::from(vec![
54+
Span::styled(
55+
"READY TO CLOSE ",
56+
Style::default()
57+
.fg(Color::Green)
58+
.add_modifier(Modifier::BOLD),
59+
),
60+
Span::styled(
61+
"Press q to close this dashboard.",
62+
Style::default()
63+
.fg(Color::White)
64+
.add_modifier(Modifier::BOLD),
65+
),
66+
])
67+
} else {
68+
Line::from(vec![
69+
Span::styled(
70+
"RUNNING ",
71+
Style::default()
72+
.fg(Color::Yellow)
73+
.add_modifier(Modifier::BOLD),
74+
),
75+
Span::raw("The dashboard stays open until completion."),
76+
])
4877
}
4978
}
5079

@@ -55,7 +84,7 @@ pub fn render_dashboard(frame: &mut Frame<'_>, state: &DashboardState, show_help
5584
.constraints([
5685
Constraint::Length(7),
5786
Constraint::Min(12),
58-
Constraint::Length(3),
87+
Constraint::Length(5),
5988
])
6089
.split(area);
6190

@@ -247,6 +276,7 @@ fn render_footer(frame: &mut Frame<'_>, area: Rect, state: &DashboardState) {
247276
.join(" "),
248277
));
249278
}
279+
lines.push(completion_hint_line(state.completed));
250280
lines.push(key_hints(state.completed));
251281
frame.render_widget(
252282
Paragraph::new(lines).block(Block::default().borders(Borders::ALL).title("Summary")),
@@ -260,7 +290,7 @@ fn render_help(frame: &mut Frame<'_>, area: Rect, completed: bool) {
260290
let quit_line = if completed {
261291
"q: close the dashboard"
262292
} else {
263-
"q: ignored while the run is still active"
293+
"q: available after the run finishes"
264294
};
265295
let lines = vec![
266296
Line::from("Up/Down: move selected item"),
@@ -307,7 +337,7 @@ mod tests {
307337
DashboardState, SummaryRow,
308338
};
309339

310-
use super::render_dashboard;
340+
use super::{completion_hint_line, key_hints, render_dashboard};
311341

312342
fn render_to_string(state: &DashboardState) -> String {
313343
let backend = TestBackend::new(100, 40);
@@ -365,6 +395,43 @@ mod tests {
365395
let rendered = render_to_string(&state);
366396
assert!(rendered.contains("failed=1"));
367397
assert!(rendered.contains("network error"));
398+
assert!(rendered.contains("READY TO CLOSE"));
399+
assert!(rendered.contains("Press q to close this dashboard"));
400+
}
401+
402+
#[test]
403+
fn key_hints_explain_when_q_is_available() {
404+
let running = key_hints(false);
405+
let completed = key_hints(true);
406+
assert!(
407+
running
408+
.spans
409+
.iter()
410+
.any(|span| span.content.contains("q closes when finished"))
411+
);
412+
assert!(
413+
completed
414+
.spans
415+
.iter()
416+
.any(|span| span.content.contains("Press q to close"))
417+
);
418+
}
419+
420+
#[test]
421+
fn completion_hint_is_explicit_when_finished() {
422+
let completed = completion_hint_line(true);
423+
assert!(
424+
completed
425+
.spans
426+
.iter()
427+
.any(|span| span.content.contains("READY TO CLOSE"))
428+
);
429+
assert!(
430+
completed
431+
.spans
432+
.iter()
433+
.any(|span| span.content.contains("Press q to close this dashboard"))
434+
);
368435
}
369436

370437
#[test]

langcodec-cli/src/tui/reporter.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::{
22
io::{self, IsTerminal, Write},
33
sync::mpsc::{self, Sender},
44
thread::{self, JoinHandle},
5-
time::Duration,
65
};
76

87
use crate::{
@@ -126,7 +125,6 @@ fn map_tone(tone: DashboardLogTone) -> ui::Tone {
126125

127126
pub(super) enum DashboardMessage {
128127
Event(DashboardEvent),
129-
Shutdown,
130128
}
131129

132130
pub struct TuiReporter {
@@ -154,8 +152,6 @@ impl RunReporter for TuiReporter {
154152
let _ = self
155153
.sender
156154
.send(DashboardMessage::Event(DashboardEvent::Completed));
157-
thread::sleep(Duration::from_millis(160));
158-
let _ = self.sender.send(DashboardMessage::Shutdown);
159155
if let Some(handle) = self.join_handle.take() {
160156
match handle.join() {
161157
Ok(result) => result,

langcodec-cli/src/tui/terminal.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,6 @@ pub fn run_dashboard(
122122
loop {
123123
match rx.try_recv() {
124124
Ok(DashboardMessage::Event(event)) => state.apply(event),
125-
Ok(DashboardMessage::Shutdown) => {
126-
should_close = true;
127-
break;
128-
}
129125
Err(TryRecvError::Empty) => break,
130126
Err(TryRecvError::Disconnected) => {
131127
should_close = true;

0 commit comments

Comments
 (0)