mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(tui/approvals): immediate UI cancel on deny; FIFO approval queue
- Add AppEvent::CancelRunningTask; handle in App loop to call ChatWidget cancel - Approval modal: send CancelRunningTask on Abort before forwarding ExecApproval - ChatWidget: expose cancel_running_task_from_approval() to trigger UI cleanup - Approval modal: process queued approvals FIFO (VecDeque) to preserve order Closes #49
This commit is contained in:
@@ -633,6 +633,11 @@ impl App<'_> {
|
||||
self.input_running.store(false, Ordering::Release);
|
||||
break 'main;
|
||||
}
|
||||
AppEvent::CancelRunningTask => {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.cancel_running_task_from_approval();
|
||||
}
|
||||
}
|
||||
// fallthrough handled by break
|
||||
AppEvent::CodexOp(op) => match &mut self.app_state {
|
||||
AppState::Chat { widget } => widget.submit_op(op),
|
||||
|
||||
@@ -140,5 +140,11 @@ pub(crate) enum AppEvent {
|
||||
/// Register an image placeholder inserted by the composer with its backing path
|
||||
/// so ChatWidget can resolve it to a LocalImage on submit.
|
||||
RegisterPastedImage { placeholder: String, path: PathBuf },
|
||||
|
||||
/// Immediately cancel any running task in the ChatWidget. This is used by
|
||||
/// the approval modal to reflect a user's Abort decision instantly in the UI
|
||||
/// (clear spinner/status, finalize running exec/tool cells) while the core
|
||||
/// continues its own abort/cleanup in parallel.
|
||||
CancelRunningTask,
|
||||
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@ use crate::user_approval_widget::UserApprovalWidget;
|
||||
use super::BottomPane;
|
||||
use super::BottomPaneView;
|
||||
use super::CancellationEvent;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Modal overlay asking the user to approve/deny a sequence of requests.
|
||||
pub(crate) struct ApprovalModalView<'a> {
|
||||
current: UserApprovalWidget<'a>,
|
||||
queue: Vec<ApprovalRequest>,
|
||||
queue: VecDeque<ApprovalRequest>,
|
||||
app_event_tx: AppEventSender,
|
||||
}
|
||||
|
||||
@@ -22,19 +23,19 @@ impl ApprovalModalView<'_> {
|
||||
pub fn new(request: ApprovalRequest, app_event_tx: AppEventSender) -> Self {
|
||||
Self {
|
||||
current: UserApprovalWidget::new(request, app_event_tx.clone()),
|
||||
queue: Vec::new(),
|
||||
queue: VecDeque::new(),
|
||||
app_event_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_request(&mut self, req: ApprovalRequest) {
|
||||
self.queue.push(req);
|
||||
self.queue.push_back(req);
|
||||
}
|
||||
|
||||
/// Advance to next request if the current one is finished.
|
||||
fn maybe_advance(&mut self) {
|
||||
if self.current.is_complete() {
|
||||
if let Some(req) = self.queue.pop() {
|
||||
if let Some(req) = self.queue.pop_front() {
|
||||
self.current = UserApprovalWidget::new(req, self.app_event_tx.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3660,6 +3660,13 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel the current running task from a non-keyboard context (e.g. approval modal).
|
||||
/// This bypasses modal key handling and invokes the same immediate UI cleanup path
|
||||
/// as pressing Ctrl-C/Esc while a task is running.
|
||||
pub(crate) fn cancel_running_task_from_approval(&mut self) {
|
||||
self.interrupt_running_task();
|
||||
}
|
||||
|
||||
pub(crate) fn insert_history_lines(&mut self, lines: Vec<ratatui::text::Line<'static>>) {
|
||||
let kind = self.stream_state.current_kind.unwrap_or(StreamKind::Answer);
|
||||
self.insert_history_lines_with_kind(kind, None, lines);
|
||||
|
||||
@@ -298,6 +298,13 @@ impl UserApprovalWidget<'_> {
|
||||
lines.push(Line::from(""));
|
||||
self.app_event_tx.send(AppEvent::InsertHistory(lines));
|
||||
|
||||
// If the user aborted an exec approval, immediately cancel any running task
|
||||
// so the UI reflects their intent (clear spinner/status) without waiting
|
||||
// for backend cleanup. Core still receives the Abort below.
|
||||
if matches!((&self.approval_request, decision), (ApprovalRequest::Exec { .. }, ReviewDecision::Abort)) {
|
||||
self.app_event_tx.send(AppEvent::CancelRunningTask);
|
||||
}
|
||||
|
||||
let op = match &self.approval_request {
|
||||
ApprovalRequest::Exec { id, .. } => Op::ExecApproval {
|
||||
id: id.clone(),
|
||||
|
||||
Reference in New Issue
Block a user