From 8dc2b03c033ee264ccf03a35b2b27729398d82de Mon Sep 17 00:00:00 2001 From: Vinicius Motta Date: Mon, 11 Aug 2025 14:08:53 -0300 Subject: [PATCH] feat(tui): show detailed patch diffs in approval widget Adds create_diff_details to display --- codex-rs/tui/src/chatwidget.rs | 5 +- codex-rs/tui/src/history_cell.rs | 48 +++++++++++++++++-- .../tui/src/onboarding/trust_directory.rs | 7 +++ codex-rs/tui/src/user_approval_widget.rs | 8 ++-- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 344f025842..6eb4478e7d 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -426,12 +426,13 @@ impl ChatWidget<'_> { // ------------------------------------------------------------------ self.add_to_history(HistoryCell::new_patch_event( PatchEventType::ApprovalRequest, - changes, + &changes, )); // Now surface the approval request in the BottomPane as before. let request = ApprovalRequest::ApplyPatch { id, + changes, reason, grant_root, }; @@ -466,7 +467,7 @@ impl ChatWidget<'_> { }) => { self.add_to_history(HistoryCell::new_patch_event( PatchEventType::ApplyBegin { auto_approved }, - changes, + &changes, )); } EventMsg::PatchApplyEnd(event) => { diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 43144a648a..616c0bf0ac 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -793,7 +793,7 @@ impl HistoryCell { /// "A path/to/file.rs"). pub(crate) fn new_patch_event( event_type: PatchEventType, - changes: HashMap, + changes: &HashMap, ) -> Self { let title = match event_type { PatchEventType::ApprovalRequest => "proposed patch", @@ -854,7 +854,7 @@ impl WidgetRef for &HistoryCell { } } -fn create_diff_summary(title: &str, changes: HashMap) -> Vec> { +fn create_diff_summary(title: &str, changes: &HashMap) -> Vec> { let mut files: Vec = Vec::new(); // Count additions/deletions from a unified diff body @@ -889,7 +889,7 @@ fn create_diff_summary(title: &str, changes: HashMap) -> Ve } }; - for (path, change) in &changes { + for (path, change) in changes { use codex_core::protocol::FileChange::*; match change { Add { content } => { @@ -988,6 +988,48 @@ fn create_diff_summary(title: &str, changes: HashMap) -> Ve out } +pub(crate) fn create_diff_details( + changes: &std::collections::HashMap, +) -> Vec> { + use ratatui::style::Stylize as _; + let mut out: Vec> = Vec::new(); + for (path, change) in changes { + match change { + FileChange::Update { unified_diff, .. } => { + out.push(RtLine::from(path.display().to_string()).bold()); + for l in unified_diff.lines() { + if l.starts_with("+++") || l.starts_with("---") || l.starts_with("@@") { + continue; + } + let styled = if let Some(first) = l.as_bytes().first() { + match first { + b'+' => RtLine::from(l.to_string()).green(), + b'-' => RtLine::from(l.to_string()).red(), + _ => RtLine::from(l.to_string()).gray(), + } + } else { + RtLine::from(l.to_string()) + }; + out.push(styled); + } + out.push(RtLine::from("")); + } + FileChange::Add { content } => { + out.push(RtLine::from(format!("{} (new)", path.display())).bold()); + for l in content.lines() { + out.push(RtLine::from(format!("+{}", l)).green()); + } + out.push(RtLine::from("")); + } + FileChange::Delete => { + out.push(RtLine::from(format!("{} (deleted)", path.display())).red()); + out.push(RtLine::from("")); + } + } + } + out +} + fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> { let args_str = invocation .arguments diff --git a/codex-rs/tui/src/onboarding/trust_directory.rs b/codex-rs/tui/src/onboarding/trust_directory.rs index 3be9bac1ac..d24b585df8 100644 --- a/codex-rs/tui/src/onboarding/trust_directory.rs +++ b/codex-rs/tui/src/onboarding/trust_directory.rs @@ -174,6 +174,13 @@ impl TrustDirectoryWidget { fn handle_dont_trust(&mut self) { self.highlighted = TrustDirectorySelection::DontTrust; + // Update in-memory chat config for this session to require approval + // of edits and commands in untrusted workspaces. + if let Ok(mut args) = self.chat_widget_args.lock() { + args.config.approval_policy = AskForApproval::UnlessTrusted; + args.config.sandbox_policy = SandboxPolicy::new_read_only_policy(); + } + self.selection = Some(TrustDirectorySelection::DontTrust); } } diff --git a/codex-rs/tui/src/user_approval_widget.rs b/codex-rs/tui/src/user_approval_widget.rs index 966b8d68f9..4de7e8d050 100644 --- a/codex-rs/tui/src/user_approval_widget.rs +++ b/codex-rs/tui/src/user_approval_widget.rs @@ -41,6 +41,7 @@ pub(crate) enum ApprovalRequest { }, ApplyPatch { id: String, + changes: std::collections::HashMap, reason: Option, grant_root: Option, }, @@ -135,9 +136,7 @@ impl UserApprovalWidget<'_> { } Paragraph::new(contents).wrap(Wrap { trim: false }) } - ApprovalRequest::ApplyPatch { - reason, grant_root, .. - } => { + ApprovalRequest::ApplyPatch { changes, reason, grant_root, .. } => { let mut contents: Vec = vec![]; if let Some(r) = reason { @@ -153,6 +152,9 @@ impl UserApprovalWidget<'_> { contents.push(Line::from("")); } + let details = crate::history_cell::create_diff_details(changes); + contents.extend(details); + Paragraph::new(contents).wrap(Wrap { trim: false }) } };