authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-04 04:20:08-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 04:20:08-07:00
log14bf3f6d1644a37029be58429e8f0138e1ceb743
treed4ecd9d072c4f32e9931edeaa33afc88b7e1b7ee
parent09d13de4381925fa2a6dea74eff8185fa301e905
signature Signed by PGP key B5690EEEBB952194

fix(web): toggle a single stashed prompt with Cmd+S (#9644)

Cmd+S opened the stash menu even when the composer was empty and only one prompt was stashed. It now restores that prompt directly, so repeated presses toggle between the draft and stash. Multiple entries and images that are still saving open the menu. The stash badge still opens the menu. Validation: 94 focused stash, shortcut, and attachment tests pass. Web typecheck and formatting pass. Targeted lint has no new warnings or errors. Browser checks were skipped at Theo's request. Original implementation by Theo Browne. No code changes were needed during the takeover audit. Audited with GPT-6 Astra (preview) in Codex.

3 files changed, 16 insertions(+), 7 deletions(-)

apps/web/src/components/chat/ChatComposer.tsx+8-1
......@@ -3336,7 +3336,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
33363336 const images = [...composerImagesRef.current];
33373337 const files = [...composerFilesRef.current];
33383338 if (prompt.length === 0 && images.length === 0 && files.length === 0) {
3339 setIsStashMenuOpen((open) => !open);
3339 const entries = usePromptStashStore.getState().entries;
3340 const entry = entries.length === 1 ? entries[0] : undefined;
3341 if (entry && !entry.pendingImageCount) {
3342 await restoreStashEntry(entry);
3343 } else {
3344 setIsStashMenuOpen((open) => !open);
3345 }
33403346 return;
33413347 }
33423348 const stashedFiles: PersistedComposerFileAttachment[] = [];
......@@ -3523,6 +3529,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
35233529 finalizeStashEntryImages,
35243530 promptRef,
35253531 pulseStashBadge,
3532 restoreStashEntry,
35263533 stashEntryToQueue,
35273534 ]);
35283535
apps/web/src/components/chat/ComposerStashMenu.tsx+4-4
......@@ -30,10 +30,10 @@ function stashEntrySnippet(entry: PromptStashEntry): string {
3030}
3131
3232/**
33 * Attached banner listing the stashed prompts. Keyboard-first: opened by ⌘S on an
34 * empty composer, navigated with arrows, restored with Enter, dismissed
35 * with Escape. The listener runs capture-phase on window so it wins over
36 * the Lexical editor's handlers while the menu is open.
33 * Attached banner listing the stashed prompts. Opened by the stash badge or ⌘S
34 * when the empty composer cannot restore a single entry. Navigated with arrows,
35 * restored with Enter, dismissed with Escape. The listener runs capture-phase
36 * on window so it wins over the Lexical editor's handlers while the menu is open.
3737 */
3838export const ComposerStashMenu = memo(function ComposerStashMenu(props: {
3939 entries: ReadonlyArray<PromptStashEntry>;
docs/user/composer.md+4-2
......@@ -182,8 +182,10 @@ open the stack. Interacting with the attached banner or composer does not open t
182182## Prompt stash
183183
184184Use the default shortcut, `Cmd+S` on macOS or `Ctrl+S` on Windows and Linux, to stash the current
185prompt and its attachments after all file uploads finish. Restore the entry later from the stash
186menu. Stashes that contain files must be restored in the environment where those files were
185prompt and its attachments after all file uploads finish. When the composer is empty and the stash
186has one entry, press the shortcut again to restore it. The shortcut opens the stash menu if there
187are multiple entries or the entry's images are still saving. You can also open the menu from the
188stash badge. Stashes that contain files must be restored in the environment where those files were
187189uploaded. Stashed files stay uploaded on the server for 24 hours. If you restore an entry after
188190that, the file comes back with **Attach again** next to it. Attach the file again or remove it, then
189191send.