From d8c0839fc2f8357db343fd523bd1b4909cbca99a Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 13 Mar 2026 05:44:18 -0700 Subject: [PATCH] tui: reject unavailable queued slash drafts Co-authored-by: Codex --- codex-rs/tui/src/chatwidget.rs | 12 ++---------- codex-rs/tui/src/chatwidget/tests.rs | 23 +++++++++++------------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 03396dd9a6..58992f3a7b 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5700,11 +5700,7 @@ impl ChatWidget { pub(crate) fn handle_serialized_slash_command(&mut self, draft: UserMessage) { let Some((cmd, _, _)) = self.parse_builtin_slash_command(&draft.text) else { - if let Some((name, _, _)) = parse_slash_name(&draft.text) - && !name.contains('/') - && SlashCommand::from_str(name).is_ok() - { - self.queue_user_message(draft); + if self.reject_unavailable_builtin_slash_command(&draft) { return; } self.add_error_message(format!("Failed to handle slash command: {}", draft.text)); @@ -5787,11 +5783,7 @@ impl ChatWidget { fn execute_serialized_slash_command(&mut self, draft: UserMessage) -> QueueReplayControl { let preview = draft.text.clone(); let Some((cmd, rest, rest_offset)) = self.parse_builtin_slash_command(&preview) else { - if let Some((name, _, _)) = parse_slash_name(&preview) - && !name.contains('/') - && SlashCommand::from_str(name).is_ok() - { - self.submit_user_message(draft); + if self.reject_unavailable_builtin_slash_command(&draft) { return QueueReplayControl::Stop; } self.add_error_message(format!("Failed to replay queued slash command: {preview}")); diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 52e1d9a1f1..eca0d9a8b7 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -8850,8 +8850,8 @@ async fn disabled_fast_slash_command_with_args_restores_draft_instead_of_queuein } #[tokio::test] -async fn queued_disabled_fast_slash_draft_replays_as_user_text() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; +async fn queued_disabled_fast_slash_draft_is_rejected_on_replay() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; chat.thread_id = Some(ThreadId::new()); chat.set_feature_enabled(Feature::FastMode, false); chat.on_task_started(); @@ -8865,16 +8865,15 @@ async fn queued_disabled_fast_slash_draft_replays_as_user_text() { chat.on_task_complete(None, false); - match next_submit_op(&mut op_rx) { - Op::UserTurn { items, .. } => assert_eq!( - items, - vec![UserInput::Text { - text: "/fast on".to_string(), - text_elements: Vec::new(), - }] - ), - other => panic!("expected queued disabled slash draft Op::UserTurn, got {other:?}"), - } + assert_no_submit_op(&mut op_rx); + assert!(chat.queued_user_messages.is_empty()); + assert_eq!(chat.bottom_pane.composer_text(), "/fast on"); + + let inserted = drain_insert_history(&mut rx); + assert_eq!(inserted.len(), 1); + assert!( + lines_to_single_string(&inserted[0]).contains("/fast is not available in this session.") + ); } #[tokio::test]