From 22763c98b02a7b61c10ca765df8e7b557c0ceb0e Mon Sep 17 00:00:00 2001 From: Jack Wotherspoon Date: Wed, 18 Feb 2026 10:52:30 -0500 Subject: [PATCH] fix: optimize height calculations for ask_user dialog (#19017) --- .../components/ToolConfirmationQueue.test.tsx | 54 ++++++++++++++++++- .../ui/components/ToolConfirmationQueue.tsx | 14 ++--- .../ToolConfirmationQueue.test.tsx.snap | 19 +++++++ .../messages/ToolConfirmationMessage.tsx | 6 ++- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx index 6e0bb87136..d1e855b5c3 100644 --- a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx +++ b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx @@ -7,7 +7,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Box } from 'ink'; import { ToolConfirmationQueue } from './ToolConfirmationQueue.js'; -import { StreamingState } from '../types.js'; +import { StreamingState, ToolCallStatus } from '../types.js'; import { renderWithProviders } from '../../test-utils/render.js'; import { waitFor } from '../../test-utils/async.js'; import { type Config, CoreToolCallStatus } from '@google/gemini-cli-core'; @@ -223,6 +223,58 @@ describe('ToolConfirmationQueue', () => { expect(lastFrame()).toMatchSnapshot(); }); + it('provides more height for ask_user by subtracting less overhead', async () => { + const confirmingTool = { + tool: { + callId: 'call-1', + name: 'ask_user', + description: 'ask user', + status: ToolCallStatus.Confirming, + confirmationDetails: { + type: 'ask_user' as const, + questions: [ + { + type: 'choice', + header: 'Height Test', + question: 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6', + options: [{ label: 'Option 1', description: 'Desc' }], + }, + ], + }, + }, + index: 1, + total: 1, + }; + + const { lastFrame } = renderWithProviders( + , + { + config: mockConfig, + uiState: { + terminalWidth: 80, + terminalHeight: 40, + availableTerminalHeight: 20, + constrainHeight: true, + streamingState: StreamingState.WaitingForConfirmation, + }, + }, + ); + + // Calculation: + // availableTerminalHeight: 20 -> maxHeight: 19 (20-1) + // hideToolIdentity is true for ask_user -> subtracts 4 instead of 6 + // availableContentHeight = 19 - 4 = 15 + // ToolConfirmationMessage handlesOwnUI=true -> returns full 15 + // AskUserDialog uses 15 lines to render its multi-line question and options. + await waitFor(() => { + expect(lastFrame()).toContain('Line 6'); + expect(lastFrame()).not.toContain('lines hidden'); + }); + expect(lastFrame()).toMatchSnapshot(); + }); + it('does not render expansion hint when constrainHeight is false', () => { const longDiff = 'line\n'.repeat(50); const confirmingTool = { diff --git a/packages/cli/src/ui/components/ToolConfirmationQueue.tsx b/packages/cli/src/ui/components/ToolConfirmationQueue.tsx index 52cba7e0d7..e3c18e0231 100644 --- a/packages/cli/src/ui/components/ToolConfirmationQueue.tsx +++ b/packages/cli/src/ui/components/ToolConfirmationQueue.tsx @@ -60,6 +60,12 @@ export const ToolConfirmationQueue: React.FC = ({ ? Math.max(uiAvailableHeight - 1, 4) : Math.floor(terminalHeight * 0.5); + const isRoutine = + tool.confirmationDetails?.type === 'ask_user' || + tool.confirmationDetails?.type === 'exit_plan_mode'; + const borderColor = isRoutine ? theme.status.success : theme.status.warning; + const hideToolIdentity = isRoutine; + // ToolConfirmationMessage needs to know the height available for its OWN content. // We subtract the lines used by the Queue wrapper: // - 2 lines for the rounded border @@ -67,15 +73,9 @@ export const ToolConfirmationQueue: React.FC = ({ // - 2 lines for Tool Identity (text + margin) const availableContentHeight = constrainHeight && !isAlternateBuffer - ? Math.max(maxHeight - 6, 4) + ? Math.max(maxHeight - (hideToolIdentity ? 4 : 6), 4) : undefined; - const isRoutine = - tool.confirmationDetails?.type === 'ask_user' || - tool.confirmationDetails?.type === 'exit_plan_mode'; - const borderColor = isRoutine ? theme.status.success : theme.status.warning; - const hideToolIdentity = isRoutine; - return ( diff --git a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap index aad58b92a7..f8ba499abd 100644 --- a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap @@ -40,6 +40,25 @@ exports[`ToolConfirmationQueue > does not render expansion hint when constrainHe ╰──────────────────────────────────────────────────────────────────────────────╯" `; +exports[`ToolConfirmationQueue > provides more height for ask_user by subtracting less overhead 1`] = ` +"╭──────────────────────────────────────────────────────────────────────────────╮ +│ Answer Questions │ +│ │ +│ Line 1 │ +│ Line 2 │ +│ Line 3 │ +│ Line 4 │ +│ Line 5 │ +│ Line 6 │ +│ │ +│ ● 1. Option 1 │ +│ Desc │ +│ 2. Enter a custom value │ +│ │ +│ Enter to select · ↑/↓ to navigate · Esc to cancel │ +╰──────────────────────────────────────────────────────────────────────────────╯" +`; + exports[`ToolConfirmationQueue > renders AskUser tool confirmation with Success color 1`] = ` "╭──────────────────────────────────────────────────────────────────────────────╮ │ Answer Questions │ diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index 13feb1682f..42642d66f9 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -235,6 +235,10 @@ export const ToolConfirmationMessage: React.FC< return undefined; } + if (handlesOwnUI) { + return availableTerminalHeight; + } + // 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). @@ -253,7 +257,7 @@ export const ToolConfirmationMessage: React.FC< 1; // Reserve one line for 'ShowMoreLines' hint return Math.max(availableTerminalHeight - surroundingElementsHeight, 1); - }, [availableTerminalHeight, getOptions]); + }, [availableTerminalHeight, getOptions, handlesOwnUI]); const { question, bodyContent, options } = useMemo(() => { let bodyContent: React.ReactNode | null = null;