feat: stashes current message with Ctrl+S#10173
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a message stash feature that allows users to temporarily save their current message draft (including text, attachments, and pending paste expansions) using Ctrl+S and have it automatically restored after submitting another message.
Changes:
- Added stash functionality triggered by Ctrl+S to save current composer state
- Implemented automatic restoration after message submission
- Added visual indicator showing when a stash exists
- Added hint message when trying to restore stash with non-empty composer
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| codex-rs/tui/src/chatwidget.rs | Added stash field, Ctrl+S handling for restore, automatic restoration after submission, and stash indicator management |
| codex-rs/tui/src/chatwidget/tests.rs | Added test for stash/restore cycle |
| codex-rs/tui/src/bottom_pane/mod.rs | Added PreparedDraft export, restore_stash method, stash indicator integration, and composer_has_draft check |
| codex-rs/tui/src/bottom_pane/chat_composer.rs | Refactored prepare logic to support stashing, added PreparedDraft struct, handle_stash method, and has_draft check |
| codex-rs/tui/src/bottom_pane/footer.rs | Added CantUnstashHint footer mode and hint message |
| codex-rs/tui/src/bottom_pane/stash_indicator.rs | New file implementing visual indicator for stash existence |
| codex-rs/tui/src/bottom_pane/snapshots/*.snap | Snapshot tests for stash indicator rendering |
Comments suppressed due to low confidence (1)
codex-rs/tui/src/chatwidget.rs:2541
- The stash restoration logic is inconsistent between submitted and queued messages. When a message is submitted (InputResult::Submitted), the stash is automatically restored at line 2526. However, when a message is queued (InputResult::Queued), there is no corresponding restore_stash() call.
This means:
- User stashes content
- User queues a follow-up message while task is running
- Stash is NOT restored, even though a message was "sent"
For consistency with the PR description ("automatically restored after the next submission"), the stash should also be restored after queuing a message. Add a restore_stash() call after the queued message is handled, similar to line 2526.
InputResult::Queued {
text,
text_elements,
} => {
let user_message = UserMessage {
text,
local_images: self
.bottom_pane
.take_recent_submission_images_with_placeholders(),
text_elements,
mention_paths: self.bottom_pane.take_mention_paths(),
};
self.queue_user_message(user_message);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
codex/codex-rs/tui/src/bottom_pane/chat_composer.rs
Lines 1941 to 1945 in b26da62
prepare_composer_text runs expand_custom_prompt (and returns an error) even in ForStash mode, so pressing Ctrl+S on an incomplete /prompts: line surfaces a validation error and refuses to stash. Stashing should be a lossless snapshot of the current draft, so it should bypass prompt expansion/validation in stash mode.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The message keeps stashed until the user submits the current message. Right after sending the stashed message is restored.
Added word wrapping to the stash indicator and if Ctrl+S is pressed with a stash present, recalls it.
The rebase from upstream/main introduced new FooterMode variants (ComposerEmpty, ComposerHasDraft) and restructured several match statements. This commit adds the CantUnstashHint variant to all affected match statements and updates snapshots for the new footer layout.
Refactors set_text_content_with_local_images_and_pending_pastes to accept LocalImageAttachment structs instead of just paths, ensuring placeholder-to-path mappings are preserved when restoring stashed drafts. This prevents images from being lost during stash/unstash operations. - Rewrite method to properly filter and map LocalImageAttachment by placeholders - Remove intermediate wrapper method in BottomPane - Add test coverage for image preservation - Fix doc comment indentation in footer.rs
- Skip prompt expansion when stashing drafts - Keep mention-path mappings in stashed drafts - Image pruning was gated behind slash_commands_enabled
|
Hey Felipe, great to see you here. I'm a fan of what you're doing over on https://github.com/fcoury/textual-rs/ I think we actually already have a version of this behavior today, but without any UI that makes it clear what’s happening. You can reproduce it like this:
At that point, the interrupted text comes back, which is useful — but pretty implicit. Framing this as a way to make it clearer what Ctrl+C does (rather than introducing a brand-new concept) might be the right angle here. We’ve also touched nearby pieces of this before:
So conceptually, this feels aligned with prior work — the main gap now is making the behavior visible and understandable to users. Maybe we could add a small UX affordance for this: for example, display a brief note or tooltip when Ctrl+C is pressed explaining what just happened, or include a short mention in the startup help text. We already have a list of these onboarding tips, and this would be a good one to add there. |
Thank you and thanks for the work you do on ratatui as well! :-)
Now that's a big surprise! Nice.
I just played with it and it even saves images, very cool.
Agreed. Feel free to close this PR or let me know if you want me to and I'll be glad to work on the smaller scope note/tooltip addition PR in the future. Thank you! |
|
I'm not 100% certain exactly what the right behavior that makes this clear is but it's obviously a needed behavior (having got two PRs for it - both reasonable). We need to make the current behavior more obvious. Feel free to throw together some ideas on this. |
Summary
Adds message stash to the composer with Ctrl+S.
This feature allows users to temporarily stash their current message, attachments and pending paste expansions and have it automatically restored after the next submission.
Motivation
There were a few occasions when I was preparing a large follow-up task for the agent while I noticed the agent was doing something undesired. Under normal circumstances I need to copy the text I had written, paste it somewhere else (not really because I use a clipboard manager, but still), enter the steering message, submit it and then paste the message I had in progress.
This PR would allow me hit Ctrl+S as soon as I realize the model needs steering and as soon as I send the steering message, the stashed message and all its attributes gets automatically restored.
Edge cases
We don't want the user to ever lose a message in progress, so if the composer isn't empty when we attempt to restore the stash, we stop, don't restore it and keep the stash untouched.