refactor(cli): extract prompt stash logic to hook and polish UI layout

This commit is contained in:
Jack Wotherspoon
2026-03-24 23:52:26 -07:00
parent d937221d2f
commit c7a2250e1e
11 changed files with 76 additions and 28 deletions
+2 -11
View File
@@ -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<string | null>(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<AgentDefinition[] | null>(null);
const [constrainHeight, setConstrainHeight] = useState<boolean>(true);
@@ -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();
});
});
});
+7 -7
View File
@@ -523,13 +523,13 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
<ConfigInitDisplay message="Resuming session..." />
)}
{showUiDetails && (
<StashedPromptDisplay stashedPrompt={uiState.stashedPrompt} />
)}
{showUiDetails && (
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
)}
{showUiDetails &&
(uiState.stashedPrompt || uiState.messageQueue.length > 0) && (
<Box flexDirection="column" marginTop={1}>
<StashedPromptDisplay stashedPrompt={uiState.stashedPrompt} />
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
</Box>
)}
{showUiDetails && <TodoTray />}
-6
View File
@@ -190,12 +190,6 @@ export const Help: React.FC<Help> = ({ commands }) => (
</Text>{' '}
- Cycle through your prompt history
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
{formatCommand(Command.STASH_INPUT)}
</Text>{' '}
- Stash current prompt (restores on next submit)
</Text>
<Box height={1} />
<Text color={theme.text.primary}>
For a full list of shortcuts, see{' '}
@@ -20,7 +20,7 @@ export const QueuedMessageDisplay = ({
}
return (
<Box flexDirection="column" marginTop={1}>
<Box flexDirection="column">
<Box paddingLeft={2}>
<Text dimColor>Queued (press to edit):</Text>
</Box>
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2025 Google LLC
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
@@ -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 (
<Box paddingLeft={2} marginTop={1}>
<Box paddingLeft={2}>
<Text dimColor>Stashed (restores after submit)</Text>
</Box>
);
@@ -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
"
`;
@@ -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<string | null>(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 };
}
View File
View File