diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 267bffd8d8..1c50fb61c3 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -130,6 +130,7 @@ import { registerCleanup, runExitCleanup } from '../utils/cleanup.js'; import { relaunchApp } from '../utils/processUtils.js'; import type { SessionInfo } from '../utils/sessionUtils.js'; import { useMessageQueue } from './hooks/useMessageQueue.js'; +import { usePromptStash } from './hooks/usePromptStash.js'; import { useMcpStatus } from './hooks/useMcpStatus.js'; import { useApprovalModeIndicator } from './hooks/useApprovalModeIndicator.js'; import { useSessionStats } from './contexts/SessionContext.js'; @@ -253,17 +254,7 @@ 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 { stashedPrompt, stashPrompt, popStashedPrompt } = usePromptStash(); const [newAgents, setNewAgents] = useState(null); const [constrainHeight, setConstrainHeight] = useState(true); diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx index a9e1c9ab05..c0cf0ed6b3 100644 --- a/packages/cli/src/ui/components/Composer.test.tsx +++ b/packages/cli/src/ui/components/Composer.test.tsx @@ -1095,5 +1095,14 @@ describe('Composer', () => { const { lastFrame } = await renderComposer(uiState); expect(lastFrame()).toMatchSnapshot(); }); + + it('matches snapshot with stashed prompt and queued messages', async () => { + const uiState = createMockUIState({ + stashedPrompt: 'This is a stashed prompt', + messageQueue: ['First queued message', 'Second queued message'], + }); + const { lastFrame } = await renderComposer(uiState); + expect(lastFrame()).toMatchSnapshot(); + }); }); }); diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index a5db19061c..1c78e4f6ff 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -523,13 +523,13 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { )} - {showUiDetails && ( - - )} - - {showUiDetails && ( - - )} + {showUiDetails && + (uiState.stashedPrompt || uiState.messageQueue.length > 0) && ( + + + + + )} {showUiDetails && } diff --git a/packages/cli/src/ui/components/Help.tsx b/packages/cli/src/ui/components/Help.tsx index 2543748e14..2569623c80 100644 --- a/packages/cli/src/ui/components/Help.tsx +++ b/packages/cli/src/ui/components/Help.tsx @@ -190,12 +190,6 @@ 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/QueuedMessageDisplay.tsx b/packages/cli/src/ui/components/QueuedMessageDisplay.tsx index edf10b5593..2e76717c65 100644 --- a/packages/cli/src/ui/components/QueuedMessageDisplay.tsx +++ b/packages/cli/src/ui/components/QueuedMessageDisplay.tsx @@ -20,7 +20,7 @@ export const QueuedMessageDisplay = ({ } return ( - + Queued (press ↑ to edit): diff --git a/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx b/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx index b7f0bc32f8..fe49fd3bf1 100644 --- a/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx +++ b/packages/cli/src/ui/components/StashedPromptDisplay.test.tsx @@ -1,6 +1,6 @@ /** * @license - * Copyright 2025 Google LLC + * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ diff --git a/packages/cli/src/ui/components/StashedPromptDisplay.tsx b/packages/cli/src/ui/components/StashedPromptDisplay.tsx index 12baeca5bf..3c654c324a 100644 --- a/packages/cli/src/ui/components/StashedPromptDisplay.tsx +++ b/packages/cli/src/ui/components/StashedPromptDisplay.tsx @@ -1,6 +1,6 @@ /** * @license - * Copyright 2025 Google LLC + * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ @@ -18,7 +18,7 @@ export const StashedPromptDisplay = ({ } return ( - + Stashed (restores after submit) ); diff --git a/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap index 745347bc95..be6bf5cd17 100644 --- a/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap @@ -43,3 +43,17 @@ InputPrompt: Type your message or @path/to/file Footer " `; + +exports[`Composer > Snapshots > matches snapshot with stashed prompt and queued messages 1`] = ` +" + Stashed (restores after submit) +First queued message +Second queued message + + ? for shortcuts +──────────────────────────────────────────────────────────────────────────────────────────────────── + ApprovalModeIndicator: default StatusDisplay +InputPrompt: Type your message or @path/to/file +Footer +" +`; diff --git a/packages/cli/src/ui/hooks/usePromptStash.ts b/packages/cli/src/ui/hooks/usePromptStash.ts new file mode 100644 index 0000000000..4b98297ab0 --- /dev/null +++ b/packages/cli/src/ui/hooks/usePromptStash.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useCallback, useState } from 'react'; + +export interface UsePromptStashReturn { + /** The currently stashed prompt text, or null if nothing is stashed. */ + stashedPrompt: string | null; + /** Save text to the stash, replacing any existing stash. No-op for empty strings. */ + stashPrompt: (text: string) => void; + /** Pop and return the stashed prompt, clearing the stash. */ + popStashedPrompt: () => string | null; +} + +/** + * Hook for managing a single stashed prompt. + * + * Allows users to temporarily set aside their current input and restore it + * after the next submit. + */ +export function usePromptStash(): UsePromptStashReturn { + 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]); + + return { stashedPrompt, stashPrompt, popStashedPrompt }; +} diff --git a/packages/core/.geminiignore b/packages/core/.geminiignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/.gitignore b/packages/core/.gitignore new file mode 100644 index 0000000000..e69de29bb2