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"
+`;