From 724cdfbd32d35fde9e0da1f1e141b836836f9a65 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 20 Sep 2026 16:31:24 +0000 Subject: [PATCH] Simplify agent command center hints and back navigation (#46840) ## Why The command center's shortcut footer consumes several rows in narrow terminals, leaving less space for tasks. The overview also ignores attempts to dismiss it with `Esc`. ## What changed - Keep primary actions in a single-line footer and show task and navigation shortcuts in a `?` help view. - Respect configured bindings, suppress conflicting hints, and ignore key repeats when toggling help. - Let the configured cancel key close help or cancel search and rename before dismissing the overview. - Track overview dismissal while disconnected and clear stale connection notices after reconnecting. - Use picker defaults for task action and error dialogs, and the theme accent color for agent status titles. ## Testing Add coverage for help rendering, cancel navigation, custom shortcut conflicts, and overview dismissal during reconnection. Update snapshots for the compact footer and narrow layouts. GitOrigin-RevId: 457b1e667dd87be2e8216936c5402eb6ab70fde1 --- codex-rs/tui/src/app.rs | 7 + codex-rs/tui/src/app/agent_center/hints.rs | 123 +++++++++- codex-rs/tui/src/app/agent_center/input.rs | 31 ++- codex-rs/tui/src/app/agent_center/render.rs | 30 ++- codex-rs/tui/src/app/agent_center_tests.rs | 105 ++++++++- codex-rs/tui/src/app/agent_status_feed.rs | 7 +- .../tui/src/app/agents_overview_actions.rs | 4 +- .../src/app/agents_overview_actions_tests.rs | 95 -------- .../tui/src/app/agents_overview_errors.rs | 2 +- .../tui/src/app/agents_overview_render.rs | 214 ------------------ codex-rs/tui/src/app/agents_overview_tests.rs | 93 +------- codex-rs/tui/src/app/agents_overview_view.rs | 12 +- codex-rs/tui/src/app/reconnect.rs | 4 +- ...tions__agents_custom_lifecycle_chords.snap | 28 --- ...view__tests__agents_overview_markdown.snap | 4 +- ...__agents_overview_markdown_long_lines.snap | 4 +- ...tests__agents_overview_markdown_table.snap | 4 +- ...tests__agents_overview_model_grouping.snap | 4 +- ...ests__agents_overview_recent_sessions.snap | 4 +- ...__command_center__live_center_columns.snap | 2 +- ...and_center__live_center_narrow_rename.snap | 2 +- ..._center_navigation_and_complete_hints.snap | 28 +++ ...and_center__live_center_status_filter.snap | 12 +- ...__tests__older_server_narrow_overview.snap | 5 + ...__tests__usage__agents_overview_usage.snap | 4 +- .../src/app/tests/active_reconnect_tests.rs | 21 ++ .../app/tests/navigation_reconnect_tests.rs | 14 -- ...daemon_command_center_vanished_rename.snap | 1 + ...ents_overview__tests__agents_overview.snap | 4 +- ...iew__tests__agents_overview_attention.snap | 4 +- ...__tests__agents_overview_empty_narrow.snap | 8 +- ...__tests__agents_overview_last_message.snap | 4 +- ..._tests__agents_overview_live_activity.snap | 4 +- ...ts__agents_overview_worktree_grouping.snap | 2 +- 34 files changed, 384 insertions(+), 506 deletions(-) delete mode 100644 codex-rs/tui/src/app/agents_overview_render.rs delete mode 100644 codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__actions__agents_custom_lifecycle_chords.snap create mode 100644 codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_navigation_and_complete_hints.snap diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 12c757223c..21005781d6 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -965,6 +965,13 @@ impl App { { if self.reconnect.presentation == reconnect::ReconnectPresentation::Overview { self.chat_widget.handle_disconnected_view_key(*key); + if self + .chat_widget + .selected_index_for_present_view(agents_overview::AGENTS_OVERVIEW_VIEW_ID) + .is_none() + { + self.reconnect.presentation = reconnect::ReconnectPresentation::Conversation; + } } else { self.chat_widget .handle_restricted_key(*key, RestrictedInputMode::Disconnected); diff --git a/codex-rs/tui/src/app/agent_center/hints.rs b/codex-rs/tui/src/app/agent_center/hints.rs index 8eeb8af0d3..7da721ef89 100644 --- a/codex-rs/tui/src/app/agent_center/hints.rs +++ b/codex-rs/tui/src/app/agent_center/hints.rs @@ -1,4 +1,4 @@ -//! Status-tab hints reflect shortcuts that are not claimed by configured actions. +//! A compact footer advertises primary actions; help lists the configured task shortcuts. use super::*; use crate::key_hint; @@ -20,6 +20,23 @@ pub(super) fn hint_line(items: &[(String, String)]) -> Line<'static> { } impl AgentsOverviewView { + fn center_list_hint(&self, action: ListAction) -> Option { + self.keymap.primary_hint(action).filter(|hint| { + !matches!(hint, ShortcutHint::Single(binding) if [ + &self.agents_keymap.resume, + &self.agents_keymap.search, + &self.agents_keymap.new_task, + &self.agents_keymap.new_worktree, + &self.agents_keymap.rename, + &self.agents_keymap.stop, + &self.agents_keymap.archive, + &self.agents_keymap.delete, + &self.agents_keymap.hide, + &self.agents_keymap.toggle_grouping, + ].into_iter().any(|bindings| bindings.contains(binding))) + }) + } + pub(super) fn center_filter_hint(&self) -> String { [key_hint::plain(KeyCode::Tab), key_hint::shift(KeyCode::Tab)] .into_iter() @@ -33,4 +50,108 @@ impl AgentsOverviewView { .collect::>() .join("/") } + + pub(super) fn center_task_hints(&self) -> Vec<(String, String)> { + let mut hints = vec![(self.center_filter_hint(), "filter".into())]; + for (action, bindings, label) in [ + ("new_task", &self.agents_keymap.new_task, "new"), + ( + "new_worktree", + &self.agents_keymap.new_worktree, + "new worktree", + ), + ("resume", &self.agents_keymap.resume, "resume"), + ("search", &self.agents_keymap.search, "search"), + ( + "toggle_grouping", + &self.agents_keymap.toggle_grouping, + "group", + ), + ("rename", &self.agents_keymap.rename, "rename"), + ("stop", &self.agents_keymap.stop, "stop"), + ("archive", &self.agents_keymap.archive, "archive"), + ("hide", &self.agents_keymap.hide, "hide"), + ("delete", &self.agents_keymap.delete, "delete"), + ] { + if (action != "new_worktree" || self.worktrees_enabled) + && let Some(hint) = self.agents_keymap.primary_hint(action, bindings) + { + hints.push((hint.display_label(), label.into())); + } + } + for (action, label) in [ + (ListAction::MoveUp, "up"), + (ListAction::MoveDown, "down"), + (ListAction::Accept, "open"), + (ListAction::PageUp, "page up"), + (ListAction::PageDown, "page down"), + ] { + if let Some(hint) = self.center_list_hint(action) { + hints.push((hint.display_label(), label.into())); + } + } + hints.push(( + key_hint::ctrl(KeyCode::Char('c')).display_label(), + "quit".into(), + )); + hints + } + + pub(super) fn center_footer_hints(&self) -> Vec<(String, String)> { + let state = self.state(); + if let Some(items) = &state.key_chord_hint { + return items.clone(); + } + let mut hints = Vec::new(); + if !state.help + && !state.editing_metadata() + && ![KeyModifiers::NONE, KeyModifiers::SHIFT] + .into_iter() + .any(|modifiers| { + self.center_shortcut_keys + .is_pressed(KeyEvent::new(KeyCode::Char('?'), modifiers)) + }) + { + hints.push(("?".into(), "help".into())); + } + let cancel = if state.help { + self.keymap.primary_hint(ListAction::Cancel) + } else { + self.center_list_hint(ListAction::Cancel) + }; + if let Some(hint) = cancel { + hints.push((hint.display_label(), "back".into())); + } + if state.help { + return hints; + } + if let Some(hint) = self.center_list_hint(ListAction::Accept) { + hints.push(( + hint.display_label(), + if state.rename_target.is_some() { + "rename" + } else { + "open" + } + .into(), + )); + } + if !state.editing_metadata() { + for (action, bindings, label) in [ + ("new_task", &self.agents_keymap.new_task, "new"), + ( + "new_worktree", + &self.agents_keymap.new_worktree, + "new worktree", + ), + ] { + if (action != "new_worktree" || self.worktrees_enabled) + && let Some(hint) = self.agents_keymap.primary_hint(action, bindings) + { + hints.push((hint.display_label(), label.into())); + } + } + } + hints + } } diff --git a/codex-rs/tui/src/app/agent_center/input.rs b/codex-rs/tui/src/app/agent_center/input.rs index 36c8e562f8..538a077f56 100644 --- a/codex-rs/tui/src/app/agent_center/input.rs +++ b/codex-rs/tui/src/app/agent_center/input.rs @@ -1,6 +1,7 @@ -//! Status-tab shortcuts yield to configured task and list bindings. +//! Tab filters and shortcut help yield to configured task and list bindings. use super::*; +use crossterm::event::KeyEventKind; impl AgentsOverviewView { pub(in crate::app::agents_overview_view) fn command_center_key( @@ -8,12 +9,40 @@ impl AgentsOverviewView { key: KeyEvent, ) -> bool { if self.state().editing_metadata() { + if self.keymap.action_for(key) == Some(ListAction::Cancel) { + self.on_ctrl_c(); + return true; + } return false; } let mut state = self.state(); + if state.help && self.keymap.action_for(key) == Some(ListAction::Cancel) { + state.help = false; + return true; + } + if state.help { + if key.code == KeyCode::Char('?') + && key.kind == KeyEventKind::Press + && !key + .modifiers + .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) + { + state.help = false; + } + return true; + } if self.center_shortcut_keys.is_pressed(key) { return false; } + if key.code == KeyCode::Char('?') + && key.kind == KeyEventKind::Press + && !key + .modifiers + .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) + { + state.help = !state.help; + return true; + } if key.code == KeyCode::Tab && !key .modifiers diff --git a/codex-rs/tui/src/app/agent_center/render.rs b/codex-rs/tui/src/app/agent_center/render.rs index b2742a1a8b..6b4cfb15a0 100644 --- a/codex-rs/tui/src/app/agent_center/render.rs +++ b/codex-rs/tui/src/app/agent_center/render.rs @@ -1,4 +1,4 @@ -//! Task rows, metadata editing and read-only details share a wide/narrow layout. +//! Status tabs, task rows and read-only details share a simple wide/narrow layout. use super::hints::hint_line; use super::*; @@ -48,9 +48,8 @@ struct CenterLayout { impl AgentsOverviewView { fn center_layout(&self, area: Rect) -> CenterLayout { - let footer_height = (self.footer_lines(area.width.saturating_sub(4)).len() as u16) - .min(area.height.saturating_sub(4)); let state = self.state(); + let footer_height = u16::from(area.height >= 2); let header_height = if area.height >= 6 { 3 } else { 0 }; let footer = row(area, area.height - footer_height, footer_height); let header = row(area, /*offset*/ 0, header_height); @@ -123,7 +122,7 @@ impl Renderable for AgentsOverviewView { let layout = self.center_layout(area); let inset = |area: Rect| area.inner(Margin::new(/*horizontal*/ 2, /*vertical*/ 0)); - let footer_lines = self.footer_lines(area.width.saturating_sub(4)); + let footer_hints = self.center_footer_hints(); let search_key = self .agents_keymap .primary_hint("search", &self.agents_keymap.search) @@ -223,12 +222,31 @@ impl Renderable for AgentsOverviewView { (state.refresh_failed && !state.loading).then(|| "Error loading tasks".to_owned()) }) .or_else(|| state.server_version_notice.clone()); - if let Some(notice) = notice.filter(|_| state.key_chord_hint.is_none()) { + if let Some(notice) = notice.filter(|_| !state.help && state.key_chord_hint.is_none()) { line(notice.dim(), inset(layout.footer), buf); } else { - Paragraph::new(footer_lines).render(inset(layout.footer), buf); + line(hint_line(&footer_hints), inset(layout.footer), buf); } + let help = state.help; drop(state); + if help { + let body = row( + area, + layout.header.height, + layout.footer.y - layout.header.bottom(), + ); + Clear.render(inset(body), buf); + let lines = crate::wrapping::word_wrap_lines( + vec![ + "Task shortcuts".bold().into(), + Line::default(), + hint_line(&self.center_task_hints()), + ], + usize::from(inset(body).width), + ); + Paragraph::new(lines).render(inset(body), buf); + return; + } for y in layout.gap.y..layout.gap.bottom() { if layout.gap.width > 1 { buf[(layout.gap.x + 1, y)] diff --git a/codex-rs/tui/src/app/agent_center_tests.rs b/codex-rs/tui/src/app/agent_center_tests.rs index 7458fac065..dfa01da767 100644 --- a/codex-rs/tui/src/app/agent_center_tests.rs +++ b/codex-rs/tui/src/app/agent_center_tests.rs @@ -235,7 +235,8 @@ async fn live_center_rename_retains_target_when_status_leaves_filter() -> Result #[tokio::test] async fn live_center_navigation_and_complete_hints() { - let app = make_test_app().await; + let mut app = make_test_app().await; + app.config.features.enable(Feature::Worktrees).unwrap(); let ready = ThreadId::from_u128(/*value*/ 42); let needs_you = ThreadId::from_u128(/*value*/ 43); let unloaded = ThreadId::from_u128(/*value*/ 44); @@ -273,17 +274,105 @@ async fn live_center_navigation_and_complete_hints() { screen(&view, /*width*/ 40, /*height*/ 12) ); view.handle_key_event(KeyCode::Tab.into()); + + let repeat_help = + KeyEvent::new_with_kind(KeyCode::Char('?'), KeyModifiers::NONE, KeyEventKind::Repeat); + view.handle_key_event(KeyCode::Char('?').into()); + view.handle_key_event(repeat_help); + insta::assert_snapshot!(screen(&view, /*width*/ 40, /*height*/ 24)); + view.handle_key_event(KeyCode::Esc.into()); + view.handle_key_event(repeat_help); + assert!(screen(&view, /*width*/ 40, /*height*/ 24).contains("Search tasks")); + assert!(!view.is_complete()); + view.handle_key_event(KeyCode::Esc.into()); + assert!(view.is_complete()); } #[tokio::test] async fn live_center_fixed_shortcuts_yield_to_configured_actions() { let mut app = make_test_app().await; - let config: TuiKeymap = toml::from_str("[agents]\nresume = 'tab'").unwrap(); + let config: TuiKeymap = toml::from_str("[list]\ncancel = 'q'").unwrap(); app.keymap = RuntimeKeymap::from_config(&config).unwrap(); - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - app.app_event_tx = AppEventSender::new(tx); - let mut view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); - view.handle_key_event(KeyCode::Tab.into()); - assert!(matches!(rx.try_recv(), Ok(AppEvent::OpenResumePicker))); - assert!(rx.try_recv().is_err()); + for editor_key in ['f', 'r'] { + let mut view = app.agents_overview_view( + vec![overview_thread( + ThreadId::from_u128(/*value*/ 42), + /*parent_thread_id*/ None, + "Task", + ThreadStatus::Idle, + )], + /*selected_thread_id*/ None, + ); + view.handle_key_event(KeyCode::Char(editor_key).into()); + assert!( + app.agents_overview + .view_state + .lock() + .unwrap() + .editing_metadata() + ); + view.handle_key_event(KeyCode::Char('q').into()); + assert!( + !app.agents_overview + .view_state + .lock() + .unwrap() + .editing_metadata() + ); + assert!(!view.is_complete()); + } + for (binding, key) in [ + ("tab", KeyEvent::from(KeyCode::Tab)), + ("?", KeyEvent::from(KeyCode::Char('?'))), + ( + "shift-?", + KeyEvent::new(KeyCode::Char('?'), KeyModifiers::SHIFT), + ), + ("f9", KeyEvent::from(KeyCode::F(9))), + ("enter", KeyEvent::from(KeyCode::Enter)), + ] { + let mut app = make_test_app().await; + let config: TuiKeymap = toml::from_str(&format!( + "[list]\ncancel = 'f9'\n[agents]\nresume = '{binding}'" + )) + .unwrap(); + app.keymap = RuntimeKeymap::from_config(&config).unwrap(); + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + app.app_event_tx = AppEventSender::new(tx); + let mut view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); + view.handle_key_event(KeyCode::Esc.into()); + assert!(!view.is_complete()); + if key.code == KeyCode::Char('?') { + assert!(!screen(&view, /*width*/ 100, /*height*/ 24).contains("? help")); + } + if key.code == KeyCode::Enter { + assert!(!screen(&view, /*width*/ 100, /*height*/ 24).contains("enter open")); + view.handle_key_event(KeyCode::Char('?').into()); + assert!(!screen(&view, /*width*/ 100, /*height*/ 24).contains("enter open")); + app.agents_overview + .view_state + .lock() + .unwrap() + .server_version_notice = Some("Version notice".into()); + assert!(screen(&view, /*width*/ 100, /*height*/ 24).contains("f9 back")); + view.handle_key_event(KeyCode::Esc.into()); + assert!(screen(&view, /*width*/ 100, /*height*/ 24).contains("Task shortcuts")); + view.handle_key_event(KeyCode::F(9).into()); + assert!(!screen(&view, /*width*/ 100, /*height*/ 24).contains("Task shortcuts")); + app.agents_overview + .view_state + .lock() + .unwrap() + .server_version_notice = None; + view.handle_key_event(KeyCode::Char('f').into()); + assert!(!screen(&view, /*width*/ 100, /*height*/ 24).contains("enter open")); + } + view.handle_key_event(key); + assert!(!view.is_complete()); + assert!( + matches!(rx.try_recv(), Ok(AppEvent::OpenResumePicker)), + "{binding}" + ); + assert!(rx.try_recv().is_err()); + } } diff --git a/codex-rs/tui/src/app/agent_status_feed.rs b/codex-rs/tui/src/app/agent_status_feed.rs index 1039b043ee..bab6e56737 100644 --- a/codex-rs/tui/src/app/agent_status_feed.rs +++ b/codex-rs/tui/src/app/agent_status_feed.rs @@ -4,6 +4,7 @@ use super::ThreadBufferedEvent; use super::ThreadEventStore; use crate::history_cell::HistoryCell; use crate::history_cell::plain_lines; +use crate::style::accent_color; use crate::text_formatting::truncate_text; use codex_app_server_protocol::CollabAgentTool; use codex_app_server_protocol::ServerNotification; @@ -112,7 +113,11 @@ impl AgentStatusThreadPreview { } fn title_line(&self) -> Line<'static> { - vec![" • ".dim(), format!("`{}`", self.agent_path).cyan()].into() + vec![ + " • ".dim(), + format!("`{}`", self.agent_path).fg(accent_color()), + ] + .into() } fn preview_lines(&self, width: u16) -> Vec> { diff --git a/codex-rs/tui/src/app/agents_overview_actions.rs b/codex-rs/tui/src/app/agents_overview_actions.rs index 6ca011cc8d..268414bacc 100644 --- a/codex-rs/tui/src/app/agents_overview_actions.rs +++ b/codex-rs/tui/src/app/agents_overview_actions.rs @@ -157,7 +157,7 @@ impl App { ..Default::default() }, ], - ..Default::default() + ..SelectionViewParams::picker() }); } @@ -313,7 +313,7 @@ impl App { dismiss_on_select: true, ..Default::default() }], - ..Default::default() + ..SelectionViewParams::picker() }); } diff --git a/codex-rs/tui/src/app/agents_overview_actions_tests.rs b/codex-rs/tui/src/app/agents_overview_actions_tests.rs index 256deb96b0..969272859c 100644 --- a/codex-rs/tui/src/app/agents_overview_actions_tests.rs +++ b/codex-rs/tui/src/app/agents_overview_actions_tests.rs @@ -742,98 +742,3 @@ async fn lifecycle_removes_background_and_current_tasks_without_losing_the_dashb } Ok(()) } - -#[tokio::test] -async fn disabled_footer_shortcuts_stay_bold_when_wrapped() { - let app = make_test_app().await; - let mut view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); - view.handle_key_event(KeyCode::Esc.into()); - let area = Rect::new( - /*x*/ 0, /*y*/ 0, /*width*/ 84, /*height*/ 24, - ); - let mut buffer = ratatui::buffer::Buffer::empty(area); - view.render(area, &mut buffer); - let delete_key = crate::key_hint::plain(KeyCode::Delete).display_label(); - for (key, label) in [ - ("x", "x stop"), - ("h", "h hide"), - ("a", "a archive"), - (delete_key.as_str(), delete_key.as_str()), - ] { - let cells = buffer - .content() - .windows(label.len()) - .find(|cells| { - cells - .iter() - .map(ratatui::buffer::Cell::symbol) - .collect::() - == label - }) - .expect("footer shortcut"); - assert_eq!( - cells[..key.len()] - .iter() - .map(|cell| cell.modifier) - .collect::>(), - vec![ratatui::style::Modifier::BOLD | ratatui::style::Modifier::DIM; key.len()] - ); - } -} - -#[tokio::test] -async fn lifecycle_footer_keeps_custom_chords_with_labels() { - let mut app = make_test_app().await; - app.keymap = RuntimeKeymap::from_config( - &serde_json::from_value(serde_json::json!({ - "agents": { "archive": "f5 f6", "delete": "f5 f7", "hide": "f5 f8" } - })) - .unwrap(), - ) - .unwrap(); - let mut view = app.agents_overview_view( - vec![overview_thread( - ThreadId::new(), - /*parent_thread_id*/ None, - "Task", - ThreadStatus::Idle, - )], - /*selected_thread_id*/ None, - ); - view.handle_key_event(KeyCode::Esc.into()); - for width in [36, 48, 80] { - let area = Rect::new(/*x*/ 0, /*y*/ 0, width + 4, /*height*/ 24); - let mut buffer = ratatui::buffer::Buffer::empty(area); - view.render(area, &mut buffer); - let lines = buffer - .content() - .chunks(usize::from(area.width)) - .map(|row| { - row.iter() - .map(ratatui::buffer::Cell::symbol) - .collect::() - .trim() - .to_string() - }) - .collect::>(); - for label in ["archive", "delete", "hide"] { - assert!( - lines - .iter() - .any(|line| line.contains(label) && line.contains("f5")) - ); - } - assert!( - lines - .iter() - .all(|line| unicode_width::UnicodeWidthStr::width(line.as_str()) - <= usize::from(width)) - ); - } - app.chat_widget.show_bottom_pane_view(Box::new(view)); - insta::assert_snapshot!( - "agents_custom_lifecycle_chords", - render_bottom_popup(&app.chat_widget, /*width*/ 48) - .replace(&test_path_display("/tmp/project"), "/tmp/project") - ); -} diff --git a/codex-rs/tui/src/app/agents_overview_errors.rs b/codex-rs/tui/src/app/agents_overview_errors.rs index 5dc356b8d1..3f94131b8c 100644 --- a/codex-rs/tui/src/app/agents_overview_errors.rs +++ b/codex-rs/tui/src/app/agents_overview_errors.rs @@ -38,7 +38,7 @@ impl App { Line::from(message.clone().dim()), ])), items, - ..Default::default() + ..SelectionViewParams::picker() }); } self.chat_widget.add_error_message(message); diff --git a/codex-rs/tui/src/app/agents_overview_render.rs b/codex-rs/tui/src/app/agents_overview_render.rs deleted file mode 100644 index b24fdc1be8..0000000000 --- a/codex-rs/tui/src/app/agents_overview_render.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! Layout rendering and cursor placement for the agent dashboard. -//! Only search and rename reserve an editor row; list actions share a wrapped footer. - -use super::*; - -impl AgentsOverviewView { - pub(super) fn footer_lines(&self, width: u16) -> Vec> { - let state = self.state(); - let message = if let Some(items) = &state.key_chord_hint { - Some( - items - .iter() - .map(|(key, label)| format!("{key} {label}")) - .collect::>() - .join(" "), - ) - } else if state.editing_metadata() && state.connection_notice.is_some() { - Some("esc cancel · actions paused until reconnected".into()) - } else if state.editing_metadata() { - Some(format!( - "{} {} esc cancel", - self.keymap - .primary_hint(ListAction::Accept) - .map(crate::key_hint::ShortcutHint::display_label) - .unwrap_or_default(), - if state.rename_target.is_some() { - "rename" - } else { - "open" - } - )) - } else if state.connection_notice.is_some() { - Some("ctrl+c quit · actions paused until the list is refreshed".into()) - } else if state.creating_worktree { - Some("Creating worktree… ctrl+c quit".into()) - } else { - None - }; - drop(state); - if let Some(message) = message { - return textwrap::wrap(&message, usize::from(width.max(1))) - .into_iter() - .map(|line| line.into_owned().dim().into()) - .collect(); - } - if width < 24 { - let hints = [ - ("new_task", &self.agents_keymap.new_task, "new"), - ( - "new_worktree", - &self.agents_keymap.new_worktree, - "new worktree", - ), - ("search", &self.agents_keymap.search, "search"), - ] - .into_iter() - .filter(|(action, _, _)| *action != "new_worktree" || self.worktrees_enabled) - .filter_map(|(action, bindings, label)| { - self.agents_keymap - .primary_hint(action, bindings) - .map(|hint| format!("{} {label}", hint.display_label())) - }); - return hints - .chain(["ctrl+c quit".into()]) - .flat_map(|hint| { - textwrap::wrap(&hint, usize::from(width.max(1))) - .into_iter() - .map(|line| line.into_owned().dim().into()) - .collect::>() - }) - .collect(); - } - let list_hint = |action| { - self.keymap.primary_hint(action).filter(|hint| { - !matches!(hint, ShortcutHint::Single(binding) - if [ - &self.agents_keymap.resume, - &self.agents_keymap.search, - &self.agents_keymap.new_task, - &self.agents_keymap.new_worktree, - &self.agents_keymap.rename, - &self.agents_keymap.stop, - &self.agents_keymap.archive, - &self.agents_keymap.delete, - &self.agents_keymap.hide, - &self.agents_keymap.toggle_grouping, - ] - .into_iter() - .any(|bindings| bindings.contains(binding))) - }) - }; - let navigation_hints = [ListAction::MoveUp, ListAction::MoveDown] - .into_iter() - .filter_map(list_hint) - .map(|hint| hint.display_label().replace(" + ", "+")) - .collect::>(); - let navigation_hint = navigation_hints.join( - if navigation_hints - .iter() - .all(|hint| hint.chars().count() == 1) - { - "" - } else { - " " - }, - ); - let mut hints: Vec> = Vec::new(); - if !navigation_hint.is_empty() { - hints.push(vec![navigation_hint.bold(), " navigate".dim()].into()); - } - let mut add_hint = |hint: Option, label: &'static str, enabled: bool| { - if let Some(hint) = hint { - let key = hint.display_label().replace(" + ", "+"); - hints.push( - vec![ - if enabled { - key.bold() - } else { - key.bold().dim() - }, - format!(" {label}").dim(), - ] - .into(), - ); - } - }; - add_hint( - self.agents_keymap - .primary_hint("resume", &self.agents_keymap.resume), - "resume", - true, - ); - let open_hint = list_hint(ListAction::Accept); - add_hint(open_hint, "open", true); - add_hint( - self.agents_keymap - .primary_hint("new_task", &self.agents_keymap.new_task), - "new", - true, - ); - add_hint( - self.agents_keymap - .primary_hint("new_worktree", &self.agents_keymap.new_worktree), - "new worktree", - self.worktrees_enabled, - ); - add_hint( - self.agents_keymap - .primary_hint("search", &self.agents_keymap.search), - "search", - true, - ); - add_hint( - self.agents_keymap - .primary_hint("toggle_grouping", &self.agents_keymap.toggle_grouping), - match self.state().grouping { - AgentsOverviewGrouping::Project => "group: project", - AgentsOverviewGrouping::Status => "group: status", - AgentsOverviewGrouping::Model => "group: model", - }, - true, - ); - add_hint( - self.agents_keymap - .primary_hint("rename", &self.agents_keymap.rename), - "rename", - true, - ); - add_hint( - self.agents_keymap - .primary_hint("stop", &self.agents_keymap.stop), - "stop", - self.selected_row() - .is_some_and(|row| matches!(row.thread.status, ThreadStatus::Active { .. })), - ); - for (action, bindings) in [ - ("hide", &self.agents_keymap.hide), - ("archive", &self.agents_keymap.archive), - ("delete", &self.agents_keymap.delete), - ] { - add_hint( - self.agents_keymap.primary_hint(action, bindings), - action, - self.selected_row().is_some(), - ); - } - if self.state().editing_metadata() { - add_hint(list_hint(ListAction::Cancel), "cancel", true); - } - hints.push(vec!["ctrl+c".bold(), " quit".dim()].into()); - let separator = if hints.iter().map(Line::width).sum::() - + hints.len().saturating_sub(1) * 2 - <= usize::from(width) - { - " " - } else { - " " - }; - crate::footer_hint::wrap_hint_rows(hints, width, separator.len(), Line::width) - .into_iter() - .flat_map(|row| { - let mut line = Line::default(); - for hint in row { - if !line.spans.is_empty() { - line.spans.push(separator.dim()); - } - line.spans.extend(hint.spans); - } - // An individual custom chord may be wider than the entire terminal. - crate::wrapping::word_wrap_lines([line], usize::from(width.max(1))) - }) - .collect() - } -} diff --git a/codex-rs/tui/src/app/agents_overview_tests.rs b/codex-rs/tui/src/app/agents_overview_tests.rs index 89a83b333e..7eee6d465b 100644 --- a/codex-rs/tui/src/app/agents_overview_tests.rs +++ b/codex-rs/tui/src/app/agents_overview_tests.rs @@ -79,12 +79,7 @@ async fn older_server_notice_is_visible_in_agents_overview() { let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); app.chat_widget.show_bottom_pane_view(Box::new(view)); let rendered = render_bottom_popup(&app.chat_widget, /*width*/ 80); - insta::assert_snapshot!( - rendered - .lines() - .find(|line| line.contains("Service v0.153.0") || line.contains("Service v0.152.1")) - .unwrap() - ); + insta::assert_snapshot!(rendered.lines().last().unwrap()); } #[tokio::test] @@ -95,7 +90,7 @@ async fn server_version_overview_notice_updates_and_clears() { let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); app.chat_widget.show_bottom_pane_view(Box::new(view)); let rendered = render_bottom_popup(&app.chat_widget, /*width*/ 80); - insta::assert_snapshot!(rendered.lines().find(|line| line.contains("Service v0.151.0")).unwrap(), @" Service v0.151.0 < Codex CLI v0.153.0"); + insta::assert_snapshot!(rendered.lines().last().unwrap(), @" Service v0.151.0 < Codex CLI v0.153.0"); app.update_server_version_overview_notice("0.153.0", /*server_version*/ None); let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); @@ -2606,90 +2601,6 @@ fn trust_fixture_folders(app: &mut App) { #[path = "agents_overview_usage_tests.rs"] mod usage; -#[tokio::test] -async fn command_center_escape_cancels_editors_and_never_closes_list() { - for (vim, offline, empty, running) in [ - (false, false, false, false), - (true, false, false, false), - (false, true, false, false), - (false, false, true, false), - (false, false, false, true), - ] { - let (mut app, mut rx, mut op_rx) = crate::app::tests::make_test_app_with_channels().await; - if vim { - app.chat_widget.toggle_vim_mode_and_notify(); - } - if running { - app.chat_widget.handle_server_notification( - ServerNotification::TurnStarted( - codex_app_server_protocol::TurnStartedNotification { - thread_id: ThreadId::new().to_string(), - turn: codex_app_server_protocol::Turn { - id: "running".into(), - items_view: codex_app_server_protocol::TurnItemsView::Full, - items: Vec::new(), - status: codex_app_server_protocol::TurnStatus::InProgress, - error: None, - started_at: None, - completed_at: None, - duration_ms: None, - }, - }, - ), - /*replay_kind*/ None, - ); - assert!(app.chat_widget.is_task_running_for_test()); - } - while rx.try_recv().is_ok() {} - let rows = if empty { - Vec::new() - } else { - vec![overview_thread( - ThreadId::new(), - /*parent_thread_id*/ None, - "Task", - ThreadStatus::Idle, - )] - }; - let mut view = app.agents_overview_view(rows, /*selected_thread_id*/ None); - if offline { - app.agents_overview - .view_state - .lock() - .unwrap() - .connection_notice = Some("Offline"); - } - view.handle_key_event(KeyCode::Char('f').into()); - view.handle_paste("nwogrxfha".into()); - assert!(rx.try_recv().is_err()); - view.handle_key_event(KeyCode::Esc.into()); - if !empty { - assert_eq!(view.selected_index(), Some(0)); - } - assert_eq!( - view.on_ctrl_c(), - crate::bottom_pane::CancellationEvent::NotHandled - ); - app.chat_widget.show_bottom_pane_view(Box::new(view)); - for _ in 0..3 { - app.chat_widget.handle_key_event(KeyCode::Esc.into()); - } - assert!( - app.chat_widget - .selected_index_for_present_view(AGENTS_OVERVIEW_VIEW_ID) - .is_some() - ); - assert!(rx.try_recv().is_err()); - app.chat_widget - .handle_key_event(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)); - assert!(op_rx.try_recv().is_err()); - assert!(matches!( - rx.try_recv(), - Ok(AppEvent::Exit(crate::app_event::ExitMode::ShutdownFirst)) - )); - } -} - #[tokio::test] async fn command_center_new_actions_use_selection_and_leave_metadata_text_alone() { let mut app = make_test_app().await; diff --git a/codex-rs/tui/src/app/agents_overview_view.rs b/codex-rs/tui/src/app/agents_overview_view.rs index 17accb4e63..b39734b128 100644 --- a/codex-rs/tui/src/app/agents_overview_view.rs +++ b/codex-rs/tui/src/app/agents_overview_view.rs @@ -4,9 +4,6 @@ #[path = "agent_center/mod.rs"] pub(super) mod command_center; -#[path = "agents_overview_render.rs"] -mod render; - #[path = "agents_overview_grouping.rs"] mod grouping; @@ -137,6 +134,7 @@ pub(super) struct AgentsOverviewViewState { scroll: usize, page_height: usize, status_filter: usize, + help: bool, pub(super) input: String, pub(super) key_chord_hint: Option>, pub(super) creating_worktree: bool, @@ -518,10 +516,6 @@ impl BottomPaneView for AgentsOverviewView { if self.command_center_key(key) { return; } - if key.code == KeyCode::Esc { - self.on_ctrl_c(); - return; - } if key.code == KeyCode::Backspace && self.state().editing_metadata() && key.modifiers.is_empty() @@ -658,7 +652,9 @@ impl BottomPaneView for AgentsOverviewView { } ListAction::Accept => self.activate(), ListAction::Cancel => { - self.on_ctrl_c(); + if matches!(self.on_ctrl_c(), CancellationEvent::NotHandled) { + self.state().completion = Some(ViewCompletion::Cancelled); + } } ListAction::PageUp | ListAction::PageDown => { self.page_selection(action); diff --git a/codex-rs/tui/src/app/reconnect.rs b/codex-rs/tui/src/app/reconnect.rs index dd7cc5b723..4c1880c89a 100644 --- a/codex-rs/tui/src/app/reconnect.rs +++ b/codex-rs/tui/src/app/reconnect.rs @@ -271,6 +271,7 @@ impl App { .view_state .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); + state.connection_notice = None; if state.creating_worktree { state.creating_worktree = false; self.pending_managed_worktree_creation = false; @@ -443,9 +444,6 @@ impl App { // A hidden overview performs this discovery when it is next opened. self.agents_overview.initialized = false; if self.reconnect.presentation == ReconnectPresentation::Overview { - if let Ok(mut state) = self.agents_overview.view_state.lock() { - state.connection_notice = None; - } let threads = self .agents_overview .threads diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__actions__agents_custom_lifecycle_chords.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__actions__agents_custom_lifecycle_chords.snap deleted file mode 100644 index 769ec1ed50..0000000000 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__actions__agents_custom_lifecycle_chords.snap +++ /dev/null @@ -1,28 +0,0 @@ ---- -source: tui/src/app/agents_overview_actions_tests.rs -expression: "render_bottom_popup(&app.chat_widget,\n48).replace(&test_path_display(\"/tmp/project\"), \"/tmp/project\")" ---- - Agent command center Group: Project g - All 1 Needs you 0 Working 0 › - ──────────────────────────────────────────── - Search tasks · f - Tasks - /tmp/project 1 - › ○ Task - - - - - - - - - - - - - - ↑↓ navigate o resume enter open n new - w new worktree f search g group: project - r rename x stop f5 f8 hide f5 f6 archive - f5 f7 delete ctrl+c quit diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown.snap index e85aad4a15..61a8d589d1 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown.snap @@ -40,5 +40,5 @@ expression: normalize_agent_center_snapshot(&cached) " │ " " │ " " │ " -" ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename " -" x stop h hide a archive del delete ctrl+c quit " +" │ " +" ? help esc back enter open n new w new worktree " diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_long_lines.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_long_lines.snap index c70ed41c2d..506aeca638 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_long_lines.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_long_lines.snap @@ -24,5 +24,5 @@ expression: "normalize_agent_center_snapshot(render_bottom_popup(&app.chat_widge │ … │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_table.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_table.snap index f91e974ea5..d9330d8c8b 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_table.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_markdown_table.snap @@ -24,5 +24,5 @@ expression: "normalize_agent_center_snapshot(render_bottom_popup(&app.chat_widge │ … │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_model_grouping.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_model_grouping.snap index 29cdd29511..09ee50d57a 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_model_grouping.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_model_grouping.snap @@ -24,5 +24,5 @@ expression: "render_bottom_popup(&app.chat_widget,\n100).replace(&test_path_disp │ │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: model r rename x stop - h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap index 5ca484b5ba..ba2d0e4430 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap @@ -24,5 +24,5 @@ expression: "age.replace_all(&rendered, \" [age]\")" - ↑↓ navigate o resume enter open n new w new worktree f search - g group: project r rename x stop h hide a archive del delete ctrl+c quit + + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_columns.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_columns.snap index bef8fee119..d5231e614c 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_columns.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_columns.snap @@ -23,4 +23,4 @@ expression: "screen(&view, 160, 22)" │ │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename x stop h hide a archive del delete ctrl+c quit + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_narrow_rename.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_narrow_rename.snap index 9ff86b3dee..0901c396de 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_narrow_rename.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_narrow_rename.snap @@ -19,4 +19,4 @@ expression: rendered - enter rename esc cancel + esc back enter rename diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_navigation_and_complete_hints.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_navigation_and_complete_hints.snap new file mode 100644 index 0000000000..de7303cc19 --- /dev/null +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_navigation_and_complete_hints.snap @@ -0,0 +1,28 @@ +--- +source: tui/src/app/agent_center_tests.rs +expression: "screen(&view, 40, 24)" +--- + Agent command center Group: Projec… + All 3 Needs you 1 Working 0 › + ──────────────────────────────────── + Task shortcuts + + tab/shift+tab filter n new w new + worktree o resume f search g + group r rename x stop a archive + h hide del delete ↑ up ↓ down + enter open pgup page up pgdn page + down ctrl+c quit + + + + + + + + + + + + + esc back diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_status_filter.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_status_filter.snap index 3dbc43ee11..76ddfe820e 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_status_filter.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__command_center__live_center_status_filter.snap @@ -6,11 +6,11 @@ expression: "screen(&view, 40, 12)" ‹ Working 0 Ready 1 Inactive 1 ──────────────────────────────────── Search tasks · f - ↑ Tasks + Tasks + /tmp/project 1 of 3 › ○ Unloaded task - ↑↓ navigate o resume enter open - n new w new worktree f search - g group: project r rename x stop - h hide a archive del delete - ctrl+c quit + + + + ? help esc back enter open n new… diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__older_server_narrow_overview.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__older_server_narrow_overview.snap index 07295b1454..e5e9a8a881 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__older_server_narrow_overview.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__older_server_narrow_overview.snap @@ -20,4 +20,9 @@ expression: "render_bottom_popup(&app.chat_widget, 12)" + + + + + Service… diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__usage__agents_overview_usage.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__usage__agents_overview_usage.snap index 893a154ecc..42022ce9f9 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__usage__agents_overview_usage.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__usage__agents_overview_usage.snap @@ -24,5 +24,5 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj │ Review parser │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/app/tests/active_reconnect_tests.rs b/codex-rs/tui/src/app/tests/active_reconnect_tests.rs index 27c1a3cd20..683e8fc461 100644 --- a/codex-rs/tui/src/app/tests/active_reconnect_tests.rs +++ b/codex-rs/tui/src/app/tests/active_reconnect_tests.rs @@ -215,6 +215,10 @@ async fn reconnect_restores_history_permissions_and_keeps_old_input_paused() -> }, ); } + if edit_offline { + let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); + app.chat_widget.show_bottom_pane_view(Box::new(view)); + } app.begin_reconnect(); if deferred_notice { assert_eq!( @@ -227,6 +231,12 @@ async fn reconnect_restores_history_permissions_and_keeps_old_input_paused() -> ); } if edit_offline { + for key in [KeyCode::Esc, KeyCode::Enter] { + app.handle_tui_event(&mut tui, &mut session, TuiEvent::Key(key.into())) + .await?; + } + assert!(!app.chat_widget.has_active_view()); + assert_eq!(app.chat_widget.composer_text_with_pending(), "kept draft"); app.handle_tui_event( &mut tui, &mut session, @@ -280,6 +290,17 @@ async fn reconnect_restores_history_permissions_and_keeps_old_input_paused() -> ); app.finish_reconnect(&mut tui, &mut session, &mut events, connected, "2.1.0") .await?; + if edit_offline { + assert!(!app.chat_widget.has_active_view()); + assert!( + app.agents_overview + .view_state + .lock() + .unwrap() + .connection_notice + .is_none() + ); + } assert!(app.pending_server_profiles.is_empty()); assert!(!app.pending_managed_worktree_creation); assert!( diff --git a/codex-rs/tui/src/app/tests/navigation_reconnect_tests.rs b/codex-rs/tui/src/app/tests/navigation_reconnect_tests.rs index d80ec2c219..c1ebfdb0b7 100644 --- a/codex-rs/tui/src/app/tests/navigation_reconnect_tests.rs +++ b/codex-rs/tui/src/app/tests/navigation_reconnect_tests.rs @@ -438,20 +438,6 @@ async fn reconnect_daemon_command_center_after_socket_replacement_without_a_conv ) .await?; - assert!(app.chat_widget.has_active_view()); - app.handle_tui_event( - &mut tui, - &mut session, - TuiEvent::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)), - ) - .await?; - - assert!(app.chat_widget.has_active_view()); - // Esc stays in the overview. Dismiss the retained view explicitly - // to inspect the unavailable conversation and its cached draft below. - app.agents_overview.view_state.lock().unwrap().completion = - Some(crate::bottom_pane::ViewCompletion::Accepted); - app.chat_widget.handle_key_event(KeyCode::Null.into()); assert!(!app.chat_widget.has_active_view()); assert_eq!(app.current_displayed_thread_id(), Some(id)); let history = drain_history(&mut app, &mut tui, &mut session, &mut events).await?; diff --git a/codex-rs/tui/src/app/tests/snapshots/codex_tui__app__tests__navigation_reconnect__daemon_command_center_vanished_rename.snap b/codex-rs/tui/src/app/tests/snapshots/codex_tui__app__tests__navigation_reconnect__daemon_command_center_vanished_rename.snap index 4516e2c2fe..87973aee55 100644 --- a/codex-rs/tui/src/app/tests/snapshots/codex_tui__app__tests__navigation_reconnect__daemon_command_center_vanished_rename.snap +++ b/codex-rs/tui/src/app/tests/snapshots/codex_tui__app__tests__navigation_reconnect__daemon_command_center_vanished_rename.snap @@ -24,4 +24,5 @@ expression: "render_bottom_popup(&app.chat_widget, 100)" │ │ │ + │ Service v2.0.0 ≠ Codex CLI v0.0.0 · /daemon diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview.snap index e29544a8f3..68c4d6f583 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview.snap @@ -24,5 +24,5 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj │ │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_attention.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_attention.snap index e1a2073641..41ebb97a0b 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_attention.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_attention.snap @@ -24,5 +24,5 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj │ Repair authentication │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap index 92cb531f89..d2bba146d3 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap @@ -22,7 +22,7 @@ expression: rendered - ↑↓ navigate o resume enter open n new - w new worktree f search g group: project - r rename x stop h hide a archive del delete - ctrl+c quit + + + + ? help esc back enter open n new w new … diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_last_message.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_last_message.snap index c508a007a5..bf39022eb2 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_last_message.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_last_message.snap @@ -15,6 +15,7 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj │ Found the regression in the parser. │ Found the regression in the parser. │ Found the regression in the parser. + │ Found the regression in the parser. │ … │ │ Project @@ -24,5 +25,4 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj │ Prompt │ Investigate parser │ … - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_live_activity.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_live_activity.snap index 43f860b04c..7e45132ce4 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_live_activity.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_live_activity.snap @@ -24,5 +24,5 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj │ │ │ - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename - x stop h hide a archive del delete ctrl+c quit + │ + ? help esc back enter open n new w new worktree diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_worktree_grouping.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_worktree_grouping.snap index b10bb269ce..2838181dcb 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_worktree_grouping.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_worktree_grouping.snap @@ -25,4 +25,4 @@ expression: normalize_agent_center_snapshot(grouping) - ↑↓ navigate o resume enter open n new w new worktree f search g group: project r rename x stop h hide a archive del delete ctrl+c quit + ? help esc back enter open n new w new worktree