diff --git a/docs/reference/keyboard-shortcuts.md b/docs/reference/keyboard-shortcuts.md
index 2ca7a6bb39..ede4470b54 100644
--- a/docs/reference/keyboard-shortcuts.md
+++ b/docs/reference/keyboard-shortcuts.md
@@ -86,12 +86,13 @@ available combinations.
#### Text Input
-| Command | Action | Keys |
-| -------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------- |
-| `input.submit` | Submit the current prompt. | `Enter` |
-| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`
`Cmd/Win+Enter`
`Alt+Enter`
`Shift+Enter`
`Ctrl+J` |
-| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` |
-| `input.paste` | Paste from the clipboard. | `Ctrl+V`
`Cmd/Win+V`
`Alt+V` |
+| Command | Action | Keys |
+| -------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
+| `input.submit` | Submit the current prompt. | `Enter` |
+| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`
`Cmd/Win+Enter`
`Alt+Enter`
`Shift+Enter`
`Ctrl+J` |
+| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` |
+| `input.paste` | Paste from the clipboard. | `Ctrl+V`
`Cmd/Win+V`
`Alt+V` |
+| `input.stash` | Stash the current input to temporarily set it aside. Restores on next submit. | `Alt+S` |
#### App Controls
@@ -232,6 +233,8 @@ a `key` combination.
- `Ctrl + X` (while a plan is presented): Open the plan in an external editor to
[collaboratively edit or comment](../cli/plan-mode.md#collaborative-plan-editing)
on the implementation strategy.
+- `Alt+S`: Stash the current prompt to temporarily set it aside. The stashed
+ prompt is restored to the input box when you submit your next prompt.
- `Double-click` on a paste placeholder (alternate buffer mode only): Expand to
view full content inline. Double-click again to collapse.
diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx
index 9dd0f96758..d93b7acc18 100644
--- a/packages/cli/src/test-utils/render.tsx
+++ b/packages/cli/src/test-utils/render.tsx
@@ -567,6 +567,8 @@ const mockUIActions: UIActions = {
handleEmptyWalletChoice: vi.fn(),
setQueueErrorMessage: vi.fn(),
popAllMessages: vi.fn(),
+ stashPrompt: vi.fn(),
+ popStashedPrompt: vi.fn(),
handleApiKeySubmit: vi.fn(),
handleApiKeyCancel: vi.fn(),
setBannerVisible: vi.fn(),
diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index ce5fc7c872..267bffd8d8 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -253,6 +253,18 @@ export const AppContainer = (props: AppContainerProps) => {
QUEUE_ERROR_DISPLAY_DURATION_MS,
);
+ const [stashedPrompt, setStashedPrompt] = useState(null);
+ const stashPrompt = useCallback((text: string) => {
+ if (text.length > 0) {
+ setStashedPrompt(text);
+ }
+ }, []);
+ const popStashedPrompt = useCallback(() => {
+ const prompt = stashedPrompt;
+ setStashedPrompt(null);
+ return prompt;
+ }, [stashedPrompt]);
+
const [newAgents, setNewAgents] = useState(null);
const [constrainHeight, setConstrainHeight] = useState(true);
const [expandHintTrigger, triggerExpandHint] = useTimedMessage(
@@ -2267,6 +2279,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
activeHooks,
messageQueue,
queueErrorMessage,
+ stashedPrompt,
showApprovalModeIndicator,
allowPlanMode,
currentModel,
@@ -2393,6 +2406,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
activeHooks,
messageQueue,
queueErrorMessage,
+ stashedPrompt,
showApprovalModeIndicator,
allowPlanMode,
userTier,
@@ -2492,6 +2506,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleDeleteSession,
setQueueErrorMessage,
popAllMessages,
+ stashPrompt,
+ popStashedPrompt,
handleApiKeySubmit,
handleApiKeyCancel,
setBannerVisible,
@@ -2583,6 +2599,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleDeleteSession,
setQueueErrorMessage,
popAllMessages,
+ stashPrompt,
+ popStashedPrompt,
handleApiKeySubmit,
handleApiKeyCancel,
setBannerVisible,
diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx
index 1cbb29a06c..a9e1c9ab05 100644
--- a/packages/cli/src/ui/components/Composer.test.tsx
+++ b/packages/cli/src/ui/components/Composer.test.tsx
@@ -158,6 +158,7 @@ const createMockUIState = (overrides: Partial = {}): UIState =>
contextFileNames: [],
showApprovalModeIndicator: ApprovalMode.DEFAULT,
messageQueue: [],
+ stashedPrompt: null,
showErrorDetails: false,
constrainHeight: false,
isInputActive: true,
diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx
index af6d3b32da..a5db19061c 100644
--- a/packages/cli/src/ui/components/Composer.tsx
+++ b/packages/cli/src/ui/components/Composer.tsx
@@ -39,6 +39,7 @@ import { InputPrompt } from './InputPrompt.js';
import { Footer } from './Footer.js';
import { ShowMoreLines } from './ShowMoreLines.js';
import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
+import { StashedPromptDisplay } from './StashedPromptDisplay.js';
import { OverflowProvider } from '../contexts/OverflowContext.js';
import { ConfigInitDisplay } from './ConfigInitDisplay.js';
import { TodoTray } from './messages/Todo.js';
@@ -522,6 +523,10 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
)}
+ {showUiDetails && (
+
+ )}
+
{showUiDetails && (
)}
diff --git a/packages/cli/src/ui/components/Help.tsx b/packages/cli/src/ui/components/Help.tsx
index 2569623c80..2543748e14 100644
--- a/packages/cli/src/ui/components/Help.tsx
+++ b/packages/cli/src/ui/components/Help.tsx
@@ -190,6 +190,12 @@ export const Help: React.FC = ({ commands }) => (
{' '}
- Cycle through your prompt history
+
+
+ {formatCommand(Command.STASH_INPUT)}
+ {' '}
+ - Stash current prompt (restores on next submit)
+
For a full list of shortcuts, see{' '}
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 35cf7ef656..baa8257b96 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -225,6 +225,8 @@ export const InputPrompt: React.FC = ({
setEmbeddedShellFocused,
setShortcutsHelpVisible,
toggleCleanUiDetailsVisible,
+ stashPrompt,
+ popStashedPrompt,
} = useUIActions();
const {
terminalWidth,
@@ -372,6 +374,12 @@ export const InputPrompt: React.FC = ({
onSubmit(processedValue);
resetCompletionState();
resetReverseSearchCompletionState();
+
+ // Restore stashed prompt after submit
+ const stashed = popStashedPrompt();
+ if (stashed) {
+ buffer.setText(stashed, 'end');
+ }
},
[
buffer,
@@ -380,6 +388,7 @@ export const InputPrompt: React.FC = ({
shellModeActive,
shellHistory,
resetReverseSearchCompletionState,
+ popStashedPrompt,
],
);
@@ -1213,6 +1222,16 @@ export const InputPrompt: React.FC = ({
return true;
}
+ // Stash current input
+ if (keyMatchers[Command.STASH_INPUT](key)) {
+ if (buffer.text.length > 0) {
+ stashPrompt(buffer.text);
+ buffer.setText('');
+ resetCompletionState();
+ }
+ return true;
+ }
+
// External editor
if (keyMatchers[Command.OPEN_EXTERNAL_EDITOR](key)) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -1304,6 +1323,7 @@ export const InputPrompt: React.FC = ({
shouldShowSuggestions,
isShellSuggestionsVisible,
forceShowShellSuggestions,
+ stashPrompt,
keyMatchers,
isHelpDismissKey,
settings,
diff --git a/packages/cli/src/ui/components/ShortcutsHelp.tsx b/packages/cli/src/ui/components/ShortcutsHelp.tsx
index d94bf2b1d4..edc84cfb9d 100644
--- a/packages/cli/src/ui/components/ShortcutsHelp.tsx
+++ b/packages/cli/src/ui/components/ShortcutsHelp.tsx
@@ -44,6 +44,10 @@ const buildShortcutItems = (): ShortcutItem[] => [
key: formatCommand(Command.OPEN_EXTERNAL_EDITOR),
description: 'open external editor',
},
+ {
+ key: formatCommand(Command.STASH_INPUT),
+ description: 'stash prompt',
+ },
];
const Shortcut: React.FC<{ item: ShortcutItem }> = ({ item }) => (
@@ -73,8 +77,9 @@ export const ShortcutsHelp: React.FC = () => {
items[7],
items[2],
items[8],
- items[9],
+ items[10],
items[3],
+ items[9],
];
return (
diff --git a/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx b/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx
new file mode 100644
index 0000000000..b7f0bc32f8
--- /dev/null
+++ b/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx
@@ -0,0 +1,41 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect } from 'vitest';
+import { render } from '../../test-utils/render.js';
+import { StashedPromptDisplay } from './StashedPromptDisplay.js';
+
+describe('StashedPromptDisplay', () => {
+ it('renders nothing when no stash exists', async () => {
+ const { lastFrame, unmount } = await render(
+ ,
+ );
+
+ expect(lastFrame({ allowEmpty: true })).toBe('');
+ unmount();
+ });
+
+ it('displays stash indicator when stash exists', async () => {
+ const { lastFrame, unmount } = await render(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Stashed (restores after submit)');
+ unmount();
+ });
+
+ it('does not display the stashed text content', async () => {
+ const { lastFrame, unmount } = await render(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Stashed');
+ expect(output).not.toContain('secret stashed content');
+ unmount();
+ });
+});
diff --git a/packages/cli/src/ui/components/StashedPromptDisplay.tsx b/packages/cli/src/ui/components/StashedPromptDisplay.tsx
new file mode 100644
index 0000000000..12baeca5bf
--- /dev/null
+++ b/packages/cli/src/ui/components/StashedPromptDisplay.tsx
@@ -0,0 +1,25 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Box, Text } from 'ink';
+
+export interface StashedPromptDisplayProps {
+ stashedPrompt: string | null;
+}
+
+export const StashedPromptDisplay = ({
+ stashedPrompt,
+}: StashedPromptDisplayProps) => {
+ if (!stashedPrompt) {
+ return null;
+ }
+
+ return (
+
+ Stashed (restores after submit)
+
+ );
+};
diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx
index 3189172792..f6209a67d6 100644
--- a/packages/cli/src/ui/contexts/KeypressContext.tsx
+++ b/packages/cli/src/ui/contexts/KeypressContext.tsx
@@ -202,6 +202,7 @@ const MAC_ALT_KEY_CHARACTER_MAP: Record = {
'\u222B': 'b', // "∫" back one word
'\u0192': 'f', // "ƒ" forward one word
'\u00B5': 'm', // "µ" toggle markup view
+ '\u00DF': 's', // "ß" stash prompt
'\u03A9': 'z', // "Ω" Option+z
'\u00B8': 'Z', // "¸" Option+Shift+z
'\u2202': 'd', // "∂" delete word forward
diff --git a/packages/cli/src/ui/contexts/UIActionsContext.tsx b/packages/cli/src/ui/contexts/UIActionsContext.tsx
index db9a51a269..843b08815f 100644
--- a/packages/cli/src/ui/contexts/UIActionsContext.tsx
+++ b/packages/cli/src/ui/contexts/UIActionsContext.tsx
@@ -71,6 +71,8 @@ export interface UIActions {
handleDeleteSession: (session: SessionInfo) => Promise;
setQueueErrorMessage: (message: string | null) => void;
popAllMessages: () => string | undefined;
+ stashPrompt: (text: string) => void;
+ popStashedPrompt: () => string | null;
handleApiKeySubmit: (apiKey: string) => Promise;
handleApiKeyCancel: () => void;
setBannerVisible: (visible: boolean) => void;
diff --git a/packages/cli/src/ui/contexts/UIStateContext.tsx b/packages/cli/src/ui/contexts/UIStateContext.tsx
index e4d95a79af..75b8843c0d 100644
--- a/packages/cli/src/ui/contexts/UIStateContext.tsx
+++ b/packages/cli/src/ui/contexts/UIStateContext.tsx
@@ -172,6 +172,7 @@ export interface UIState {
activeHooks: ActiveHook[];
messageQueue: string[];
queueErrorMessage: string | null;
+ stashedPrompt: string | null;
showApprovalModeIndicator: ApprovalMode;
allowPlanMode: boolean;
// Quota-related state
diff --git a/packages/cli/src/ui/key/keyBindings.ts b/packages/cli/src/ui/key/keyBindings.ts
index c84f189664..79b19e1a94 100644
--- a/packages/cli/src/ui/key/keyBindings.ts
+++ b/packages/cli/src/ui/key/keyBindings.ts
@@ -77,6 +77,7 @@ export enum Command {
NEWLINE = 'input.newline',
OPEN_EXTERNAL_EDITOR = 'input.openExternalEditor',
PASTE_CLIPBOARD = 'input.paste',
+ STASH_INPUT = 'input.stash',
// App Controls
SHOW_ERROR_DETAILS = 'app.showErrorDetails',
@@ -374,6 +375,8 @@ export const defaultKeyBindingConfig: KeyBindingConfig = new Map([
],
],
+ [Command.STASH_INPUT, [new KeyBinding('alt+s')]],
+
// App Controls
[Command.SHOW_ERROR_DETAILS, [new KeyBinding('f12')]],
[Command.SHOW_FULL_TODOS, [new KeyBinding('ctrl+t')]],
@@ -491,6 +494,7 @@ export const commandCategories: readonly CommandCategory[] = [
Command.NEWLINE,
Command.OPEN_EXTERNAL_EDITOR,
Command.PASTE_CLIPBOARD,
+ Command.STASH_INPUT,
],
},
{
@@ -597,6 +601,8 @@ export const commandDescriptions: Readonly> = {
[Command.OPEN_EXTERNAL_EDITOR]:
'Open the current prompt or the plan in an external editor.',
[Command.PASTE_CLIPBOARD]: 'Paste from the clipboard.',
+ [Command.STASH_INPUT]:
+ 'Stash the current input to temporarily set it aside. Restores on next submit.',
// App Controls
[Command.SHOW_ERROR_DETAILS]: 'Toggle detailed error information.',
diff --git a/prompt-stashing-spec.md b/prompt-stashing-spec.md
new file mode 100644
index 0000000000..4c4f41c81b
--- /dev/null
+++ b/prompt-stashing-spec.md
@@ -0,0 +1,206 @@
+# Prompt Stashing Feature Spec
+
+## Overview
+
+Prompt stashing allows a user to temporarily set aside the current contents of
+the prompt input box, type and submit a different prompt, and then have the
+stashed text automatically re-populated into the input box as soon as the new
+prompt is submitted.
+
+This is useful when a user is composing a long or complex prompt and needs to
+quickly ask the model something else (e.g., a clarifying question, a quick
+lookup) without losing their in-progress work.
+
+## User Flow
+
+1. User is typing a prompt in the input box (e.g., "Refactor the auth module to
+ use...")
+2. User presses **Alt+S** to stash the current input
+3. The input box clears — ready for a new prompt
+4. A **"Stashed"** indicator appears above the input (similar to the existing
+ "Queued" display)
+5. User types and submits a new prompt (e.g., "What auth patterns does this
+ codebase use?")
+6. **Immediately on submit**, the stashed text re-populates the input box
+7. The "Stashed" indicator disappears
+8. The model responds to the submitted prompt while the user can continue
+ editing their original prompt
+
+## Keybinding
+
+| Action | Shortcut | Command ID |
+| ------------------- | --------- | ------------- |
+| Stash current input | **Alt+S** | `input.stash` |
+
+- `Alt+S` is currently unbound — no conflicts
+- Added to the `Command` enum as `STASH_INPUT = 'input.stash'`
+- Added to `defaultKeyBindingConfig` with `new KeyBinding('alt+s')`
+- Added to the `'Text Input'` command category and `commandDescriptions`
+- User-customizable via `keybindings.json` like all other shortcuts
+
+## Behavior Rules
+
+### Stashing
+
+- **Alt+S with text in the input box**: Stashes the text. Input box clears.
+ Stash indicator appears.
+- **Alt+S with empty input box**: No-op (nothing to stash), even if a stash
+ already exists.
+- **Alt+S when a stash already exists and input has text**: Overwrites the
+ existing stash with the current input text.
+
+### Restoring
+
+- **On prompt submit**: When the user submits a prompt and a stash exists, the
+ stashed text is immediately restored into the input box via
+ `buffer.setText()`. Cursor is placed at the end of the restored text. This
+ happens as part of the submit flow, before the model begins responding. The
+ stash is consumed (cleared) — it only restores once.
+
+### Edge Cases
+
+- **User quits/restarts while stash exists**: Stash is lost (in-memory only, not
+ persisted).
+- **Slash commands that open dialogs** (e.g. `/help`, `/settings`): The stash
+ restores on any submit, including these. The stash is consumed even if the
+ submission doesn't go to the model.
+- **Shell mode**: Stashing works in shell mode too.
+- **External editor (Ctrl+X)**: If the user opens the external editor while a
+ stash exists, the editor shows only the current (non-stashed) input. The stash
+ remains separately stored.
+- **Ctrl+C to cancel a response**: The stash survives. Ctrl+C only cancels the
+ running response — the stash persists and restores on the next submit.
+- **Queued messages + stash**: These are independent systems. If a prompt is
+ queued (submitted while model is responding), the stash still restores on that
+ submit. The queued message and the stash restore happen independently.
+
+## State Management
+
+### New Hook: `usePromptStash`
+
+Located at `packages/cli/src/ui/hooks/usePromptStash.ts`.
+
+```typescript
+interface UsePromptStashReturn {
+ stashedPrompt: string | null;
+ stashPrompt: (text: string) => void;
+ popStashedPrompt: () => string | null; // returns & clears
+ hasStash: boolean;
+}
+```
+
+- Simple `useState`-based hook — no persistence, no core dependency
+- Exposed through `UIStateContext` (`stashedPrompt`) and `UIActionsContext`
+ (`stashPrompt`, `popStashedPrompt`)
+
+### Integration Points
+
+| File | Change |
+| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| **Code** | |
+| `keyBindings.ts` | Add `STASH_INPUT` command, binding, category entry, description |
+| `usePromptStash.ts` | New hook (state management) |
+| `StashedPromptDisplay.tsx` | New component (UI display) |
+| `InputPrompt.tsx` | Handle `Alt+S` keypress: call `stashPrompt(buffer.text)` then `buffer.setText('')`. On submit (in `handleSubmit`): if `hasStash`, call `popStashedPrompt()` and `buffer.setText(text)` after submission (cursor at end). |
+| `Composer.tsx` | Render `StashedPromptDisplay` above `QueuedMessageDisplay` |
+| `UIStateContext` / `UIActionsContext` | Expose stash state and actions |
+| **UX / Docs** | |
+| `ShortcutsHelp.tsx` | Add `Alt+S — Stash prompt` to the `?` panel |
+| `Help.tsx` | Add `Alt+S — Stash current prompt` to `/help` keyboard shortcuts list |
+| `docs/reference/keyboard-shortcuts.md` | Auto-regenerated via `npm run docs:keybindings`; also add manual entry in context-specific section |
+
+## Documentation Updates
+
+### Auto-Generated Keyboard Shortcuts Reference
+
+**File:** `docs/reference/keyboard-shortcuts.md`
+
+This file is auto-generated from `keyBindings.ts` via
+`scripts/generate-keybindings-doc.ts`. After adding the new `STASH_INPUT`
+command to `keyBindings.ts` (enum, config, category, description), run:
+
+```
+npm run docs:keybindings
+```
+
+This will regenerate the tables between `` and
+`` markers, adding the new `Alt+S` shortcut
+under the **Text Input** section automatically.
+
+### Shortcuts Help Panel (`?` key)
+
+**File:** `packages/cli/src/ui/components/ShortcutsHelp.tsx`
+
+Add `Alt+S — Stash prompt` to the curated shortcut list in
+`buildShortcutItems()`. This is the quick-reference panel users see when
+pressing `?` — it shows only the most essential shortcuts, and stashing is a key
+workflow shortcut worth surfacing here.
+
+### /help Command Output
+
+**File:** `packages/cli/src/ui/components/Help.tsx`
+
+Add `Alt+S — Stash current prompt` to the keyboard shortcuts section (lines
+~115-192). This component renders when users type `/help` and lists selected
+important shortcuts. It also points users to the full reference at
+`https://geminicli.com/docs/reference/keyboard-shortcuts/`.
+
+### Context-Specific Shortcuts Section
+
+**File:** `docs/reference/keyboard-shortcuts.md` (manual section, lines
+~206-237)
+
+Add a brief entry under the context-specific shortcuts section explaining the
+stash workflow:
+
+> **Alt+S** — Stash the current prompt to temporarily set it aside. The stashed
+> prompt is restored to the input box when you submit your next prompt.
+
+## UX: Stashed Prompt Display
+
+A new `StashedPromptDisplay` component, rendered in `Composer.tsx` in the same
+location as `QueuedMessageDisplay`, shown when a stash exists.
+
+### Visual Design
+
+```
+ Stashed (restores after submit)
+```
+
+- Same dim styling as `QueuedMessageDisplay` (``)
+- No preview of the stashed text — just the indicator line
+- Rendered **above** `QueuedMessageDisplay` when both exist
+- Only shown when `showUiDetails` is true (matching queued display behavior)
+
+### Layout in Composer.tsx
+
+```
+ [StashedPromptDisplay] ← new, only if stash exists
+ [QueuedMessageDisplay] ← existing, only if queue non-empty
+ [ShortcutsHelp] ← existing, only if ? panel open
+ [InputPrompt] ← existing
+```
+
+## Testing
+
+### Unit Tests (`usePromptStash.test.ts`)
+
+- Stash stores text
+- `popStashedPrompt` returns stashed text and clears state
+- `hasStash` reflects current state accurately
+- Stash with empty string is a no-op
+
+### Integration Tests (`InputPrompt.test.tsx`)
+
+- Alt+S with text clears input and stores stash
+- Alt+S with empty input is a no-op
+- Alt+S when stash already exists overwrites the stash
+- Submitting a prompt while stash exists restores stashed text to input box
+- Restored text has cursor at end
+
+### E2E Considerations
+
+- Stash → submit interrupting prompt → verify stashed text re-populates input
+ immediately on submit
+- Stash → submit while model responding (queued) → verify stash still restores
+ on submit