| author | |
| committer | |
| log | 14bf3f6d1644a37029be58429e8f0138e1ceb743 |
| tree | d4ecd9d072c4f32e9931edeaa33afc88b7e1b7ee |
| parent | 09d13de4381925fa2a6dea74eff8185fa301e905 |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | ]); |
| 3528 | 3535 |
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 | } |
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | * Attached banner listing the stashed prompts. Keyboard-first: opened by ⌘S on an | 33 | * Attached banner listing the stashed prompts. Opened by the stash badge or ⌘S |
| 34 | * empty composer, navigated with arrows, restored with Enter, dismissed | 34 | * 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 over | 35 | * 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 | */ |
| 38 | export const ComposerStashMenu = memo(function ComposerStashMenu(props: { | 38 | export 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 stash | 182 | ## Prompt stash |
| 183 | 183 | ||
| 184 | Use the default shortcut, `Cmd+S` on macOS or `Ctrl+S` on Windows and Linux, to stash the current | 184 | Use the default shortcut, `Cmd+S` on macOS or `Ctrl+S` on Windows and Linux, to stash the current |
| 185 | prompt and its attachments after all file uploads finish. Restore the entry later from the stash | 185 | prompt and its attachments after all file uploads finish. When the composer is empty and the stash |
| 186 | menu. Stashes that contain files must be restored in the environment where those files were | 186 | has one entry, press the shortcut again to restore it. The shortcut opens the stash menu if there |
| 187 | are multiple entries or the entry's images are still saving. You can also open the menu from the | ||
| 188 | stash badge. Stashes that contain files must be restored in the environment where those files were | ||
| 187 | uploaded. Stashed files stay uploaded on the server for 24 hours. If you restore an entry after | 189 | uploaded. Stashed files stay uploaded on the server for 24 hours. If you restore an entry after |
| 188 | that, the file comes back with **Attach again** next to it. Attach the file again or remove it, then | 190 | that, the file comes back with **Attach again** next to it. Attach the file again or remove it, then |
| 189 | send. | 191 | send. |