feat(tui): enhance approval modal button prominence with visual grouping#3437
feat(tui): enhance approval modal button prominence with visual grouping#3437donglovejava wants to merge 5 commits into
Conversation
The sidebar was only showing when terminal width >= 100 columns, which is too restrictive for many terminal setups. Reduced the minimum width to 60 columns to make the sidebar visible in more common terminal configurations. This fixes the issue where the sidebar would not appear in v0.8.62+ when using typical terminal sizes that are narrower than 100 columns.
Nightly builds: - Add artifact existence check to skip redundant builds for the same commit - Add build retry logic (up to 3 attempts) for transient failures - Add nightly-complete summary job for branch protection rules - Improve concurrency group to use ref_name instead of full ref Auto-tag idempotency: - Add semver validation for workspace version - Add annotated tags with release metadata - Add push retry logic with exponential backoff - Fail fast if version consistency check fails - Add concurrency control to prevent race conditions Addresses v0.8.64 reliability concerns for nightly builds and auto-tagging.
Update SECURITY.md email address from the legacy deepseek-tui.com domain to codewhale.com to match the project rebranding. Addresses v0.8.64 security hardening requirements.
- Add visual separator between approve (0-1) and deny/abort (2-3) groups - Render selected option as a solid button row with background strip - Add 'EXECUTE' indicator on the selected row for clear action feedback - Maintain keyboard shortcut emphasis with BOLD modifier Improves UX by making the decision surface read as two distinct choice clusters rather than a flat list, and gives the selected option a clear button-like appearance.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Code Review
This pull request introduces several updates, including adding new native tools to the tool catalog, reducing the minimum sidebar width, and enhancing the 'ApprovalWidget' with visual separators and selection highlights. It also includes several documentation files and a helper script ('fix_engine.py'). The code review feedback suggests improving the 'ApprovalWidget' layout by dynamically calculating the separator line width to prevent wrapping, padding the selected option background to span the full card width, and removing the unused 'locale_separator' helper. Additionally, it recommends replacing the hardcoded absolute Windows path in 'fix_engine.py' with a relative path for portability.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| let separator = locale_separator(locale); | ||
|
|
||
| for (i, opt) in options.iter().enumerate() { | ||
| // Insert a visual separator between the approve group (0-1) | ||
| // and the deny/abort group (2-3) so the decision surface | ||
| // reads as two distinct choice clusters rather than a flat | ||
| // list. | ||
| if i == 2 { | ||
| lines.push(Line::from(vec![Span::styled( | ||
| separator, | ||
| Style::default().fg(palette::TEXT_MUTED), | ||
| )])); | ||
| } |
There was a problem hiding this comment.
The separator line is currently hardcoded to 40 characters. However, the approval card width is dynamic and can be as small as APPROVAL_CARD_MIN_WIDTH (40), which results in a content width of 36. In such cases, the 40-character separator line will wrap to the next line, breaking the visual layout.
We can dynamically construct the separator line using card_area.width to ensure it perfectly spans the available content width without wrapping.
| let separator = locale_separator(locale); | |
| for (i, opt) in options.iter().enumerate() { | |
| // Insert a visual separator between the approve group (0-1) | |
| // and the deny/abort group (2-3) so the decision surface | |
| // reads as two distinct choice clusters rather than a flat | |
| // list. | |
| if i == 2 { | |
| lines.push(Line::from(vec![Span::styled( | |
| separator, | |
| Style::default().fg(palette::TEXT_MUTED), | |
| )])); | |
| } | |
| let content_width = card_area.width.saturating_sub(4) as usize; | |
| for (i, opt) in options.iter().enumerate() { | |
| // Insert a visual separator between the approve group (0-1) | |
| // and the deny/abort group (2-3) so the decision surface | |
| // reads as two distinct choice clusters rather than a flat | |
| // list. | |
| if i == 2 { | |
| let sep_chars = "─".repeat(content_width.saturating_sub(2)); | |
| lines.push(Line::from(vec![Span::styled( | |
| format!(" {sep_chars}"), | |
| Style::default().fg(palette::TEXT_MUTED), | |
| )])); | |
| } |
| if is_selected { | ||
| // Selected option: render as a solid button row with a | ||
| // distinct background strip so the user can instantly see | ||
| // which action will fire on Enter. | ||
| lines.push(Line::from(vec![ | ||
| Span::styled(" ", Style::default().bg(palette::SELECTION_BG)), | ||
| Span::styled( | ||
| format!("[{}] ", opt.key_hint), | ||
| shortcut_style.add_modifier(Modifier::BOLD).bg(palette::SELECTION_BG), | ||
| ), | ||
| Span::styled( | ||
| opt.label.to_string(), | ||
| option_style.add_modifier(Modifier::BOLD).bg(palette::SELECTION_BG), | ||
| ), | ||
| Span::styled(" ", Style::default().bg(palette::SELECTION_BG)), | ||
| Span::styled( | ||
| selected_indicator(locale), | ||
| Style::default() | ||
| .fg(palette::SELECTION_TEXT) | ||
| .bg(palette::SELECTION_BG) | ||
| .add_modifier(Modifier::BOLD), | ||
| ), | ||
| ])); |
There was a problem hiding this comment.
The selected option background is currently applied only to the individual text spans. This means the background highlight will only cover the text itself, rather than forming a solid, full-width button row as described in the PR. Additionally, the ◄ EXECUTE indicator is placed immediately after the label instead of being right-aligned to the edge of the card.
We can resolve both issues by calculating the remaining width of the row and inserting a padding span of spaces with the SELECTION_BG background between the label and the indicator. This right-aligns the indicator and extends the background highlight across the entire width of the card.
if is_selected {
// Selected option: render as a solid button row with a
// distinct background strip so the user can instantly see
// which action will fire on Enter.
let shortcut_text = format!("[{}] ", opt.key_hint);
let label_text = opt.label.to_string();
let indicator_text = selected_indicator(locale);
let text_width = 2 + shortcut_text.width() + label_text.width() + indicator_text.width();
let padding_width = content_width.saturating_sub(text_width);
lines.push(Line::from(vec![
Span::styled(" ", Style::default().bg(palette::SELECTION_BG)),
Span::styled(
shortcut_text,
shortcut_style.add_modifier(Modifier::BOLD).bg(palette::SELECTION_BG),
),
Span::styled(
label_text,
option_style.add_modifier(Modifier::BOLD).bg(palette::SELECTION_BG),
),
Span::styled(
" ".repeat(padding_width),
Style::default().bg(palette::SELECTION_BG),
),
Span::styled(
indicator_text,
Style::default()
.fg(palette::SELECTION_TEXT)
.bg(palette::SELECTION_BG)
.add_modifier(Modifier::BOLD),
),
]));
} else {| fn locale_separator(locale: Locale) -> &'static str { | ||
| match locale { | ||
| Locale::ZhHans => " ────────────────────────────────────────", | ||
| _ => " ────────────────────────────────────────", | ||
| } | ||
| } |
| @@ -0,0 +1,39 @@ | |||
| import re | |||
|
|
|||
| file_path = r'C:\project\F_project1\CodeWhale\crates\tui\src\core\engine.rs' | |||
There was a problem hiding this comment.
The script uses a hardcoded absolute Windows path (C:\project\F_project1\...). This will fail on other developers' machines or in CI environments.
Using a relative path makes the script portable across different environments and operating systems.
| file_path = r'C:\project\F_project1\CodeWhale\crates\tui\src\core\engine.rs' | |
| file_path = 'crates/tui/src/core/engine.rs' |
|
I love this!! I've been fighting getting this down so your assistance is so appreciated. |
Summary
This PR enhances the approval modal UX by making the approve/deny buttons more visually prominent and easier to distinguish.
Problem
The approval modal displayed all four options (Approve once, Approve always, Deny, Abort) as a flat list with no visual grouping. Users had to read each line carefully to distinguish between approve and deny actions. The selected option was only indicated by a color change, which could be missed at a glance.
Solution
Visual Grouping
Button-Like Selected Row
SELECTION_BG.◄ EXECUTEindicator on the right side of the selected row, giving clear feedback about which action will fire on Enter.Keyboard Shortcut Emphasis
Before vs After
Before:
After:
Selected row now shows:
Impact
Generated with Claude Code