revert(cli): restore original emoji-based status bar thinking indicator

This commit is contained in:
Keith Guerin
2026-02-10 00:33:18 -08:00
parent 6bc4d99377
commit acf986eddc
3 changed files with 42 additions and 20 deletions
@@ -12,6 +12,7 @@ import { StreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js'; import { StreamingState } from '../types.js';
import { vi } from 'vitest'; import { vi } from 'vitest';
import * as useTerminalSize from '../hooks/useTerminalSize.js'; import * as useTerminalSize from '../hooks/useTerminalSize.js';
import * as terminalUtils from '../utils/terminalUtils.js';
// Mock GeminiRespondingSpinner // Mock GeminiRespondingSpinner
vi.mock('./GeminiRespondingSpinner.js', () => ({ vi.mock('./GeminiRespondingSpinner.js', () => ({
@@ -34,7 +35,12 @@ vi.mock('../hooks/useTerminalSize.js', () => ({
useTerminalSize: vi.fn(), useTerminalSize: vi.fn(),
})); }));
vi.mock('../utils/terminalUtils.js', () => ({
shouldUseEmoji: vi.fn(() => true),
}));
const useTerminalSizeMock = vi.mocked(useTerminalSize.useTerminalSize); const useTerminalSizeMock = vi.mocked(useTerminalSize.useTerminalSize);
const shouldUseEmojiMock = vi.mocked(terminalUtils.shouldUseEmoji);
const renderWithContext = ( const renderWithContext = (
ui: React.ReactElement, ui: React.ReactElement,
@@ -258,17 +264,15 @@ describe('<LoadingIndicator />', () => {
const output = lastFrame(); const output = lastFrame();
expect(output).toBeDefined(); expect(output).toBeDefined();
if (output) { if (output) {
expect(output).toContain('Thinking: '); expect(output).toContain('💬');
expect(output).toContain('Thinking about something...'); expect(output).toContain('Thinking about something...');
expect(output).not.toContain('and other stuff.'); expect(output).not.toContain('and other stuff.');
} }
unmount(); unmount();
}); });
<<<<<<< HEAD it('should use ASCII fallback thought indicator when emoji is unavailable', async () => {
it('should prioritize thought.subject over currentLoadingPhrase', async () => { shouldUseEmojiMock.mockReturnValue(false);
=======
it('should use "Thinking: " as the thought indicator', () => {
const props = { const props = {
thought: { thought: {
subject: 'Thinking with fallback', subject: 'Thinking with fallback',
@@ -276,17 +280,19 @@ describe('<LoadingIndicator />', () => {
}, },
elapsedTime: 5, elapsedTime: 5,
}; };
const { lastFrame, unmount } = renderWithContext( const { lastFrame, unmount, waitUntilReady } = renderWithContext(
<LoadingIndicator {...props} />, <LoadingIndicator {...props} />,
StreamingState.Responding, StreamingState.Responding,
); );
await waitUntilReady();
const output = lastFrame(); 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(); unmount();
}); });
it('should prioritize thought.subject over currentLoadingPhrase', () => { it('should prioritize thought.subject over currentLoadingPhrase', async () => {
>>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator)
const props = { const props = {
thought: { thought: {
subject: 'This should be displayed', subject: 'This should be displayed',
@@ -301,31 +307,22 @@ describe('<LoadingIndicator />', () => {
); );
await waitUntilReady(); await waitUntilReady();
const output = lastFrame(); const output = lastFrame();
expect(output).toContain('Thinking: '); expect(output).toContain('💬');
expect(output).toContain('This should be displayed'); expect(output).toContain('This should be displayed');
expect(output).not.toContain('This should not be displayed'); expect(output).not.toContain('This should not be displayed');
unmount(); unmount();
}); });
<<<<<<< HEAD
it('should not display thought icon for non-thought loading phrases', async () => { it('should not display thought icon for non-thought loading phrases', async () => {
const { lastFrame, unmount, waitUntilReady } = renderWithContext( 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)
<LoadingIndicator <LoadingIndicator
currentLoadingPhrase="some random tip..." currentLoadingPhrase="some random tip..."
elapsedTime={3} elapsedTime={3}
/>, />,
StreamingState.Responding, StreamingState.Responding,
); );
<<<<<<< HEAD
await waitUntilReady(); await waitUntilReady();
expect(lastFrame()).not.toContain('💬'); 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(); unmount();
}); });
@@ -15,6 +15,7 @@ import { formatDuration } from '../utils/formatters.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js'; import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js'; import { isNarrowWidth } from '../utils/isNarrowWidth.js';
import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js'; import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js';
import { shouldUseEmoji } from '../utils/terminalUtils.js';
interface LoadingIndicatorProps { interface LoadingIndicatorProps {
currentLoadingPhrase?: string; currentLoadingPhrase?: string;
@@ -58,7 +59,9 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
const hasThoughtIndicator = const hasThoughtIndicator =
currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE && currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE &&
Boolean(thought?.subject?.trim()); Boolean(thought?.subject?.trim());
const thinkingIndicator = hasThoughtIndicator ? 'Thinking: ' : ''; const thinkingIndicator = hasThoughtIndicator
? `${shouldUseEmoji() ? '💬' : 'o'} `
: '';
const cancelAndTimerContent = const cancelAndTimerContent =
showCancelAndTimer && showCancelAndTimer &&
@@ -43,3 +43,25 @@ export function isITerm2(): boolean {
export function resetITerm2Cache(): void { export function resetITerm2Cache(): void {
cachedIsITerm2 = undefined; 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;
}