From acf986eddcbdebca5991afc520eeb358f3853997 Mon Sep 17 00:00:00 2001 From: Keith Guerin Date: Tue, 10 Feb 2026 00:33:18 -0800 Subject: [PATCH] revert(cli): restore original emoji-based status bar thinking indicator --- .../ui/components/LoadingIndicator.test.tsx | 35 +++++++++---------- .../src/ui/components/LoadingIndicator.tsx | 5 ++- packages/cli/src/ui/utils/terminalUtils.ts | 22 ++++++++++++ 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx index 9df7dec8a0..cb4b65097a 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx @@ -12,6 +12,7 @@ import { StreamingContext } from '../contexts/StreamingContext.js'; import { StreamingState } from '../types.js'; import { vi } from 'vitest'; import * as useTerminalSize from '../hooks/useTerminalSize.js'; +import * as terminalUtils from '../utils/terminalUtils.js'; // Mock GeminiRespondingSpinner vi.mock('./GeminiRespondingSpinner.js', () => ({ @@ -34,7 +35,12 @@ vi.mock('../hooks/useTerminalSize.js', () => ({ useTerminalSize: vi.fn(), })); +vi.mock('../utils/terminalUtils.js', () => ({ + shouldUseEmoji: vi.fn(() => true), +})); + const useTerminalSizeMock = vi.mocked(useTerminalSize.useTerminalSize); +const shouldUseEmojiMock = vi.mocked(terminalUtils.shouldUseEmoji); const renderWithContext = ( ui: React.ReactElement, @@ -258,17 +264,15 @@ describe('', () => { const output = lastFrame(); expect(output).toBeDefined(); if (output) { - expect(output).toContain('Thinking: '); + expect(output).toContain('💬'); expect(output).toContain('Thinking about something...'); expect(output).not.toContain('and other stuff.'); } unmount(); }); -<<<<<<< HEAD - it('should prioritize thought.subject over currentLoadingPhrase', async () => { -======= - it('should use "Thinking: " as the thought indicator', () => { + it('should use ASCII fallback thought indicator when emoji is unavailable', async () => { + shouldUseEmojiMock.mockReturnValue(false); const props = { thought: { subject: 'Thinking with fallback', @@ -276,17 +280,19 @@ describe('', () => { }, elapsedTime: 5, }; - const { lastFrame, unmount } = renderWithContext( + const { lastFrame, unmount, waitUntilReady } = renderWithContext( , StreamingState.Responding, ); + await waitUntilReady(); const output = lastFrame(); - expect(output).toContain('Thinking: Thinking with fallback'); + expect(output).toContain('o Thinking with fallback'); + expect(output).not.toContain('💬'); + shouldUseEmojiMock.mockReturnValue(true); unmount(); }); - it('should prioritize thought.subject over currentLoadingPhrase', () => { ->>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator) + it('should prioritize thought.subject over currentLoadingPhrase', async () => { const props = { thought: { subject: 'This should be displayed', @@ -301,31 +307,22 @@ describe('', () => { ); await waitUntilReady(); const output = lastFrame(); - expect(output).toContain('Thinking: '); + expect(output).toContain('💬'); expect(output).toContain('This should be displayed'); expect(output).not.toContain('This should not be displayed'); unmount(); }); -<<<<<<< HEAD it('should not display thought icon for non-thought loading phrases', async () => { const { lastFrame, unmount, waitUntilReady } = renderWithContext( -======= - it('should not display thought indicator for non-thought loading phrases', () => { - const { lastFrame, unmount } = renderWithContext( ->>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator) , StreamingState.Responding, ); -<<<<<<< HEAD await waitUntilReady(); expect(lastFrame()).not.toContain('💬'); -======= - expect(lastFrame()).not.toContain('Thinking: '); ->>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator) unmount(); }); diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx index de4db32391..3d6a838370 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.tsx @@ -15,6 +15,7 @@ import { formatDuration } from '../utils/formatters.js'; import { useTerminalSize } from '../hooks/useTerminalSize.js'; import { isNarrowWidth } from '../utils/isNarrowWidth.js'; import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js'; +import { shouldUseEmoji } from '../utils/terminalUtils.js'; interface LoadingIndicatorProps { currentLoadingPhrase?: string; @@ -58,7 +59,9 @@ export const LoadingIndicator: React.FC = ({ const hasThoughtIndicator = currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE && Boolean(thought?.subject?.trim()); - const thinkingIndicator = hasThoughtIndicator ? 'Thinking: ' : ''; + const thinkingIndicator = hasThoughtIndicator + ? `${shouldUseEmoji() ? '💬' : 'o'} ` + : ''; const cancelAndTimerContent = showCancelAndTimer && diff --git a/packages/cli/src/ui/utils/terminalUtils.ts b/packages/cli/src/ui/utils/terminalUtils.ts index 18cd08f952..b0a3b93034 100644 --- a/packages/cli/src/ui/utils/terminalUtils.ts +++ b/packages/cli/src/ui/utils/terminalUtils.ts @@ -43,3 +43,25 @@ export function isITerm2(): boolean { export function resetITerm2Cache(): void { cachedIsITerm2 = undefined; } + +/** + * Returns true if the terminal likely supports emoji. + */ +export function shouldUseEmoji(): boolean { + const locale = ( + process.env['LC_ALL'] || + process.env['LC_CTYPE'] || + process.env['LANG'] || + '' + ).toLowerCase(); + const supportsUtf8 = locale.includes('utf-8') || locale.includes('utf8'); + if (!supportsUtf8) { + return false; + } + + if (process.env['TERM'] === 'linux') { + return false; + } + + return true; +}