fix(tui): clean up pending steer preview wrapping (#13642)

## Summary
- render pending steer previews with a single `pending steer:` prefix
instead of repeating it for each source line
- reuse the same truncation path for pending steers and queued drafts so
multiline previews behave consistently
- add snapshot coverage for the multiline pending steer case

Before
<img width="969" height="219" alt="Screenshot 2026-03-05 at 3 55 11 PM"
src="https://github.com/user-attachments/assets/b062c9c8-43d3-4a52-98e0-3c7643d1697b"
/>

After
<img width="965" height="203" alt="Screenshot 2026-03-05 at 3 56 08 PM"
src="https://github.com/user-attachments/assets/40935863-55b3-444f-9e14-1ac63126b2e1"
/>

## Codex author
`codex resume 019cc054-385e-79a3-bb85-ec9499623bd8`

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Charley Cunningham
2026-03-05 16:51:40 -08:00
committed by GitHub
parent 629cb15bc6
commit e15e191ff7
2 changed files with 62 additions and 19 deletions

View File

@@ -25,6 +25,8 @@ pub(crate) struct PendingInputPreview {
edit_binding: key_hint::KeyBinding,
}
const PREVIEW_LINE_LIMIT: usize = 3;
impl PendingInputPreview {
pub(crate) fn new() -> Self {
Self {
@@ -41,6 +43,18 @@ impl PendingInputPreview {
self.edit_binding = binding;
}
fn push_truncated_preview_lines(
lines: &mut Vec<Line<'static>>,
wrapped: Vec<Line<'static>>,
overflow_line: Line<'static>,
) {
let wrapped_len = wrapped.len();
lines.extend(wrapped.into_iter().take(PREVIEW_LINE_LIMIT));
if wrapped_len > PREVIEW_LINE_LIMIT {
lines.push(overflow_line);
}
}
fn as_renderable(&self, width: u16) -> Box<dyn Renderable> {
if (self.pending_steers.is_empty() && self.queued_messages.is_empty()) || width < 4 {
return Box::new(());
@@ -50,36 +64,26 @@ impl PendingInputPreview {
for steer in &self.pending_steers {
let wrapped = adaptive_wrap_lines(
steer
.lines()
.map(|line| format!("pending steer: {line}").dim()),
steer.lines().map(|line| Line::from(line.dim())),
RtOptions::new(width as usize)
.initial_indent(Line::from(" ! ".dim()))
.initial_indent(Line::from(" ! pending steer: ".dim()))
.subsequent_indent(Line::from(" ")),
);
let len = wrapped.len();
for line in wrapped.into_iter().take(3) {
lines.push(line);
}
if len > 3 {
lines.push(Line::from("".dim()));
}
Self::push_truncated_preview_lines(&mut lines, wrapped, Line::from("".dim()));
}
for message in &self.queued_messages {
let wrapped = adaptive_wrap_lines(
message.lines().map(|line| line.dim().italic()),
message.lines().map(|line| Line::from(line.dim().italic())),
RtOptions::new(width as usize)
.initial_indent(Line::from("".dim()))
.subsequent_indent(Line::from(" ")),
);
let len = wrapped.len();
for line in wrapped.into_iter().take(3) {
lines.push(line);
}
if len > 3 {
lines.push(Line::from("".dim().italic()));
}
Self::push_truncated_preview_lines(
&mut lines,
wrapped,
Line::from("".dim().italic()),
);
}
if !self.queued_messages.is_empty() {
@@ -266,4 +270,20 @@ mod tests {
format!("{buf:?}")
);
}
#[test]
fn render_multiline_pending_steer_uses_single_prefix_and_truncates() {
let mut queue = PendingInputPreview::new();
queue
.pending_steers
.push("First line\nSecond line\nThird line\nFourth line".to_string());
let width = 48;
let height = queue.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
queue.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!(
"render_multiline_pending_steer_uses_single_prefix_and_truncates",
format!("{buf:?}")
);
}
}

View File

@@ -0,0 +1,23 @@
---
source: tui/src/bottom_pane/pending_input_preview.rs
expression: "format!(\"{buf:?}\")"
---
Buffer {
area: Rect { x: 0, y: 0, width: 48, height: 4 },
content: [
" ! pending steer: First line ",
" Second line ",
" Third line ",
" … ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 29, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 4, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 15, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 4, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 14, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 5, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}