summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
diff options
context:
space:
mode:
authorJacob Richman <[email protected]>2025-06-19 20:17:23 +0000
committerGitHub <[email protected]>2025-06-19 13:17:23 -0700
commitb0bc7c3d996d25c9fefdfbcba3ca19fa46ad199f (patch)
treec2d89d14b8dade1daf51f835969d9b0e79d4df30 /packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
parent10a83a6395b70f21b01da99d0992c78d0354a8dd (diff)
Fix flicker issues by ensuring all actively changing content fits in the viewport (#1217)
Diffstat (limited to 'packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx')
-rw-r--r--packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx114
1 files changed, 81 insertions, 33 deletions
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
index e1e53ff6..4f2c31d3 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
@@ -19,17 +19,26 @@ import {
RadioButtonSelect,
RadioSelectItem,
} from '../shared/RadioButtonSelect.js';
+import { MaxSizedBox } from '../shared/MaxSizedBox.js';
export interface ToolConfirmationMessageProps {
confirmationDetails: ToolCallConfirmationDetails;
config?: Config;
isFocused?: boolean;
+ availableTerminalHeight?: number;
+ terminalWidth: number;
}
export const ToolConfirmationMessage: React.FC<
ToolConfirmationMessageProps
-> = ({ confirmationDetails, isFocused = true }) => {
+> = ({
+ confirmationDetails,
+ isFocused = true,
+ availableTerminalHeight,
+ terminalWidth,
+}) => {
const { onConfirm } = confirmationDetails;
+ const childWidth = terminalWidth - 2; // 2 for padding
useInput((_, key) => {
if (!isFocused) return;
@@ -47,6 +56,35 @@ export const ToolConfirmationMessage: React.FC<
RadioSelectItem<ToolConfirmationOutcome>
>();
+ // Body content is now the DiffRenderer, passing filename to it
+ // The bordered box is removed from here and handled within DiffRenderer
+
+ function availableBodyContentHeight() {
+ if (options.length === 0) {
+ // This should not happen in practice as options are always added before this is called.
+ throw new Error('Options not provided for confirmation message');
+ }
+
+ if (availableTerminalHeight === undefined) {
+ return undefined;
+ }
+
+ // Calculate the vertical space (in lines) consumed by UI elements
+ // surrounding the main body content.
+ const PADDING_OUTER_Y = 2; // Main container has `padding={1}` (top & bottom).
+ const MARGIN_BODY_BOTTOM = 1; // margin on the body container.
+ const HEIGHT_QUESTION = 1; // The question text is one line.
+ const MARGIN_QUESTION_BOTTOM = 1; // Margin on the question container.
+ const HEIGHT_OPTIONS = options.length; // Each option in the radio select takes one line.
+
+ const surroundingElementsHeight =
+ PADDING_OUTER_Y +
+ MARGIN_BODY_BOTTOM +
+ HEIGHT_QUESTION +
+ MARGIN_QUESTION_BOTTOM +
+ HEIGHT_OPTIONS;
+ return Math.max(availableTerminalHeight - surroundingElementsHeight, 1);
+ }
if (confirmationDetails.type === 'edit') {
if (confirmationDetails.isModifying) {
return (
@@ -66,15 +104,6 @@ export const ToolConfirmationMessage: React.FC<
);
}
- // Body content is now the DiffRenderer, passing filename to it
- // The bordered box is removed from here and handled within DiffRenderer
- bodyContent = (
- <DiffRenderer
- diffContent={confirmationDetails.fileDiff}
- filename={confirmationDetails.fileName}
- />
- );
-
question = `Apply this change?`;
options.push(
{
@@ -91,18 +120,18 @@ export const ToolConfirmationMessage: React.FC<
},
{ label: 'No (esc)', value: ToolConfirmationOutcome.Cancel },
);
+ bodyContent = (
+ <DiffRenderer
+ diffContent={confirmationDetails.fileDiff}
+ filename={confirmationDetails.fileName}
+ availableTerminalHeight={availableBodyContentHeight()}
+ terminalWidth={childWidth}
+ />
+ );
} else if (confirmationDetails.type === 'exec') {
const executionProps =
confirmationDetails as ToolExecuteConfirmationDetails;
- bodyContent = (
- <Box flexDirection="column">
- <Box paddingX={1} marginLeft={1}>
- <Text color={Colors.AccentCyan}>{executionProps.command}</Text>
- </Box>
- </Box>
- );
-
question = `Allow execution?`;
options.push(
{
@@ -115,12 +144,44 @@ export const ToolConfirmationMessage: React.FC<
},
{ label: 'No (esc)', value: ToolConfirmationOutcome.Cancel },
);
+
+ let bodyContentHeight = availableBodyContentHeight();
+ if (bodyContentHeight !== undefined) {
+ bodyContentHeight -= 2; // Account for padding;
+ }
+ bodyContent = (
+ <Box flexDirection="column">
+ <Box paddingX={1} marginLeft={1}>
+ <MaxSizedBox
+ maxHeight={bodyContentHeight}
+ maxWidth={Math.max(childWidth - 4, 1)}
+ >
+ <Box>
+ <Text color={Colors.AccentCyan}>{executionProps.command}</Text>
+ </Box>
+ </MaxSizedBox>
+ </Box>
+ </Box>
+ );
} else if (confirmationDetails.type === 'info') {
const infoProps = confirmationDetails;
const displayUrls =
infoProps.urls &&
!(infoProps.urls.length === 1 && infoProps.urls[0] === infoProps.prompt);
+ question = `Do you want to proceed?`;
+ options.push(
+ {
+ label: 'Yes, allow once',
+ value: ToolConfirmationOutcome.ProceedOnce,
+ },
+ {
+ label: 'Yes, allow always',
+ value: ToolConfirmationOutcome.ProceedAlways,
+ },
+ { label: 'No (esc)', value: ToolConfirmationOutcome.Cancel },
+ );
+
bodyContent = (
<Box flexDirection="column" paddingX={1} marginLeft={1}>
<Text color={Colors.AccentCyan}>{infoProps.prompt}</Text>
@@ -134,19 +195,6 @@ export const ToolConfirmationMessage: React.FC<
)}
</Box>
);
-
- question = `Do you want to proceed?`;
- options.push(
- {
- label: 'Yes, allow once',
- value: ToolConfirmationOutcome.ProceedOnce,
- },
- {
- label: 'Yes, allow always',
- value: ToolConfirmationOutcome.ProceedAlways,
- },
- { label: 'No (esc)', value: ToolConfirmationOutcome.Cancel },
- );
} else {
// mcp tool confirmation
const mcpProps = confirmationDetails as ToolMcpConfirmationDetails;
@@ -177,7 +225,7 @@ export const ToolConfirmationMessage: React.FC<
}
return (
- <Box flexDirection="column" padding={1} minWidth="90%">
+ <Box flexDirection="column" padding={1} width={childWidth}>
{/* Body Content (Diff Renderer or Command Info) */}
{/* No separate context display here anymore for edits */}
<Box flexGrow={1} flexShrink={1} overflow="hidden" marginBottom={1}>
@@ -186,7 +234,7 @@ export const ToolConfirmationMessage: React.FC<
{/* Confirmation Question */}
<Box marginBottom={1} flexShrink={0}>
- <Text>{question}</Text>
+ <Text wrap="truncate">{question}</Text>
</Box>
{/* Select Input for Options */}