Allow /archive while a turn is running (#50764)

## Why

`/archive` was disabled during a running turn, preventing users from archiving the current session until the turn ended.

## What changed

Enable `/archive` during a running turn and warn in the confirmation dialog that archiving will stop the current turn. Cancelling the dialog leaves the turn running.

## Testing

Extend confirmation tests to cover both idle and running sessions, add a snapshot of the running-turn warning, and verify that cancellation leaves the task running without emitting archive events or operations.

GitOrigin-RevId: c7c530aa671b5f04e5dc2a637e3d27a81d98d8bf
This commit is contained in:
Benjamin Carlsson
2026-10-04 02:32:39 +00:00
committed by copyberry
parent cd7d9e128c
commit dde5f8c5ae
4 changed files with 46 additions and 25 deletions
@@ -238,7 +238,12 @@ impl ChatWidget {
self.bottom_pane.show_selection_view(SelectionViewParams {
title: Some("Archive this session?".to_string()),
subtitle: Some(
"Are you sure? This will archive the current session".to_string(),
if self.bottom_pane.is_task_running() {
"This will stop the current turn and archive the session."
} else {
"Are you sure? This will archive the current session"
}
.to_string(),
),
footer_hint: Some(standard_popup_hint_line()),
items: vec![
@@ -0,0 +1,13 @@
---
source: tui/src/chatwidget/tests/slash_commands.rs
assertion_line: 2937
expression: popup
---
• Wo Archive this session?
This will stop the current turn and archive the session.
› As
› 1. No, don't archive Return to the current session
GP 2. Yes, archive Archive this session now
enter select · esc back
@@ -2755,24 +2755,19 @@ async fn slash_clear_is_disabled_while_task_running() {
}
#[tokio::test]
async fn slash_archive_is_disabled_while_task_running() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
async fn slash_archive_cancellation_keeps_task_running() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.bottom_pane.set_task_running(/*running*/ true);
chat.dispatch_command(SlashCommand::Archive);
let event = rx.try_recv().expect("expected disabled command error");
match event {
AppEvent::InsertHistoryCell(cell) => {
let rendered = lines_to_single_string(&cell.display_lines(/*width*/ 80));
assert!(
rendered.contains("'/archive' is disabled while a task is in progress."),
"expected /archive task-running error, got {rendered:?}"
);
}
other => panic!("expected InsertHistoryCell error, got {other:?}"),
}
assert!(rx.try_recv().is_err(), "expected no follow-up events");
assert!(chat.bottom_pane.has_active_view());
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
assert!(!chat.bottom_pane.has_active_view());
assert!(chat.bottom_pane.is_task_running());
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
assert_matches!(op_rx.try_recv(), Err(TryRecvError::Empty));
}
#[tokio::test]
@@ -2927,20 +2922,28 @@ async fn slash_import_opens_claude_code_import_picker() {
#[tokio::test]
async fn slash_archive_confirmation_requests_current_thread_archive() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
for running in [false, true] {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.bottom_pane.set_task_running(running);
chat.dispatch_command(SlashCommand::Archive);
chat.dispatch_command(SlashCommand::Archive);
assert!(chat.bottom_pane.has_active_view());
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
assert!(chat.bottom_pane.has_active_view());
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
assert_matches!(op_rx.try_recv(), Err(TryRecvError::Empty));
let popup = render_bottom_popup(&chat, /*width*/ 80);
assert_chatwidget_snapshot!("slash_archive_confirmation_popup", popup);
let popup = render_bottom_popup(&chat, /*width*/ 80);
if running {
assert_chatwidget_snapshot!("slash_archive_running_confirmation_popup", popup);
} else {
assert_chatwidget_snapshot!("slash_archive_confirmation_popup", popup);
}
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
assert_matches!(rx.try_recv(), Ok(AppEvent::ArchiveCurrentThread));
assert_matches!(rx.try_recv(), Ok(AppEvent::ArchiveCurrentThread));
}
}
#[tokio::test]
+1 -1
View File
@@ -241,7 +241,6 @@ impl SlashCommand {
pub fn available_during_task(self) -> bool {
match self {
SlashCommand::New
| SlashCommand::Archive
| SlashCommand::Delete
| SlashCommand::Fork
| SlashCommand::Worktree
@@ -264,6 +263,7 @@ impl SlashCommand {
| SlashCommand::MemoryDrop
| SlashCommand::MemoryUpdate => false,
SlashCommand::Diff
| SlashCommand::Archive
| SlashCommand::Resume
| SlashCommand::Model
| SlashCommand::Daybreak