diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 9a12195a15..264ad96593 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -37,8 +37,6 @@ import { Footer } from './Footer.js'; import { ShowMoreLines } from './ShowMoreLines.js'; import { QueuedMessageDisplay } from './QueuedMessageDisplay.js'; import { OverflowProvider } from '../contexts/OverflowContext.js'; -import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js'; -import { HookStatusDisplay } from './HookStatusDisplay.js'; import { ConfigInitDisplay } from './ConfigInitDisplay.js'; import { TodoTray } from './messages/Todo.js'; @@ -279,7 +277,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { const showRow1 = showUiDetails || showRow1_MiniMode; const showRow2 = showUiDetails || showRow2_MiniMode; - const showMinimalInlineLoading = !showUiDetails && showLoadingIndicator; const showMinimalBleedThroughRow = !showUiDetails && showRow2_MiniMode; const renderAmbientNode = () => { @@ -311,41 +308,47 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { }; const renderStatusNode = () => { + if (!hasUserHooks && !showLoadingIndicator) return null; + if (hasUserHooks) { const activeHook = userHooks[0]; const hookIcon = activeHook?.eventName?.startsWith('After') ? '↩' : '↪'; + const label = userHooks.length > 1 ? 'Executing Hooks' : 'Executing Hook'; + const displayNames = userHooks.map((h) => { + let name = h.name; + if (h.index && h.total && h.total > 1) name += ` (${h.index}/${h.total})`; + return name; + }); + const hookText = `${label}: ${displayNames.join(', ')}`; - return ( - - - - {showWit && uiState.currentWittyPhrase && ( - - {uiState.currentWittyPhrase} - - )} - - ); - } - - if (showLoadingIndicator) { return ( ); } - return null; + + return ( + + ); }; const statusNode = renderStatusNode(); @@ -355,22 +358,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { */ const renderMinimalMetaRowContent = () => ( - {showMinimalInlineLoading && ( - - )} - {hasUserHooks && ( - - - - - )} + {renderStatusNode()} {showMinimalBleedThroughRow && ( {miniMode_ShowApprovalMode && modeContentObj && ( diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx index 1aba99ebc1..e5e0f6bb91 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx @@ -448,4 +448,20 @@ describe('', () => { unmount(); }); }); + + it('should use spinnerIcon when provided', async () => { + const props = { + currentLoadingPhrase: 'Confirm action', + elapsedTime: 10, + spinnerIcon: '?', + }; + const { lastFrame, waitUntilReady } = renderWithContext( + , + StreamingState.WaitingForConfirmation, + ); + await waitUntilReady(); + const output = lastFrame(); + expect(output).toContain('?'); + expect(output).not.toContain('⠏'); + }); }); diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx index d0a030a807..a48451b26c 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.tsx @@ -29,6 +29,8 @@ interface LoadingIndicatorProps { thoughtLabel?: string; showCancelAndTimer?: boolean; forceRealStatusOnly?: boolean; + spinnerIcon?: string; + isHookActive?: boolean; } export const LoadingIndicator: React.FC = ({ @@ -42,6 +44,8 @@ export const LoadingIndicator: React.FC = ({ thoughtLabel, showCancelAndTimer = true, forceRealStatusOnly = false, + spinnerIcon, + isHookActive = false, }) => { const streamingState = useStreamingContext(); const { columns: terminalWidth } = useTerminalSize(); @@ -91,10 +95,12 @@ export const LoadingIndicator: React.FC = ({ {primaryText && ( @@ -133,10 +139,12 @@ export const LoadingIndicator: React.FC = ({ {primaryText && (