From 742feaf40f5fd03796afcf17f8f500c0706cc78c Mon Sep 17 00:00:00 2001
From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com>
Date: Thu, 4 Sep 2025 16:54:53 -0700
Subject: [PATCH] tui: fix approval dialog for large commands (#3087)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
#### Summary
- Emit a “Proposed Command” history cell when an ExecApprovalRequest
arrives (parity with proposed patches).
- Simplify the approval dialog: show only the reason/instructions; move
the command preview into history.
- Make approval/abort decision history concise:
- Single line snippet; if multiline, show first line + " ...".
- Truncate to 80 graphemes with ellipsis for very long commands.
#### Details
- History
- Add `new_proposed_command` to render a header and indented command
preview.
- Use shared `prefix_lines` helper for first/subsequent line prefixes.
- Approval UI
- `UserApprovalWidget` no longer renders the command in the modal; shows
optional `reason` text only.
- Decision history renders an inline, dimmed snippet per rules above.
- Tests (snapshot-based)
- Proposed/decision flow for short command.
- Proposed multi-line + aborted decision snippet with “ ...”.
- Very long one-line command -> truncated snippet with “…”.
- Updated existing exec approval snapshots and test reasons.
after approving:
rejection:
big command:
https://github.com/user-attachments/assets/2dd976e5-799f-4af7-9682-a046e66cc494
---
codex-rs/tui/src/chatwidget.rs | 2 +
...hatwidget__tests__approval_modal_exec.snap | 4 +-
..._tests__approval_modal_exec_no_reason.snap | 2 -
...pproval_history_decision_aborted_long.snap | 5 +
...al_history_decision_aborted_multiline.snap | 6 +
...roval_history_decision_approved_short.snap | 6 +
...c_approval_history_proposed_multiline.snap | 7 +
..._exec_approval_history_proposed_short.snap | 6 +
...sts__status_widget_and_approval_modal.snap | 4 +-
codex-rs/tui/src/chatwidget/tests.rs | 106 ++++++++++++++-
codex-rs/tui/src/history_cell.rs | 49 ++++---
codex-rs/tui/src/render/line_utils.rs | 23 ++++
codex-rs/tui/src/user_approval_widget.rs | 126 +++++++-----------
13 files changed, 230 insertions(+), 116 deletions(-)
create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_long.snap
create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_multiline.snap
create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_approved_short.snap
create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_multiline.snap
create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_short.snap
diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs
index e8381c6f..7acb802c 100644
--- a/codex-rs/tui/src/chatwidget.rs
+++ b/codex-rs/tui/src/chatwidget.rs
@@ -524,6 +524,8 @@ impl ChatWidget {
pub(crate) fn handle_exec_approval_now(&mut self, id: String, ev: ExecApprovalRequestEvent) {
self.flush_answer_stream_with_separator();
+ // Emit the proposed command into history (like proposed patches)
+ self.add_to_history(history_cell::new_proposed_command(&ev.command));
let request = ApprovalRequest::Exec {
id,
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec.snap
index e190ef4d..f306381f 100644
--- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec.snap
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec.snap
@@ -4,9 +4,7 @@ assertion_line: 728
expression: terminal.backend()
---
" "
-"? Codex wants to run echo hello world "
-" "
-"Model wants to run a command "
+"this is a test reason such as one that would be produced by the model "
" "
"▌Allow command? "
"▌ Yes Always No, provide feedback "
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec_no_reason.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec_no_reason.snap
index f8aee639..fb1ecce7 100644
--- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec_no_reason.snap
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approval_modal_exec_no_reason.snap
@@ -3,8 +3,6 @@ source: tui/src/chatwidget/tests.rs
expression: terminal.backend()
---
" "
-"? Codex wants to run echo hello world "
-" "
"▌Allow command? "
"▌ Yes Always No, provide feedback "
"▌ Approve and run the command "
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_long.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_long.snap
new file mode 100644
index 00000000..524e0968
--- /dev/null
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_long.snap
@@ -0,0 +1,5 @@
+---
+source: tui/src/chatwidget/tests.rs
+expression: lines_to_single_string(&aborted_long)
+---
+✗ You canceled the request to run echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_multiline.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_multiline.snap
new file mode 100644
index 00000000..d35cb175
--- /dev/null
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_aborted_multiline.snap
@@ -0,0 +1,6 @@
+---
+source: tui/src/chatwidget/tests.rs
+expression: lines_to_single_string(&aborted_multi)
+---
+✗ You canceled the request to run echo line1 ...
+
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_approved_short.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_approved_short.snap
new file mode 100644
index 00000000..1b18a23d
--- /dev/null
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_decision_approved_short.snap
@@ -0,0 +1,6 @@
+---
+source: tui/src/chatwidget/tests.rs
+expression: lines_to_single_string(&decision)
+---
+✔ You approved codex to run echo hello world this time
+
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_multiline.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_multiline.snap
new file mode 100644
index 00000000..119965df
--- /dev/null
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_multiline.snap
@@ -0,0 +1,7 @@
+---
+source: tui/src/chatwidget/tests.rs
+expression: lines_to_single_string(&proposed_multi)
+---
+• Proposed Command
+ └ echo line1
+ echo line2
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_short.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_short.snap
new file mode 100644
index 00000000..dfc52c01
--- /dev/null
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_history_proposed_short.snap
@@ -0,0 +1,6 @@
+---
+source: tui/src/chatwidget/tests.rs
+expression: lines_to_single_string(&proposed)
+---
+• Proposed Command
+ └ echo hello world
diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_and_approval_modal.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_and_approval_modal.snap
index 645ddb8e..63fb213e 100644
--- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_and_approval_modal.snap
+++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_widget_and_approval_modal.snap
@@ -4,9 +4,7 @@ assertion_line: 921
expression: terminal.backend()
---
" "
-"? Codex wants to run echo 'hello world' "
-" "
-"Codex wants to run a command "
+"this is a test reason such as one that would be produced by the model "
" "
"▌Allow command? "
"▌ Yes Always No, provide feedback "
diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs
index 287ce706..555ce50d 100644
--- a/codex-rs/tui/src/chatwidget/tests.rs
+++ b/codex-rs/tui/src/chatwidget/tests.rs
@@ -265,6 +265,104 @@ fn lines_to_single_string(lines: &[ratatui::text::Line<'static>]) -> String {
s
}
+// (removed experimental resize snapshot test)
+
+#[test]
+fn exec_approval_emits_proposed_command_and_decision_history() {
+ let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
+
+ // Trigger an exec approval request with a short, single-line command
+ let ev = ExecApprovalRequestEvent {
+ call_id: "call-short".into(),
+ command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
+ cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
+ reason: Some(
+ "this is a test reason such as one that would be produced by the model".into(),
+ ),
+ };
+ chat.handle_codex_event(Event {
+ id: "sub-short".into(),
+ msg: EventMsg::ExecApprovalRequest(ev),
+ });
+
+ // Snapshot the Proposed Command cell emitted into history
+ let proposed = drain_insert_history(&mut rx)
+ .pop()
+ .expect("expected proposed command cell");
+ assert_snapshot!(
+ "exec_approval_history_proposed_short",
+ lines_to_single_string(&proposed)
+ );
+
+ // Approve via keyboard and verify a concise decision history line is added
+ chat.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
+ let decision = drain_insert_history(&mut rx)
+ .pop()
+ .expect("expected decision cell in history");
+ assert_snapshot!(
+ "exec_approval_history_decision_approved_short",
+ lines_to_single_string(&decision)
+ );
+}
+
+#[test]
+fn exec_approval_decision_truncates_multiline_and_long_commands() {
+ let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
+
+ // Multiline command: should render proposed command fully in history with prefixes
+ let ev_multi = ExecApprovalRequestEvent {
+ call_id: "call-multi".into(),
+ command: vec!["bash".into(), "-lc".into(), "echo line1\necho line2".into()],
+ cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
+ reason: Some(
+ "this is a test reason such as one that would be produced by the model".into(),
+ ),
+ };
+ chat.handle_codex_event(Event {
+ id: "sub-multi".into(),
+ msg: EventMsg::ExecApprovalRequest(ev_multi),
+ });
+ let proposed_multi = drain_insert_history(&mut rx)
+ .pop()
+ .expect("expected proposed multiline command cell");
+ assert_snapshot!(
+ "exec_approval_history_proposed_multiline",
+ lines_to_single_string(&proposed_multi)
+ );
+
+ // Deny via keyboard; decision snippet should be single-line and elided with " ..."
+ chat.handle_key_event(KeyEvent::new(KeyCode::Char('n'), KeyModifiers::NONE));
+ let aborted_multi = drain_insert_history(&mut rx)
+ .pop()
+ .expect("expected aborted decision cell (multiline)");
+ assert_snapshot!(
+ "exec_approval_history_decision_aborted_multiline",
+ lines_to_single_string(&aborted_multi)
+ );
+
+ // Very long single-line command: decision snippet should be truncated <= 80 chars with trailing ...
+ let long = format!("echo {}", "a".repeat(200));
+ let ev_long = ExecApprovalRequestEvent {
+ call_id: "call-long".into(),
+ command: vec!["bash".into(), "-lc".into(), long.clone()],
+ cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
+ reason: None,
+ };
+ chat.handle_codex_event(Event {
+ id: "sub-long".into(),
+ msg: EventMsg::ExecApprovalRequest(ev_long),
+ });
+ drain_insert_history(&mut rx); // proposed cell not needed for this assertion
+ chat.handle_key_event(KeyEvent::new(KeyCode::Char('n'), KeyModifiers::NONE));
+ let aborted_long = drain_insert_history(&mut rx)
+ .pop()
+ .expect("expected aborted decision cell (long)");
+ assert_snapshot!(
+ "exec_approval_history_decision_aborted_long",
+ lines_to_single_string(&aborted_long)
+ );
+}
+
// --- Small helpers to tersely drive exec begin/end and snapshot active cell ---
fn begin_exec(chat: &mut ChatWidget, call_id: &str, raw_cmd: &str) {
// Build the full command vec and parse it using core's parser,
@@ -714,7 +812,9 @@ fn approval_modal_exec_snapshot() {
call_id: "call-approve-cmd".into(),
command: vec!["bash".into(), "-lc".into(), "echo hello world".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
- reason: Some("Model wants to run a command".into()),
+ reason: Some(
+ "this is a test reason such as one that would be produced by the model".into(),
+ ),
};
chat.handle_codex_event(Event {
id: "sub-approve".into(),
@@ -907,7 +1007,9 @@ fn status_widget_and_approval_modal_snapshot() {
call_id: "call-approve-exec".into(),
command: vec!["echo".into(), "hello world".into()],
cwd: std::path::PathBuf::from("/tmp"),
- reason: Some("Codex wants to run a command".into()),
+ reason: Some(
+ "this is a test reason such as one that would be produced by the model".into(),
+ ),
};
chat.handle_codex_event(Event {
id: "sub-approve-exec".into(),
diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs
index 798a87dc..ce048f48 100644
--- a/codex-rs/tui/src/history_cell.rs
+++ b/codex-rs/tui/src/history_cell.rs
@@ -2,6 +2,7 @@ use crate::diff_render::create_diff_summary;
use crate::exec_command::relativize_to_home;
use crate::exec_command::strip_bash_lc_and_escape;
use crate::markdown::append_markdown;
+use crate::render::line_utils::prefix_lines;
use crate::slash_command::SlashCommand;
use crate::text_formatting::format_and_truncate_tool_result;
use base64::Engine;
@@ -1140,33 +1141,9 @@ impl HistoryCell for PlanUpdateCell {
.into_iter()
.map(|s| s.to_string().set_style(step_style).into())
.collect();
- prefix_lines(step_text, &box_str.into(), &" ".into())
+ prefix_lines(step_text, box_str.into(), " ".into())
};
- fn prefix_lines(
- lines: Vec>,
- initial_prefix: &Span<'static>,
- subsequent_prefix: &Span<'static>,
- ) -> Vec> {
- lines
- .into_iter()
- .enumerate()
- .map(|(i, l)| {
- Line::from(
- [
- vec![if i == 0 {
- initial_prefix.clone()
- } else {
- subsequent_prefix.clone()
- }],
- l.spans,
- ]
- .concat(),
- )
- })
- .collect()
- }
-
let mut lines: Vec> = vec![];
lines.push(vec!["• ".into(), "Updated Plan".bold()].into());
@@ -1187,7 +1164,7 @@ impl HistoryCell for PlanUpdateCell {
indented_lines.extend(render_step(status, step));
}
}
- lines.extend(prefix_lines(indented_lines, &" └ ".into(), &" ".into()));
+ lines.extend(prefix_lines(indented_lines, " └ ".into(), " ".into()));
lines
}
@@ -1231,6 +1208,26 @@ pub(crate) fn new_patch_apply_failure(stderr: String) -> PlainHistoryCell {
PlainHistoryCell { lines }
}
+/// Create a new history cell for a proposed command approval.
+/// Renders a header and the command preview similar to how proposed patches
+/// show a header and summary.
+pub(crate) fn new_proposed_command(command: &[String]) -> PlainHistoryCell {
+ let cmd = strip_bash_lc_and_escape(command);
+
+ let mut lines: Vec> = Vec::new();
+ lines.push(Line::from(vec!["• ".into(), "Proposed Command".bold()]));
+
+ let cmd_lines: Vec> = cmd
+ .lines()
+ .map(|part| Line::from(part.to_string()))
+ .collect();
+ let initial_prefix: Span<'static> = " └ ".dim();
+ let subsequent_prefix: Span<'static> = " ".into();
+ lines.extend(prefix_lines(cmd_lines, initial_prefix, subsequent_prefix));
+
+ PlainHistoryCell { lines }
+}
+
pub(crate) fn new_reasoning_block(
full_reasoning_buffer: String,
config: &Config,
diff --git a/codex-rs/tui/src/render/line_utils.rs b/codex-rs/tui/src/render/line_utils.rs
index 1cc9d5c9..5a8e0906 100644
--- a/codex-rs/tui/src/render/line_utils.rs
+++ b/codex-rs/tui/src/render/line_utils.rs
@@ -34,3 +34,26 @@ pub fn is_blank_line_spaces_only(line: &Line<'_>) -> bool {
.iter()
.all(|s| s.content.is_empty() || s.content.chars().all(|c| c == ' '))
}
+
+/// Prefix each line with `initial_prefix` for the first line and
+/// `subsequent_prefix` for following lines. Returns a new Vec of owned lines.
+pub fn prefix_lines(
+ lines: Vec>,
+ initial_prefix: Span<'static>,
+ subsequent_prefix: Span<'static>,
+) -> Vec> {
+ lines
+ .into_iter()
+ .enumerate()
+ .map(|(i, l)| {
+ let mut spans = Vec::with_capacity(l.spans.len() + 1);
+ spans.push(if i == 0 {
+ initial_prefix.clone()
+ } else {
+ subsequent_prefix.clone()
+ });
+ spans.extend(l.spans);
+ Line::from(spans)
+ })
+ .collect()
+}
diff --git a/codex-rs/tui/src/user_approval_widget.rs b/codex-rs/tui/src/user_approval_widget.rs
index 9ea2c0bc..4f173955 100644
--- a/codex-rs/tui/src/user_approval_widget.rs
+++ b/codex-rs/tui/src/user_approval_widget.rs
@@ -30,6 +30,7 @@ use crate::app_event::AppEvent;
use crate::app_event_sender::AppEventSender;
use crate::exec_command::strip_bash_lc_and_escape;
use crate::history_cell;
+use crate::text_formatting::truncate_text;
/// Request coming from the agent that needs user approval.
pub(crate) enum ApprovalRequest {
@@ -110,45 +111,11 @@ pub(crate) struct UserApprovalWidget {
done: bool,
}
-fn to_command_display<'a>(
- first_line: Vec>,
- cmd: String,
- last_line: Vec>,
-) -> Vec> {
- let command_lines: Vec = cmd.lines().map(|line| line.to_string().dim()).collect();
-
- let mut lines: Vec> = vec![];
-
- let mut first_line = first_line.clone();
- if command_lines.len() == 1 {
- first_line.push(command_lines[0].clone());
- first_line.extend(last_line);
- } else {
- for line in command_lines {
- lines.push(vec![" ".into(), line].into());
- }
- let last_line = last_line.clone();
- lines.push(Line::from(last_line));
- }
- lines.insert(0, Line::from(first_line));
-
- lines
-}
-
impl UserApprovalWidget {
pub(crate) fn new(approval_request: ApprovalRequest, app_event_tx: AppEventSender) -> Self {
let confirmation_prompt = match &approval_request {
- ApprovalRequest::Exec {
- command, reason, ..
- } => {
- let cmd = strip_bash_lc_and_escape(command);
- let mut contents: Vec = to_command_display(
- vec!["? ".fg(Color::Cyan), "Codex wants to run ".bold()],
- cmd,
- vec![],
- );
-
- contents.push(Line::from(""));
+ ApprovalRequest::Exec { reason, .. } => {
+ let mut contents: Vec = vec![];
if let Some(reason) = reason {
contents.push(Line::from(reason.clone().italic()));
contents.push(Line::from(""));
@@ -258,62 +225,61 @@ impl UserApprovalWidget {
fn send_decision_with_feedback(&mut self, decision: ReviewDecision, feedback: String) {
match &self.approval_request {
ApprovalRequest::Exec { command, .. } => {
- let cmd = strip_bash_lc_and_escape(command);
- // TODO: move this rendering into history_cell.
- let mut lines: Vec> = vec![];
+ let full_cmd = strip_bash_lc_and_escape(command);
+ // Construct a concise, single-line summary of the command:
+ // - If multi-line, take the first line and append " ...".
+ // - Truncate to 80 graphemes.
+ let mut snippet = match full_cmd.split_once('\n') {
+ Some((first, _)) => format!("{first} ..."),
+ None => full_cmd.clone(),
+ };
+ // Enforce the 80 character length limit.
+ snippet = truncate_text(&snippet, 80);
- // Result line based on decision.
+ let mut result_spans: Vec> = Vec::new();
match decision {
ReviewDecision::Approved => {
- lines.extend(to_command_display(
- vec![
- "✔ ".fg(Color::Green),
- "You ".into(),
- "approved".bold(),
- " codex to run ".into(),
- ],
- cmd,
- vec![" this time".bold()],
- ));
+ result_spans.extend(vec![
+ "✔ ".fg(Color::Green),
+ "You ".into(),
+ "approved".bold(),
+ " codex to run ".into(),
+ snippet.clone().dim(),
+ " this time".bold(),
+ ]);
}
ReviewDecision::ApprovedForSession => {
- lines.extend(to_command_display(
- vec![
- "✔ ".fg(Color::Green),
- "You ".into(),
- "approved".bold(),
- " codex to run ".into(),
- ],
- cmd,
- vec![" every time this session".bold()],
- ));
+ result_spans.extend(vec![
+ "✔ ".fg(Color::Green),
+ "You ".into(),
+ "approved".bold(),
+ " codex to run ".into(),
+ snippet.clone().dim(),
+ " every time this session".bold(),
+ ]);
}
ReviewDecision::Denied => {
- lines.extend(to_command_display(
- vec![
- "✗ ".fg(Color::Red),
- "You ".into(),
- "did not approve".bold(),
- " codex to run ".into(),
- ],
- cmd,
- vec![],
- ));
+ result_spans.extend(vec![
+ "✗ ".fg(Color::Red),
+ "You ".into(),
+ "did not approve".bold(),
+ " codex to run ".into(),
+ snippet.clone().dim(),
+ ]);
}
ReviewDecision::Abort => {
- lines.extend(to_command_display(
- vec![
- "✗ ".fg(Color::Red),
- "You ".into(),
- "canceled".bold(),
- " the request to run ".into(),
- ],
- cmd,
- vec![],
- ));
+ result_spans.extend(vec![
+ "✗ ".fg(Color::Red),
+ "You ".into(),
+ "canceled".bold(),
+ " the request to run ".into(),
+ snippet.clone().dim(),
+ ]);
}
}
+ let mut lines: Vec> = vec![Line::from(result_spans)];
+
if !feedback.trim().is_empty() {
lines.push(Line::from("feedback:"));
for l in feedback.lines() {