From ec2d0a89d256d6e8922774280da53f6719f3346b Mon Sep 17 00:00:00 2001 From: "A.K.M. Adib" Date: Thu, 5 Mar 2026 15:40:12 -0500 Subject: [PATCH] update to height based on all available height module fix overflow --- .../cli/src/ui/components/AskUserDialog.tsx | 28 ++++++++++--------- .../components/ToolConfirmationQueue.test.tsx | 7 +++-- .../__snapshots__/AskUserDialog.test.tsx.snap | 16 +++++++++++ .../src/ui/components/shared/MaxSizedBox.tsx | 14 ++++++++++ 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/ui/components/AskUserDialog.tsx b/packages/cli/src/ui/components/AskUserDialog.tsx index b5651f79bb..56a433934b 100644 --- a/packages/cli/src/ui/components/AskUserDialog.tsx +++ b/packages/cli/src/ui/components/AskUserDialog.tsx @@ -12,6 +12,7 @@ import { useEffect, useReducer, useContext, + useState, } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; @@ -782,28 +783,28 @@ const ChoiceQuestionView: React.FC = ({ } }, [customOptionText, isCustomOptionSelected, question.multiSelect]); + const [actualQuestionHeight, setActualQuestionHeight] = useState(0); + const HEADER_HEIGHT = progressHeader ? 2 : 0; const TITLE_MARGIN = 1; const FOOTER_HEIGHT = 2; // DialogFooter + margin const overhead = HEADER_HEIGHT + TITLE_MARGIN + FOOTER_HEIGHT; + const listHeight = availableHeight ? Math.max(1, availableHeight - overhead) : undefined; - const maxQuestionHeight = - question.unconstrainedHeight && listHeight - ? // When unconstrained, give the question a majority of the vertical space (e.g., 70%). - // The options list will take the remaining space and scroll if necessary. - // This is more robust than calculating based on `selectionItems.length`, - // which can incorrectly shrink the question if there are many options. - Math.max(5, Math.floor(listHeight * 0.7)) - : 15; - const questionHeight = + + // Modulo the fixed overflow (overhead + 4 lines reserved for list), use all remaining height. + const maxQuestionHeight = listHeight ? Math.max(5, listHeight - 4) : 15; + + const questionHeightLimit = listHeight && !isAlternateBuffer - ? Math.min(maxQuestionHeight, Math.max(1, listHeight - DIALOG_PADDING)) + ? Math.min(maxQuestionHeight, listHeight) : undefined; + const maxItemsToShow = - listHeight && questionHeight - ? Math.max(1, Math.floor((listHeight - questionHeight) / 2)) + listHeight && actualQuestionHeight + ? Math.max(1, Math.floor((listHeight - actualQuestionHeight) / 2)) : selectionItems.length; return ( @@ -811,9 +812,10 @@ const ChoiceQuestionView: React.FC = ({ {progressHeader} { // hideToolIdentity is true for ask_user -> subtracts 4 instead of 6 // availableContentHeight = 19 - 4 = 15 // ToolConfirmationMessage handlesOwnUI=true -> returns full 15 - // AskUserDialog allocates questionHeight = Math.min(maxQuestionHeight, Math.max(5, listHeight - DIALOG_PADDING)). - // maxQuestionHeight = floor(15 * 0.7) = 10. - // 10 lines is enough for the 6-line question + padding. + // AskUserDialog allocates questionHeight = availableHeight - overhead - minListHeight. + // listHeight = 15 - overhead (Header:0, Margin:1, Footer:2) = 12. + // maxQuestionHeight = listHeight - 4 = 8. + // 8 lines is enough for the 6-line question. await waitFor(() => { expect(lastFrame()).toContain('Line 6'); expect(lastFrame()).not.toContain('lines hidden'); diff --git a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap index 06f509f1f6..e3e4336368 100644 --- a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap @@ -36,6 +36,22 @@ Enter to select · ↑/↓ to navigate · Esc to cancel " `; +exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 2`] = ` +"Choose an option + +▲ +● 1. Option 1 + Description 1 + 2. Option 2 + Description 2 + 3. Option 3 + Description 3 +▼ + +Enter to select · ↑/↓ to navigate · Esc to cancel +" +`; + exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = ` "Choose an option diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx index ee91d34f57..f365fea9a4 100644 --- a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx +++ b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx @@ -26,6 +26,7 @@ interface MaxSizedBoxProps { maxHeight?: number; overflowDirection?: 'top' | 'bottom'; additionalHiddenLinesCount?: number; + onHeightChange?: (height: number) => void; } /** @@ -38,6 +39,7 @@ export const MaxSizedBox: React.FC = ({ maxHeight, overflowDirection = 'top', additionalHiddenLinesCount = 0, + onHeightChange, }) => { const id = useId(); const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {}; @@ -80,6 +82,18 @@ export const MaxSizedBox: React.FC = ({ ? effectiveMaxHeight - 1 : effectiveMaxHeight; + const actualHeight = + visibleContentHeight !== undefined + ? Math.min(contentHeight, visibleContentHeight) + : contentHeight; + + const totalActualHeight = + actualHeight + (isOverflowing && effectiveMaxHeight !== undefined ? 1 : 0); + + useEffect(() => { + onHeightChange?.(totalActualHeight); + }, [totalActualHeight, onHeightChange]); + const hiddenLinesCount = visibleContentHeight !== undefined ? Math.max(0, contentHeight - visibleContentHeight)