From 679a944dbcdd5de1e745172268eb9e050d0d6034 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Fri, 5 Jun 2026 15:10:13 -0300 Subject: [PATCH] fix(tui): restore cancelled prompt cursor at end (#26457) ## Why Pressing `Esc` on a turn that produced no visible output restores the submitted prompt so the user can keep editing it. That restore path preserved the prompt content, images, and mention bindings, but left the composer cursor at the start of the restored text. The next edit therefore inserted at the beginning instead of continuing from the end of the prompt. ## What Changed - Move the cursor to the end after `BottomPane::set_composer_text_with_mention_bindings` rehydrates a restored draft. - Add test-only cursor accessors so restore tests can assert the composer state directly. - Extend the queued restore regression to assert the restored composer cursor is positioned at `text.len()`. ## How to Test Manual reviewer flow: 1. Start Codex in the TUI. 2. Submit a prompt that will take long enough to interrupt. 3. Press `Esc` before any visible assistant output appears. 4. Confirm the prompt is restored into the composer and the cursor is at the end, so typing appends to the prompt. 5. Repeat with a prompt that includes an attached image or resolved mention and confirm the restored content remains intact. Targeted tests: - `just test -p codex-tui chatwidget::tests::composer_submission::queued_restore_with_remote_images_keeps_local_placeholder_mapping` Lint note: - `just argument-comment-lint` is blocked locally by the existing Bazel `compiler-rt` empty glob failure before analyzing touched code. The touched Rust diff was manually inspected and adds no new opaque positional literal callsites. --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 5 +++++ codex-rs/tui/src/bottom_pane/mod.rs | 6 ++++++ codex-rs/tui/src/chatwidget/tests/composer_submission.rs | 1 + 3 files changed, 12 insertions(+) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 5a39b53aa..6ebfac433 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -1258,6 +1258,11 @@ impl ChatComposer { self.draft.textarea.cursor() + if self.draft.is_bash_mode { 1 } else { 0 } } + #[cfg(test)] + pub(crate) fn cursor(&self) -> usize { + self.current_cursor() + } + fn history_navigation_cursor(&self) -> usize { if self.draft.is_bash_mode && self.draft.textarea.cursor() == 0 { 0 diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 9bada615f..cdd4c752d 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -786,6 +786,7 @@ impl BottomPane { local_image_paths, mention_bindings, ); + self.composer.move_cursor_to_end(); self.request_redraw(); } @@ -824,6 +825,11 @@ impl BottomPane { self.composer.current_text() } + #[cfg(test)] + pub(crate) fn composer_cursor(&self) -> usize { + self.composer.cursor() + } + pub(crate) fn composer_draft_snapshot(&self) -> chat_composer::ComposerDraftSnapshot { self.composer.draft_snapshot() } diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs index f3e32b2eb..154add1d6 100644 --- a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs +++ b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs @@ -730,6 +730,7 @@ async fn queued_restore_with_remote_images_keeps_local_placeholder_mapping() { }); assert_eq!(chat.bottom_pane.composer_text(), text); + assert_eq!(chat.bottom_pane.composer_cursor(), text.len()); assert_eq!(chat.bottom_pane.composer_text_elements(), text_elements); assert_eq!(chat.bottom_pane.composer_local_images(), local_images); assert_eq!(chat.remote_image_urls(), remote_image_urls);