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)...@@ -3336,7 +3336,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
3336 const images = [...composerImagesRef.current];3336 const images = [...composerImagesRef.current];
3337 const files = [...composerFilesRef.current];3337 const files = [...composerFilesRef.current];
3338 if (prompt.length === 0 && images.length === 0 && files.length === 0) {3338 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 }
3340 return;3346 return;
3341 }3347 }
3342 const stashedFiles: PersistedComposerFileAttachment[] = [];3348 const stashedFiles: PersistedComposerFileAttachment[] = [];
...@@ -3523,6 +3529,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -3523,6 +3529,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
3523 finalizeStashEntryImages,3529 finalizeStashEntryImages,
3524 promptRef,3530 promptRef,
3525 pulseStashBadge,3531 pulseStashBadge,
3532 restoreStashEntry,
3526 stashEntryToQueue,3533 stashEntryToQueue,
3527 ]);3534 ]);
35283535
apps/web/src/components/chat/ComposerStashMenu.tsx+4-4
...@@ -30,10 +30,10 @@ function stashEntrySnippet(entry: PromptStashEntry): string {...@@ -30,10 +30,10 @@ function stashEntrySnippet(entry: PromptStashEntry): string {
30}30}
3131
32/**32/**
33 * Attached banner listing the stashed prompts. Keyboard-first: opened by ⌘S on an33 * Attached banner listing the stashed prompts. Opened by the stash badge or ⌘S
34 * empty composer, navigated with arrows, restored with Enter, dismissed34 * when the empty composer cannot restore a single entry. Navigated with arrows,
35 * with Escape. The listener runs capture-phase on window so it wins over35 * restored with Enter, dismissed with Escape. The listener runs capture-phase
36 * the Lexical editor's handlers while the menu is open.36 * on window so it wins over the Lexical editor's handlers while the menu is open.
37 */37 */
38export const ComposerStashMenu = memo(function ComposerStashMenu(props: {38export const ComposerStashMenu = memo(function ComposerStashMenu(props: {
39 entries: ReadonlyArray<PromptStashEntry>;39 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...@@ -182,8 +182,10 @@ open the stack. Interacting with the attached banner or composer does not open t
182## Prompt stash182## Prompt stash
183183
184Use the default shortcut, `Cmd+S` on macOS or `Ctrl+S` on Windows and Linux, to stash the current184Use 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 stash185prompt and its attachments after all file uploads finish. When the composer is empty and the stash
186menu. Stashes that contain files must be restored in the environment where those files were186has 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
187uploaded. Stashed files stay uploaded on the server for 24 hours. If you restore an entry after189uploaded. Stashed files stay uploaded on the server for 24 hours. If you restore an entry after
188that, the file comes back with **Attach again** next to it. Attach the file again or remove it, then190that, the file comes back with **Attach again** next to it. Attach the file again or remove it, then
189send.191send.