Files
codex/prs/bolinfest/study/PR-1696-study.md
2025-09-02 15:17:45 -07:00

5.4 KiB
Raw Blame History

PR 1696 — Review Takeaways (bolinfest)

DOs

  • Use enums for cancellation: Return a small, self-documenting enum (not bool) for Ctrl-C handling across views.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CancellationEvent { Ignored, Handled }

pub(crate) trait BottomPaneView<'a> {
    fn on_ctrl_c(&mut self, _pane: &mut BottomPane<'a>) -> CancellationEvent {
        CancellationEvent::Ignored
    }
}
  • Centralize Ctrl-C routing: Let BottomPane delegate Ctrl-C to the active view; show the quit hint when handled.
pub(crate) fn on_ctrl_c(&mut self) -> CancellationEvent {
    let mut view = match self.active_view.take() {
        Some(v) => v,
        None => return CancellationEvent::Ignored,
    };

    let event = view.on_ctrl_c(self);
    match event {
        CancellationEvent::Handled => {
            if !view.is_complete() {
                self.active_view = Some(view);
            } else if self.is_task_running {
                self.active_view = Some(Box::new(StatusIndicatorView::new(self.app_event_tx.clone())));
            }
            self.show_ctrl_c_quit_hint();
        }
        CancellationEvent::Ignored => {
            self.active_view = Some(view);
        }
    }
    event
}
  • Treat Ctrl-C like Esc in modals: Abort the request and clear the queue in the approval modal.
fn on_ctrl_c(&mut self, _pane: &mut BottomPane<'a>) -> CancellationEvent {
    self.current.on_ctrl_c();
    self.queue.clear();
    CancellationEvent::Handled
}
  • Print approval-needed commands before the modal: Add a history entry first; inline variables with format!.
let cmdline = strip_bash_lc_and_escape(&command);
let text = format!(
    "command requires approval:\n$ {cmdline}{reason}",
    reason = reason.as_ref().map(|r| format!("\n{r}")).unwrap_or_default()
);
self.conversation_history.add_background_event(text);
self.emit_last_history_entry();
self.conversation_history.scroll_to_bottom();
self.bottom_pane.push_approval_request(request);
self.request_redraw();
  • Record approval decisions (with feedback) in history: Build lines once; append optional feedback after the match.
let mut lines: Vec<Line<'static>> = match &self.approval_request {
    ApprovalRequest::Exec { command, .. } => {
        let cmd = strip_bash_lc_and_escape(command);
        vec![
            Line::from("approval decision"),
            Line::from(format!("$ {cmd}")),
            Line::from(format!("decision: {decision:?}")),
        ]
    }
    ApprovalRequest::ApplyPatch { .. } => vec![
        Line::from(format!("patch approval decision: {decision:?}")),
    ],
};

if !feedback.trim().is_empty() {
    lines.push(Line::from("feedback:"));
    lines.extend(feedback.lines().map(|l| Line::from(l.to_string())));
}
lines.push(Line::from(""));
self.app_event_tx.send(AppEvent::InsertHistory(lines));
  • Prefer early returns to reduce nesting: Use match + early return instead of deep if/else chains.
let Some(mut view) = self.active_view.take() else {
    return CancellationEvent::Ignored;
};
  • Keep mpsc receivers alive in tests: Retain rx for the tests scope; assert on enums.
let (tx, _rx) = channel::<AppEvent>(); // keep _rx in scope
let tx = AppEventSender::new(tx);
let mut pane = BottomPane::new(BottomPaneParams { app_event_tx: tx, has_input_focus: true });
pane.push_approval_request(exec_request());
assert_eq!(CancellationEvent::Handled, pane.on_ctrl_c());
assert!(pane.ctrl_c_quit_hint_visible());
assert_eq!(CancellationEvent::Ignored, pane.on_ctrl_c());
  • Propagate enum up the stack: Make ChatWidget::on_ctrl_c return CancellationEvent.
pub(crate) fn on_ctrl_c(&mut self) -> CancellationEvent {
    match self.bottom_pane.on_ctrl_c() {
        CancellationEvent::Handled => return CancellationEvent::Handled,
        CancellationEvent::Ignored => {}
    }
    if self.bottom_pane.is_task_running() {
        self.bottom_pane.clear_ctrl_c_quit_hint();
        self.submit_op(Op::Interrupt);
        self.answer_buffer.clear();
        self.reasoning_buffer.clear();
        CancellationEvent::Ignored
    } else if self.bottom_pane.ctrl_c_quit_hint_visible() {
        self.submit_op(Op::Shutdown);
        CancellationEvent::Handled
    } else {
        self.bottom_pane.show_ctrl_c_quit_hint();
        CancellationEvent::Ignored
    }
}

DONTs

  • Dont return raw bools for cancellation: Avoid bool for “handled or not”; use a CancellationEvent enum instead.

  • Dont build strings piecemeal: Avoid push_str chains when format! with inlined placeholders is clearer and safer.

// Prefer:
let msg = format!("command requires approval:\n$ {cmdline}{reason}", reason = opt_reason);
// Over:
let mut msg = String::new();
msg.push_str("command requires approval:\n$ ");
msg.push_str(&cmdline);
  • Dont drop test receivers: Dont hide channel creation in helpers that drop rx; construct (tx, _rx) inline and keep _rx alive.

  • Dont duplicate feedback handling: Dont repeat “append feedback lines” in each match arm; do it once after building the common lines.

  • Dont lose view state after Ctrl-C: Dont forget to put the view back when ignored, or to switch to StatusIndicatorView when the modal completes and a task is running.

  • Dont forget UI updates: Dont omit request_redraw() after enqueueing modal requests, or the Ctrl-C quit hint after a handled cancellation.