diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index fcafc7ddcf..5dc0e29e44 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2626,6 +2626,7 @@ impl App { self.sync_tui_theme_selection(name); } Err(err) => { + self.restore_runtime_theme_from_config(); tracing::error!(error = %err, "failed to persist theme selection"); self.chat_widget .add_error_message(format!("Failed to save theme: {err}")); @@ -2815,6 +2816,27 @@ impl App { self.chat_widget.set_tui_theme(Some(name)); } + fn restore_runtime_theme_from_config(&self) { + if let Some(name) = self.config.tui_theme.as_deref() + && let Some(theme) = + crate::render::highlight::resolve_theme_by_name(name, Some(&self.config.codex_home)) + { + crate::render::highlight::set_syntax_theme(theme); + return; + } + + let auto_theme_name = match crate::terminal_palette::default_bg() { + Some(bg) if crate::color::is_light(bg) => "catppuccin-latte", + _ => "catppuccin-mocha", + }; + if let Some(theme) = crate::render::highlight::resolve_theme_by_name( + auto_theme_name, + Some(&self.config.codex_home), + ) { + crate::render::highlight::set_syntax_theme(theme); + } + } + fn personality_label(personality: Personality) -> &'static str { match personality { Personality::None => "None", diff --git a/codex-rs/tui/src/bottom_pane/list_selection_view.rs b/codex-rs/tui/src/bottom_pane/list_selection_view.rs index 6f62a048f8..1411c860fe 100644 --- a/codex-rs/tui/src/bottom_pane/list_selection_view.rs +++ b/codex-rs/tui/src/bottom_pane/list_selection_view.rs @@ -407,6 +407,9 @@ impl ListSelectionView { self.complete = true; } } else if selected_item.is_none() { + if let Some(cb) = &self.on_cancel { + cb(&self.app_event_tx); + } self.complete = true; } } @@ -1145,6 +1148,37 @@ mod tests { ); } + #[test] + fn enter_with_no_matches_triggers_cancel_callback() { + let (tx_raw, mut rx) = unbounded_channel::(); + let tx = AppEventSender::new(tx_raw); + let mut view = ListSelectionView::new( + SelectionViewParams { + items: vec![SelectionItem { + name: "Read Only".to_string(), + dismiss_on_select: true, + ..Default::default() + }], + is_searchable: true, + on_cancel: Some(Box::new(|tx: &_| { + tx.send(AppEvent::OpenApprovalsPopup); + })), + ..Default::default() + }, + tx, + ); + view.set_search_query("no-matches".to_string()); + + view.handle_key_event(KeyEvent::from(KeyCode::Enter)); + + assert!(view.is_complete()); + match rx.try_recv() { + Ok(AppEvent::OpenApprovalsPopup) => {} + Ok(other) => panic!("expected OpenApprovalsPopup cancel event, got {other:?}"), + Err(err) => panic!("expected cancel callback event, got {err}"), + } + } + #[test] fn wraps_long_option_without_overflowing_columns() { let (tx_raw, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui/src/render/highlight.rs b/codex-rs/tui/src/render/highlight.rs index 88770620cc..9691a1f444 100644 --- a/codex-rs/tui/src/render/highlight.rs +++ b/codex-rs/tui/src/render/highlight.rs @@ -232,7 +232,8 @@ pub(crate) fn list_available_themes(codex_home: Option<&Path>) -> Vec