diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx index 353e1ad535f..29e077e126a 100644 --- a/packages/cli/src/ui/components/Composer.test.tsx +++ b/packages/cli/src/ui/components/Composer.test.tsx @@ -455,6 +455,23 @@ describe('Composer', () => { expect(output).toContain('LoadingIndicator'); }); + it('renders both LoadingIndicator and ApprovalModeIndicator when streaming in full UI mode', () => { + const uiState = createMockUIState({ + streamingState: StreamingState.Responding, + thought: { + subject: 'Thinking', + description: '', + }, + showApprovalModeIndicator: ApprovalMode.PLAN, + }); + + const { lastFrame } = renderComposer(uiState); + + const output = lastFrame(); + expect(output).toContain('LoadingIndicator: Thinking'); + expect(output).toContain('ApprovalModeIndicator'); + }); + it('does NOT render LoadingIndicator when embedded shell is focused and background shell is NOT visible', () => { const uiState = createMockUIState({ streamingState: StreamingState.Responding, @@ -937,4 +954,50 @@ describe('Composer', () => { expect(lastFrame()).not.toContain('ShortcutsHelp'); }); }); + + describe('Snapshots', () => { + it('matches snapshot in idle state', () => { + const uiState = createMockUIState(); + const { lastFrame } = renderComposer(uiState); + expect(lastFrame()).toMatchSnapshot(); + }); + + it('matches snapshot while streaming', () => { + const uiState = createMockUIState({ + streamingState: StreamingState.Responding, + thought: { + subject: 'Thinking', + description: 'Thinking about the meaning of life...', + }, + }); + const { lastFrame } = renderComposer(uiState); + expect(lastFrame()).toMatchSnapshot(); + }); + + it('matches snapshot in narrow view', () => { + const uiState = createMockUIState({ + terminalWidth: 40, + }); + const { lastFrame } = renderComposer(uiState); + expect(lastFrame()).toMatchSnapshot(); + }); + + it('matches snapshot in minimal UI mode', () => { + const uiState = createMockUIState({ + cleanUiDetailsVisible: false, + }); + const { lastFrame } = renderComposer(uiState); + expect(lastFrame()).toMatchSnapshot(); + }); + + it('matches snapshot in minimal UI mode while loading', () => { + const uiState = createMockUIState({ + cleanUiDetailsVisible: false, + streamingState: StreamingState.Responding, + elapsedTime: 1000, + }); + const { lastFrame } = renderComposer(uiState); + expect(lastFrame()).toMatchSnapshot(); + }); + }); }); diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 8101e7303c2..1905f7798bb 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -141,8 +141,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { settings.merged.ui.showShortcutsHint && !hideShortcutsHintForSuggestions && !hideMinimalModeHintWhileBusy && - !hasPendingActionRequired && - (!showUiDetails || !showLoadingIndicator); + !hasPendingActionRequired; const showMinimalModeBleedThrough = !hideUiDetailsForSuggestions && Boolean(minimalModeBleedThrough); const showMinimalInlineLoading = !showUiDetails && showLoadingIndicator; @@ -189,7 +188,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { marginLeft={1} marginRight={isNarrow ? 0 : 1} flexDirection="row" - alignItems="center" + alignItems={isNarrow ? 'flex-start' : 'center'} flexGrow={1} > {showUiDetails && showLoadingIndicator && ( @@ -326,45 +325,51 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { {hasToast ? ( ) : ( - !showLoadingIndicator && ( - - {showApprovalIndicator && ( - - )} - {uiState.shellModeActive && ( - - - - )} - {showRawMarkdownIndicator && ( - - - - )} - - ) + + {showApprovalIndicator && ( + + )} + {!showLoadingIndicator && ( + <> + {uiState.shellModeActive && ( + + + + )} + {showRawMarkdownIndicator && ( + + + + )} + + )} + )} diff --git a/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap new file mode 100644 index 00000000000..eb3e0096c5c --- /dev/null +++ b/packages/cli/src/ui/components/__snapshots__/Composer.test.tsx.snap @@ -0,0 +1,44 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Composer > Snapshots > matches snapshot in idle state 1`] = ` +" + ShortcutsHint +──────────────────────────────────────────────────────────────────────────────────────────────────── + ApprovalModeIndicator StatusDisplay +InputPrompt: Type your message or @path/to/file +Footer" +`; + +exports[`Composer > Snapshots > matches snapshot in minimal UI mode 1`] = ` +" + ShortcutsHint +InputPrompt: Type your message or @path/to/file" +`; + +exports[`Composer > Snapshots > matches snapshot in minimal UI mode while loading 1`] = ` +" + LoadingIndicator +InputPrompt: Type your message or @path/to/file" +`; + +exports[`Composer > Snapshots > matches snapshot in narrow view 1`] = ` +" + +ShortcutsHint +──────────────────────────────────────── + ApprovalModeIndicator + +StatusDisplay +InputPrompt: Type your message or +@path/to/file +Footer" +`; + +exports[`Composer > Snapshots > matches snapshot while streaming 1`] = ` +" + LoadingIndicator: Thinking ShortcutsHint +──────────────────────────────────────────────────────────────────────────────────────────────────── + ApprovalModeIndicator +InputPrompt: Type your message or @path/to/file +Footer" +`;