From d785498f7cf8249f107b570f1afabd37cc7dfe37 Mon Sep 17 00:00:00 2001 From: Keith Guerin Date: Tue, 10 Feb 2026 00:41:01 -0800 Subject: [PATCH] feat(cli): finalize thinking UI with white unindented Thinking text --- .../ui/components/LoadingIndicator.test.tsx | 35 +++---------------- .../src/ui/components/LoadingIndicator.tsx | 5 +-- .../components/messages/ThinkingMessage.tsx | 9 +++-- packages/cli/src/ui/utils/terminalUtils.ts | 22 ------------ 4 files changed, 9 insertions(+), 62 deletions(-) diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx index cb4b65097a..fca56afd38 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx @@ -12,7 +12,6 @@ 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', () => ({ @@ -35,12 +34,7 @@ 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, @@ -264,34 +258,13 @@ describe('', () => { const output = lastFrame(); expect(output).toBeDefined(); if (output) { - expect(output).toContain('💬'); + expect(output).toContain('Thinking... '); expect(output).toContain('Thinking about something...'); expect(output).not.toContain('and other stuff.'); } unmount(); }); - it('should use ASCII fallback thought indicator when emoji is unavailable', async () => { - shouldUseEmojiMock.mockReturnValue(false); - const props = { - thought: { - subject: 'Thinking with fallback', - description: 'details', - }, - elapsedTime: 5, - }; - const { lastFrame, unmount, waitUntilReady } = renderWithContext( - , - StreamingState.Responding, - ); - await waitUntilReady(); - const output = lastFrame(); - expect(output).toContain('o Thinking with fallback'); - expect(output).not.toContain('💬'); - shouldUseEmojiMock.mockReturnValue(true); - unmount(); - }); - it('should prioritize thought.subject over currentLoadingPhrase', async () => { const props = { thought: { @@ -307,13 +280,13 @@ describe('', () => { ); await waitUntilReady(); const output = lastFrame(); - expect(output).toContain('💬'); + expect(output).toContain('Thinking... '); expect(output).toContain('This should be displayed'); expect(output).not.toContain('This should not be displayed'); unmount(); }); - it('should not display thought icon for non-thought loading phrases', async () => { + it('should not display thought indicator for non-thought loading phrases', async () => { const { lastFrame, unmount, waitUntilReady } = renderWithContext( ', () => { StreamingState.Responding, ); await waitUntilReady(); - expect(lastFrame()).not.toContain('💬'); + expect(lastFrame()).not.toContain('Thinking... '); unmount(); }); diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx index 3d6a838370..4cb49d5750 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.tsx @@ -15,7 +15,6 @@ 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; @@ -59,9 +58,7 @@ export const LoadingIndicator: React.FC = ({ const hasThoughtIndicator = currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE && Boolean(thought?.subject?.trim()); - const thinkingIndicator = hasThoughtIndicator - ? `${shouldUseEmoji() ? '💬' : 'o'} ` - : ''; + const thinkingIndicator = hasThoughtIndicator ? 'Thinking... ' : ''; const cancelAndTimerContent = showCancelAndTimer && diff --git a/packages/cli/src/ui/components/messages/ThinkingMessage.tsx b/packages/cli/src/ui/components/messages/ThinkingMessage.tsx index 010cef46ca..595f898ffa 100644 --- a/packages/cli/src/ui/components/messages/ThinkingMessage.tsx +++ b/packages/cli/src/ui/components/messages/ThinkingMessage.tsx @@ -131,15 +131,14 @@ export const ThinkingMessage: React.FC = ({ ); const fullSummaryDisplayLines = useMemo( - () => (fullLines.length > 0 ? wrapLineToWidth(fullLines[0], contentWidth) : []), + () => + fullLines.length > 0 ? wrapLineToWidth(fullLines[0], contentWidth) : [], [fullLines, contentWidth], ); const fullBodyDisplayLines = useMemo( () => - fullLines - .slice(1) - .flatMap((line) => wrapLineToWidth(line, contentWidth)), + fullLines.slice(1).flatMap((line) => wrapLineToWidth(line, contentWidth)), [fullLines, contentWidth], ); @@ -163,7 +162,7 @@ export const ThinkingMessage: React.FC = ({ <> {' '} - Thinking... + Thinking...{' '} diff --git a/packages/cli/src/ui/utils/terminalUtils.ts b/packages/cli/src/ui/utils/terminalUtils.ts index b0a3b93034..18cd08f952 100644 --- a/packages/cli/src/ui/utils/terminalUtils.ts +++ b/packages/cli/src/ui/utils/terminalUtils.ts @@ -43,25 +43,3 @@ 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; -}