From 6849549b49446fc3db89ecc13d6226ab971bec79 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 8 Jul 2026 08:57:25 -0400 Subject: [PATCH] Align empty branch list message with search (#31465) ## Summary Selection rows reserve their first two columns for the selection cursor and render into the menu surface's left gutter. The empty-state message reused that shifted row area even though it has no cursor prefix, so `no matches` appeared two columns to the left of the search field. This renders empty lists at the inset list origin while preserving the cursor gutter for non-empty rows. --- .../src/bottom_pane/list_selection_view.rs | 29 ++++++++++++++++++- ...ests__list_selection_empty_searchable.snap | 11 +++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_empty_searchable.snap 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 4455a6b670..75d11ae91f 100644 --- a/codex-rs/tui/src/bottom_pane/list_selection_view.rs +++ b/codex-rs/tui/src/bottom_pane/list_selection_view.rs @@ -1269,7 +1269,11 @@ impl Renderable for ListSelectionView { // -- List rows -- if list_area.height > 0 { let render_area = Rect { - x: list_area.x.saturating_sub(2), + x: if rows.is_empty() { + list_area.x + } else { + list_area.x.saturating_sub(2) + }, y: list_area.y, width: effective_rows_width.max(1), height: list_area.height, @@ -1715,6 +1719,29 @@ mod tests { ); } + #[test] + fn empty_searchable_list_aligns_message_with_search() { + let (tx_raw, _rx) = unbounded_channel::(); + let tx = AppEventSender::new(tx_raw); + let view = new_view( + SelectionViewParams { + title: Some("Select a base branch".to_string()), + footer_hint: Some(standard_popup_hint_line()), + is_searchable: true, + search_placeholder: Some("Type to search branches".to_string()), + ..Default::default() + }, + tx, + ); + + let rendered = render_lines_with_width(&view, /*width*/ 48) + .lines() + .map(str::trim_end) + .collect::>() + .join("\n"); + assert_snapshot!("list_selection_empty_searchable", rendered); + } + #[test] fn paste_appends_to_search_query_and_filters_items() { let (tx_raw, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_empty_searchable.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_empty_searchable.snap new file mode 100644 index 0000000000..a69e588ec5 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_empty_searchable.snap @@ -0,0 +1,11 @@ +--- +source: tui/src/bottom_pane/list_selection_view.rs +expression: rendered +--- + + Select a base branch + + Type to search branches + no matches + + Press enter to confirm or esc to go back