From d63053cb59c6c1fbb7af2b27ca8160387f4ebc97 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 10 Mar 2026 14:29:29 -0700 Subject: [PATCH] fix(cli): stabilize prompt layout to prevent jumping when typing (#21081) --- .../cli/src/ui/components/Composer.test.tsx | 12 +------ packages/cli/src/ui/components/Composer.tsx | 35 ++++++++++--------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx index b1f804dd42..84f8d15a06 100644 --- a/packages/cli/src/ui/components/Composer.test.tsx +++ b/packages/cli/src/ui/components/Composer.test.tsx @@ -831,7 +831,7 @@ describe('Composer', () => { expect(lastFrame({ allowEmpty: true })).toContain('ShortcutsHint'); }); - it('does not show shortcuts hint immediately when buffer has text', async () => { + it('hides shortcuts hint when text is typed in buffer', async () => { const uiState = createMockUIState({ buffer: { text: 'hello' } as unknown as TextBuffer, cleanUiDetailsVisible: false, @@ -901,16 +901,6 @@ describe('Composer', () => { expect(lastFrame()).not.toContain('ShortcutsHint'); }); - it('hides shortcuts hint when text is typed in buffer', async () => { - const uiState = createMockUIState({ - buffer: { text: 'hello' } as unknown as TextBuffer, - }); - - const { lastFrame } = await renderComposer(uiState); - - expect(lastFrame()).not.toContain('ShortcutsHint'); - }); - it('hides shortcuts hint while loading in minimal mode', async () => { const uiState = createMockUIState({ cleanUiDetailsVisible: false, diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index d30f52dddf..0864b8f02b 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -171,10 +171,10 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { return () => clearTimeout(timeout); }, [canShowShortcutsHint]); + const shouldReserveSpaceForShortcutsHint = + settings.merged.ui.showShortcutsHint && !hideShortcutsHintForSuggestions; const showShortcutsHint = - settings.merged.ui.showShortcutsHint && - !hideShortcutsHintForSuggestions && - showShortcutsHintDebounced; + shouldReserveSpaceForShortcutsHint && showShortcutsHintDebounced; const showMinimalModeBleedThrough = !hideUiDetailsForSuggestions && Boolean(minimalModeBleedThrough); const showMinimalInlineLoading = !showUiDetails && showLoadingIndicator; @@ -187,7 +187,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { !showUiDetails && (showMinimalInlineLoading || showMinimalBleedThroughRow || - showShortcutsHint); + shouldReserveSpaceForShortcutsHint); return ( { marginTop={isNarrow ? 1 : 0} flexDirection="column" alignItems={isNarrow ? 'flex-start' : 'flex-end'} + minHeight={ + showUiDetails && shouldReserveSpaceForShortcutsHint ? 1 : 0 + } > {showUiDetails && showShortcutsHint && } @@ -304,11 +307,13 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { )} - {(showMinimalContextBleedThrough || showShortcutsHint) && ( + {(showMinimalContextBleedThrough || + shouldReserveSpaceForShortcutsHint) && ( {showMinimalContextBleedThrough && ( { terminalWidth={uiState.terminalWidth} /> )} - {showShortcutsHint && ( - - - - )} + + {showShortcutsHint && } + )}