From 567821305831fc506efdecb9c39cdc8ddbddd6fe Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sun, 4 Jan 2026 22:05:08 -0800 Subject: [PATCH] fix(tui2): render copy pill at viewport bottom (#8716) When the selection ends on the last visible row, the copy affordance had no space below and never rendered. Fall back to placing it above (or on the same row for 1-row viewports) and add a regression test. --- codex-rs/tui2/src/transcript_copy_ui.rs | 34 +++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/codex-rs/tui2/src/transcript_copy_ui.rs b/codex-rs/tui2/src/transcript_copy_ui.rs index 6554864e0..6ec76a1de 100644 --- a/codex-rs/tui2/src/transcript_copy_ui.rs +++ b/codex-rs/tui2/src/transcript_copy_ui.rs @@ -249,8 +249,16 @@ impl TranscriptCopyUi { let Some((y, to_x)) = last_visible_segment else { return; }; - // Place the pill on the row below the last visible selection segment. - let Some(y) = y.checked_add(1).filter(|y| *y < area.bottom()) else { + // Prefer placing the pill on the row below the last visible selection segment. If the + // selection ends on the last visible row, fall back to placing it above (or, if the view + // is only one row tall, on the same row). + let Some(y) = y + .checked_add(1) + .filter(|y| *y < area.bottom()) + .or_else(|| y.checked_sub(1).filter(|y| *y >= area.y)) + .or(Some(y)) + .filter(|y| *y < area.bottom()) + else { return; }; @@ -329,4 +337,26 @@ mod tests { assert!(!rendered.contains("ctrl + shift + c")); assert!(ui.affordance_rect.is_some()); } + + #[test] + fn pill_renders_when_selection_on_last_row() { + let area = Rect::new(0, 0, 60, 3); + let mut buf = Buffer::empty(area); + for y in 0..area.height { + for x in 2..area.width.saturating_sub(1) { + buf[(x, y)].set_symbol("X"); + } + } + + let mut ui = TranscriptCopyUi::new_with_shortcut(CopySelectionShortcut::CtrlShiftC); + ui.render_copy_pill(area, &mut buf, (2, 2), (2, 6), 0, 3); + + let rendered = buf_to_string(&buf, area); + assert!(rendered.contains("copy")); + assert!(rendered.contains("ctrl + shift + c")); + + let rect = ui.affordance_rect.expect("expected pill to render"); + assert_eq!(rect.y, 1); + assert!(ui.hit_test(rect.x, rect.y)); + } }