diff --git a/crates/tui/src/tui/widgets/mod.rs b/crates/tui/src/tui/widgets/mod.rs index 0d2140cd0..15fce7acc 100644 --- a/crates/tui/src/tui/widgets/mod.rs +++ b/crates/tui/src/tui/widgets/mod.rs @@ -1384,6 +1384,17 @@ impl Renderable for ApprovalWidget<'_> { let options = approval_options_for(risk, locale); for (i, opt) in options.iter().enumerate() { + // Divider between the approve group (0-1) and the deny/abort + // group (2-3) so the two clusters read as distinct decisions and + // an approve is harder to misread as a deny. Sized to fit the + // minimum card inner width without wrapping. + if i == 2 { + lines.push(Line::from(vec![Span::styled( + format!(" {}", "─".repeat(28)), + Style::default().fg(palette::TEXT_MUTED), + )])); + } + let is_selected = i == self.view.selected(); let label_color = if opt.dangerous { palette_colors.accent @@ -1394,15 +1405,22 @@ impl Renderable for ApprovalWidget<'_> { let option_style = approval_option_style(is_selected, label_color); let shortcut_style = approval_option_style(is_selected, palette_colors.shortcut); - let spans = vec![ - Span::raw(" "), + // The selected row is already painted as a highlight strip by the + // styles above; give it a leading caret so the action Enter will + // fire is unmistakable, not signalled by the background alone. + let lead = if is_selected { + Span::styled("\u{25b8} ", approval_selected_style()) + } else { + Span::raw(" ") + }; + lines.push(Line::from(vec![ + lead, Span::styled( format!("[{}] ", opt.key_hint), shortcut_style.add_modifier(Modifier::BOLD), ), Span::styled(opt.label.to_string(), option_style), - ]; - lines.push(Line::from(spans)); + ])); } // Footer: Enter commits the highlighted row; y/a/d remain direct @@ -4574,6 +4592,33 @@ mod tests { ); } + #[test] + fn approval_card_shows_group_divider_and_selected_caret() { + let request = crate::tui::approval::ApprovalRequest::new( + "approval-1", + "exec_shell", + "Run git commit", + &serde_json::json!({ "command": "git commit -m fix" }), + "exec_shell:git commit", + ); + let view = crate::tui::approval::ApprovalView::new(request.clone()); + let widget = ApprovalWidget::new(&request, &view); + let area = Rect::new(0, 0, 100, 30); + let mut buf = Buffer::empty(area); + + widget.render(area, &mut buf); + let rendered = buffer_text(&buf, area); + + assert!( + rendered.contains('\u{25b8}'), + "selected row should show a caret:\n{rendered}" + ); + assert!( + rendered.contains('\u{2500}'), + "approve and deny groups should be split by a divider:\n{rendered}" + ); + } + #[test] fn approval_shell_command_detects_printf_write_file_preview() { let request = crate::tui::approval::ApprovalRequest::new(