diff options
| author | Jacob Richman <[email protected]> | 2025-06-19 20:17:23 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-19 13:17:23 -0700 |
| commit | b0bc7c3d996d25c9fefdfbcba3ca19fa46ad199f (patch) | |
| tree | c2d89d14b8dade1daf51f835969d9b0e79d4df30 /packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx | |
| parent | 10a83a6395b70f21b01da99d0992c78d0354a8dd (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.tsx | 114 |
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 */} |
