Skip to content

Commit 20358f7

Browse files
committed
fix(tui): resume status after resolved approval removal
Restore modal teardown side effects when removing a resolved exec approval from the active bottom-pane overlay. This resumes the paused status timer when the active approval view is cleared by `serverRequest/resolved`, while leaving hidden overlay pruning behavior unchanged. Add a regression test that exercises the active-view removal path directly.
1 parent 89efa80 commit 20358f7

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

codex-rs/tui_app_server/src/bottom_pane/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,12 +914,27 @@ impl BottomPane {
914914
}
915915

916916
pub(crate) fn remove_resolved_exec_approvals(&mut self, approval_ids: &[String]) {
917+
let active_view_ptr = self
918+
.view_stack
919+
.last()
920+
.map(|view| (&**view) as *const dyn BottomPaneView as *const ());
917921
let mut changed = false;
918922
self.view_stack.retain_mut(|view| {
919923
changed |= view.remove_resolved_exec_approvals(approval_ids);
920924
!view.is_complete()
921925
});
922926
if changed {
927+
let active_view_removed = active_view_ptr.is_some_and(|active_view_ptr| {
928+
!self.view_stack.iter().any(|view| {
929+
std::ptr::eq(
930+
(&**view) as *const dyn BottomPaneView as *const (),
931+
active_view_ptr,
932+
)
933+
})
934+
});
935+
if active_view_removed && self.view_stack.is_empty() {
936+
self.on_active_view_complete();
937+
}
923938
self.request_redraw();
924939
}
925940
}
@@ -1514,6 +1529,40 @@ mod tests {
15141529
);
15151530
}
15161531

1532+
#[test]
1533+
fn removing_active_resolved_exec_approval_resumes_status_timer() {
1534+
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
1535+
let tx = AppEventSender::new(tx_raw);
1536+
let features = Features::with_defaults();
1537+
let mut pane = BottomPane::new(BottomPaneParams {
1538+
app_event_tx: tx,
1539+
frame_requester: FrameRequester::test_dummy(),
1540+
has_input_focus: true,
1541+
enhanced_keys_supported: false,
1542+
placeholder_text: "Ask Codex to do anything".to_string(),
1543+
disable_paste_burst: false,
1544+
animations_enabled: true,
1545+
skills: Some(Vec::new()),
1546+
});
1547+
1548+
pane.set_task_running(true);
1549+
pane.push_approval_request(exec_request(), &features);
1550+
assert!(
1551+
pane.status
1552+
.as_ref()
1553+
.is_some_and(StatusIndicatorWidget::is_paused),
1554+
"approval modal should pause the running-task timer"
1555+
);
1556+
1557+
pane.remove_resolved_exec_approvals(&["1".to_string()]);
1558+
assert!(
1559+
pane.status
1560+
.as_ref()
1561+
.is_some_and(|status| !status.is_paused()),
1562+
"removing the active resolved approval should resume the running-task timer"
1563+
);
1564+
}
1565+
15171566
#[test]
15181567
fn status_indicator_visible_during_command_execution() {
15191568
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();

codex-rs/tui_app_server/src/status_indicator_widget.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ impl StatusIndicatorWidget {
153153
self.show_interrupt_hint
154154
}
155155

156+
#[cfg(test)]
157+
pub(crate) fn is_paused(&self) -> bool {
158+
self.is_paused
159+
}
160+
156161
pub(crate) fn pause_timer(&mut self) {
157162
self.pause_timer_at(Instant::now());
158163
}

0 commit comments

Comments
 (0)