From 1a43bf1ba86027a8547c7d46b73fa48cc312bb14 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 3 Aug 2026 22:24:30 +0200 Subject: [PATCH] Restore TUI key hints after updates --- crates/tui/src/app.rs | 22 ++++++++++++++++++++++ crates/tui/src/ui.rs | 10 +++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index abea556..354d29b 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -12,6 +12,7 @@ use gotcha_gitea::{ use crate::editor::{Editor, EditorAction, EditorEvent}; const ALL_BRANCHES: &str = "All branches"; +const UPDATE_STATUS_DURATION: Duration = Duration::from_secs(2); #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Tab { @@ -162,6 +163,7 @@ pub struct App { pub editor: Option, pub confirm: Option<(String, ConfirmAction)>, pub status: String, + status_deadline: Option, pub should_quit: bool, pub last_input: Instant, pub last_reload: Instant, @@ -218,6 +220,7 @@ impl App { editor: None, confirm: None, status: "Loading…".into(), + status_deadline: None, should_quit: false, last_input: Instant::now(), last_reload: Instant::now(), @@ -256,6 +259,7 @@ impl App { pub async fn handle_key(&mut self, key: KeyEvent) { self.last_input = Instant::now(); + self.status_deadline = None; if let Some(editor) = &mut self.editor { match editor.handle_key(key) { EditorEvent::Submit => self.submit_editor().await, @@ -328,6 +332,7 @@ impl App { pub async fn handle_mouse(&mut self, mouse: MouseEvent) { self.last_input = Instant::now(); + self.status_deadline = None; if self.editor.is_some() || self.confirm.is_some() { return; } @@ -396,6 +401,7 @@ impl App { let page = self.screen.page; let selected_key = self.screen.selected_item().map(|item| item.key.clone()); self.status = "Loading…".into(); + self.status_deadline = None; match self.load_screen(kind, page).await { Ok(mut screen) => { if let Some(key) = selected_key @@ -405,12 +411,21 @@ impl App { } self.screen = screen; self.status = format!("Updated {}", clock()); + self.status_deadline = Some(Instant::now() + UPDATE_STATUS_DURATION); } Err(error) => self.status = error, } self.last_reload = Instant::now(); } + pub fn visible_status(&self) -> Option<&str> { + (!self.status.is_empty() + && self + .status_deadline + .is_none_or(|deadline| Instant::now() < deadline)) + .then_some(self.status.as_str()) + } + async fn switch_tab(&mut self, tab: Tab) { self.tab = tab; self.history.clear(); @@ -1898,6 +1913,13 @@ mod tests { #[tokio::test] async fn auto_reload_waits_for_idle_overview_without_an_editor() { let mut app = App::new(Config::default(), None).await.unwrap(); + assert!(app.visible_status().unwrap().starts_with("Updated ")); + app.status_deadline = Some(Instant::now() - Duration::from_secs(1)); + assert_eq!(app.visible_status(), None); + app.status = "Network unavailable".into(); + app.status_deadline = None; + assert_eq!(app.visible_status(), Some("Network unavailable")); + app.last_reload = Instant::now() - Duration::from_secs(6); app.last_input = Instant::now() - Duration::from_secs(6); assert!(app.should_auto_reload()); diff --git a/crates/tui/src/ui.rs b/crates/tui/src/ui.rs index 63def97..cfbd96d 100644 --- a/crates/tui/src/ui.rs +++ b/crates/tui/src/ui.rs @@ -187,15 +187,15 @@ fn draw_detail(frame: &mut Frame<'_>, app: &App, area: Rect) { } fn draw_footer(frame: &mut Frame<'_>, app: &App, area: Rect) { - let status = if app.status.is_empty() { + let status = if let Some(status) = app.visible_status() { format!( - "Page {} · j/k move · Enter open · Backspace back · n/p pages · a/e/c/x/d actions · / filters · * favorite · r reload · q quit", - app.screen.page + "{} · Page {} · j/k move · Enter open · Backspace back · n/p pages · q quit", + status, app.screen.page ) } else { format!( - "{} · Page {} · j/k move · Enter open · Backspace back · n/p pages · q quit", - app.status, app.screen.page + "Page {} · j/k move · Enter open · Backspace back · n/p pages · a/e/c/x/d actions · / filters · * favorite · r reload · q quit", + app.screen.page ) }; frame.render_widget(