Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3aefc66
first commit
LyalinDotCom Dec 31, 2025
3a513f9
Fixing broken tests, adding UI setting to docs
LyalinDotCom Dec 31, 2025
d58a127
test commit
LyalinDotCom Jan 4, 2026
f351a33
Merge branch 'main' into show_thinking
LyalinDotCom Jan 4, 2026
648fd5d
Unify thought handling into pendingHistoryItem by removing thoughtsBu…
LyalinDotCom Jan 5, 2026
cbf2efd
Merge branch 'main' into show_thinking
LyalinDotCom Jan 31, 2026
1f9177f
Merge branch 'google-gemini:main' into show_thinking
LyalinDotCom Jan 31, 2026
c773ae4
Fix inline thinking in alternate buffer and pending renders
LyalinDotCom Jan 31, 2026
0d8a3a0
Merge remote-tracking branch 'origin/show_thinking' into show_thinking
LyalinDotCom Jan 31, 2026
66bbfa0
Fix missing bottom border in tool boxes
LyalinDotCom Jan 31, 2026
8841361
Stabilize inline thinking box during streaming
LyalinDotCom Jan 31, 2026
3649548
Show inline thoughts as individual bubbles
LyalinDotCom Jan 31, 2026
8b04b21
Add inline thinking mode helper
LyalinDotCom Jan 31, 2026
4620d5b
Simplify inline thinking display
LyalinDotCom Jan 31, 2026
a55d278
Add emoji thought bubble with fallback
LyalinDotCom Jan 31, 2026
c5d2c0b
Add reusable IconText with emoji fallback
LyalinDotCom Jan 31, 2026
f35143c
Regenerate settings schema and docs
LyalinDotCom Feb 1, 2026
2490db7
Remove deprecated inline thinking setting
LyalinDotCom Feb 1, 2026
874b8e1
Merge branch 'google-gemini:main' into show_thinking
LyalinDotCom Feb 1, 2026
bebc519
Fix inline thinking box width
LyalinDotCom Feb 1, 2026
e2cc788
misc.
LyalinDotCom Feb 1, 2026
62d0461
Merge branch 'main' into show_thinking
LyalinDotCom Feb 1, 2026
ab743ce
Merge branch 'main' into show_thinking
LyalinDotCom Feb 2, 2026
697bc92
refactor: move emoji detection to terminalUtils and add tests
LyalinDotCom Feb 2, 2026
b77bfac
Merge branch 'main' into show_thinking
LyalinDotCom Feb 2, 2026
71b3487
Merge branch 'main' into show_thinking
LyalinDotCom Feb 3, 2026
63eeca6
Merge branch 'main' into show_thinking
LyalinDotCom Feb 3, 2026
0d56279
Merge branch 'main' into show_thinking
LyalinDotCom Feb 3, 2026
4d95bb5
Merge branch 'main' into show_thinking
LyalinDotCom Feb 3, 2026
933725e
Fix review issues: copyright years, import ordering, test consistency
LyalinDotCom Feb 3, 2026
ec5297b
Merge branch 'main' into show_thinking
LyalinDotCom Feb 4, 2026
c2b57a3
Merge branch 'main' into show_thinking
LyalinDotCom Feb 4, 2026
963ec64
Merge branch 'main' into show_thinking
LyalinDotCom Feb 5, 2026
4ba053a
Merge branch 'main' into show_thinking
LyalinDotCom Feb 5, 2026
9ed674e
refactor(settings): consolidate inline thinking settings into single …
LyalinDotCom Feb 5, 2026
3cec6e7
Merge branch 'main' into show_thinking
LyalinDotCom Feb 5, 2026
b0af753
misc.
LyalinDotCom Feb 5, 2026
dc22d2b
Merge branch 'main' into show_thinking
LyalinDotCom Feb 5, 2026
cb79e66
Merge remote-tracking branch 'upstream/main' into show_thinking
LyalinDotCom Feb 6, 2026
0f559db
Merge branch 'main' into show_thinking
LyalinDotCom Feb 6, 2026
b25343a
Merge branch 'main' into show_thinking
LyalinDotCom Feb 6, 2026
492bfb6
Merge branch 'main' into show_thinking
LyalinDotCom Feb 6, 2026
a6c33a5
Merge branch 'main' into show_thinking
LyalinDotCom Feb 6, 2026
cf6b34e
Merge branch 'main' into show_thinking
LyalinDotCom Feb 6, 2026
8e7aca6
Merge remote-tracking branch 'upstream/main' into show_thinking
LyalinDotCom Feb 9, 2026
b93d610
feat(cli): streamline inline thinking display
LyalinDotCom Feb 9, 2026
4881112
feat(cli): streamline inline thinking and status indicator
LyalinDotCom Feb 9, 2026
b3d5296
Merge branch 'main' into show_thinking
LyalinDotCom Feb 9, 2026
664812d
chore(docs): regenerate settings schema and docs
LyalinDotCom Feb 9, 2026
3d93cfe
Merge branch 'main' into show_thinking
LyalinDotCom Feb 9, 2026
51cfd7d
Merge branch 'main' into show_thinking
LyalinDotCom Feb 9, 2026
3a7cca6
Merge branch 'main' into show_thinking
jacob314 Feb 9, 2026
848712f
fix: remove unused theme import in Composer.tsx
LyalinDotCom Feb 9, 2026
a3dba71
Merge branch 'main' into show_thinking
LyalinDotCom Feb 9, 2026
a2281e2
Merge branch 'main' into show_thinking
LyalinDotCom Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/cli/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ they appear in the UI.
| Auto Theme Switching | `ui.autoThemeSwitching` | Automatically switch between default light and dark themes based on terminal background color. | `true` |
| Terminal Background Polling Interval | `ui.terminalBackgroundPollingInterval` | Interval in seconds to poll the terminal background color. | `60` |
| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar | `false` |
| Inline Thinking | `ui.inlineThinkingMode` | Display model thinking inline: off or full. | `"off"` |
| Show Thoughts in Title | `ui.showStatusInTitle` | Show Gemini CLI model thoughts in the terminal window title during the working phase | `false` |
| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` |
| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` |
Expand Down
5 changes: 5 additions & 0 deletions docs/get-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes

- **`ui.inlineThinkingMode`** (enum):
- **Description:** Display model thinking inline: off or full.
- **Default:** `"off"`
- **Values:** `"off"`, `"full"`

- **`ui.showStatusInTitle`** (boolean):
- **Description:** Show Gemini CLI model thoughts in the terminal window title
during the working phase
Expand Down
13 changes: 13 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,19 @@ const SETTINGS_SCHEMA = {
description: 'Hide the window title bar',
showInDialog: true,
},
inlineThinkingMode: {
type: 'enum',
label: 'Inline Thinking',
category: 'UI',
requiresRestart: false,
default: 'off',
description: 'Display model thinking inline: off or full.',
showInDialog: true,
options: [
{ value: 'off', label: 'Off' },
{ value: 'full', label: 'Full' },
],
},
showStatusInTitle: {
type: 'boolean',
label: 'Show Thoughts in Title',
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/test-utils/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ vi.mock('../ui/utils/terminalUtils.js', () => ({
isLowColorDepth: vi.fn(() => false),
getColorDepth: vi.fn(() => 24),
isITerm2: vi.fn(() => false),
shouldUseEmoji: vi.fn(() => true),
}));

// Wrapper around ink-testing-library's render that ensures act() is called
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { Box, Text } from 'ink';
import { useUIState } from '../contexts/UIStateContext.js';
import { useSettings } from '../contexts/SettingsContext.js';
import { AppHeader } from './AppHeader.js';
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import { QuittingDisplay } from './QuittingDisplay.js';
Expand All @@ -15,15 +16,18 @@ import { useConfirmingTool } from '../hooks/useConfirmingTool.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { ToolStatusIndicator, ToolInfo } from './messages/ToolShared.js';
import { theme } from '../semantic-colors.js';
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';

export const AlternateBufferQuittingDisplay = () => {
const { version } = useAppContext();
const uiState = useUIState();
const settings = useSettings();
const config = useConfig();

const confirmingTool = useConfirmingTool();
const showPromptedTool =
config.isEventDrivenSchedulerEnabled() && confirmingTool !== null;
const inlineThinkingMode = getInlineThinkingMode(settings);

// We render the entire chat history and header here to ensure that the
// conversation history is visible to the user after the app quits and the
Expand All @@ -47,6 +51,7 @@ export const AlternateBufferQuittingDisplay = () => {
item={h}
isPending={false}
commands={uiState.slashCommands}
inlineThinkingMode={inlineThinkingMode}
/>
))}
{uiState.pendingHistoryItems.map((item, i) => (
Expand All @@ -59,6 +64,7 @@ export const AlternateBufferQuittingDisplay = () => {
isFocused={false}
activeShellPtyId={uiState.activePtyId}
embeddedShellFocused={uiState.embeddedShellFocused}
inlineThinkingMode={inlineThinkingMode}
/>
))}
{showPromptedTool && (
Expand Down
35 changes: 31 additions & 4 deletions packages/cli/src/ui/components/Composer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@ import { StreamingState, ToolCallStatus } from '../types.js';

// Mock child components
vi.mock('./LoadingIndicator.js', () => ({
LoadingIndicator: ({ thought }: { thought?: string }) => (
<Text>LoadingIndicator{thought ? `: ${thought}` : ''}</Text>
),
LoadingIndicator: ({
thought,
thoughtLabel,
}: {
thought?: { subject?: string } | string;
thoughtLabel?: string;
}) => {
const fallbackText =
typeof thought === 'string' ? thought : thought?.subject;
const text = thoughtLabel ?? fallbackText;
return <Text>LoadingIndicator{text ? `: ${text}` : ''}</Text>;
},
}));

vi.mock('./ContextSummaryDisplay.js', () => ({
Expand Down Expand Up @@ -276,7 +285,25 @@ describe('Composer', () => {
const { lastFrame } = renderComposer(uiState);

const output = lastFrame();
expect(output).toContain('LoadingIndicator');
expect(output).toContain('LoadingIndicator: Processing');
});

it('renders generic thinking text in loading indicator when full inline thinking is enabled', () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
thought: {
subject: 'Detailed in-history thought',
description: 'Full text is already in history',
},
});
const settings = createMockSettings({
ui: { inlineThinkingMode: 'full' },
});

const { lastFrame } = renderComposer(uiState, settings);

const output = lastFrame();
expect(output).toContain('LoadingIndicator: Thinking ...');
});

it('keeps shortcuts hint visible while loading', () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/ui/components/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import { StreamingState, ToolCallStatus } from '../types.js';
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
import { TodoTray } from './messages/Todo.js';
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';

export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
const config = useConfig();
Expand All @@ -38,6 +39,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
const uiState = useUIState();
const uiActions = useUIActions();
const { vimEnabled, vimMode } = useVimMode();
const inlineThinkingMode = getInlineThinkingMode(settings);
const terminalWidth = process.stdout.columns;
const isNarrow = isNarrowWidth(terminalWidth);
const debugConsoleMaxHeight = Math.floor(Math.max(terminalWidth * 0.2, 5));
Expand Down Expand Up @@ -117,6 +119,9 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
? undefined
: uiState.currentLoadingPhrase
}
thoughtLabel={
inlineThinkingMode === 'full' ? 'Thinking ...' : undefined
}
elapsedTime={uiState.elapsedTime}
/>
)}
Expand Down
36 changes: 36 additions & 0 deletions packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,42 @@ describe('<HistoryItemDisplay />', () => {
);
});

describe('thinking items', () => {
it('renders thinking item when enabled', () => {
const item: HistoryItem = {
...baseItem,
type: 'thinking',
thought: { subject: 'Thinking', description: 'test' },
};
const { lastFrame } = renderWithProviders(
<HistoryItemDisplay
{...baseItem}
item={item}
inlineThinkingMode="full"
/>,
);

expect(lastFrame()).toContain('Thinking');
});

it('does not render thinking item when disabled', () => {
const item: HistoryItem = {
...baseItem,
type: 'thinking',
thought: { subject: 'Thinking', description: 'test' },
};
const { lastFrame } = renderWithProviders(
<HistoryItemDisplay
{...baseItem}
item={item}
inlineThinkingMode="off"
/>,
);

expect(lastFrame()).toBe('');
});
});

describe.each([true, false])(
'gemini items (alternateBuffer=%s)',
(useAlternateBuffer) => {
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/ui/components/HistoryItemDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { McpStatus } from './views/McpStatus.js';
import { ChatList } from './views/ChatList.js';
import { HooksList } from './views/HooksList.js';
import { ModelMessage } from './messages/ModelMessage.js';
import { ThinkingMessage } from './messages/ThinkingMessage.js';
import type { InlineThinkingMode } from '../utils/inlineThinkingMode.js';

interface HistoryItemDisplayProps {
item: HistoryItem;
Expand All @@ -45,6 +47,7 @@ interface HistoryItemDisplayProps {
activeShellPtyId?: number | null;
embeddedShellFocused?: boolean;
availableTerminalHeightGemini?: number;
inlineThinkingMode?: InlineThinkingMode;
}

export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
Expand All @@ -57,12 +60,19 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
activeShellPtyId,
embeddedShellFocused,
availableTerminalHeightGemini,
inlineThinkingMode = 'off',
}) => {
const itemForDisplay = useMemo(() => escapeAnsiCtrlCodes(item), [item]);

return (
<Box flexDirection="column" key={itemForDisplay.id} width={terminalWidth}>
{/* Render standard message types */}
{itemForDisplay.type === 'thinking' && inlineThinkingMode !== 'off' && (
<ThinkingMessage
thought={itemForDisplay.thought}
terminalWidth={terminalWidth}
/>
)}
{itemForDisplay.type === 'user' && (
<UserMessage text={itemForDisplay.text} width={terminalWidth} />
)}
Expand Down
40 changes: 40 additions & 0 deletions packages/cli/src/ui/components/LoadingIndicator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => ({
Expand All @@ -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,
Expand Down Expand Up @@ -217,12 +223,33 @@ describe('<LoadingIndicator />', () => {
const output = lastFrame();
expect(output).toBeDefined();
if (output) {
expect(output).toContain('💬');
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', () => {
shouldUseEmojiMock.mockReturnValue(false);
const props = {
thought: {
subject: 'Thinking with fallback',
description: 'details',
},
elapsedTime: 5,
};
const { lastFrame, unmount } = renderWithContext(
<LoadingIndicator {...props} />,
StreamingState.Responding,
);
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', () => {
const props = {
thought: {
Expand All @@ -237,11 +264,24 @@ describe('<LoadingIndicator />', () => {
StreamingState.Responding,
);
const output = lastFrame();
expect(output).toContain('💬');
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', () => {
const { lastFrame, unmount } = renderWithContext(
<LoadingIndicator
currentLoadingPhrase="some random tip..."
elapsedTime={3}
/>,
StreamingState.Responding,
);
expect(lastFrame()).not.toContain('💬');
unmount();
});

it('should truncate long primary text instead of wrapping', () => {
const { lastFrame, unmount } = renderWithContext(
<LoadingIndicator
Expand Down
15 changes: 14 additions & 1 deletion packages/cli/src/ui/components/LoadingIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ 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;
elapsedTime: number;
inline?: boolean;
rightContent?: React.ReactNode;
thought?: ThoughtSummary | null;
thoughtLabel?: string;
showCancelAndTimer?: boolean;
}

Expand All @@ -31,6 +33,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
inline = false,
rightContent,
thought,
thoughtLabel,
showCancelAndTimer = true,
}) => {
const streamingState = useStreamingContext();
Expand All @@ -50,7 +53,15 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
const primaryText =
currentLoadingPhrase === INTERACTIVE_SHELL_WAITING_PHRASE
? currentLoadingPhrase
: thought?.subject || currentLoadingPhrase;
: thought?.subject
? (thoughtLabel ?? thought.subject)
: currentLoadingPhrase;
const hasThoughtIndicator =
currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE &&
Boolean(thought?.subject?.trim());
const thinkingIndicator = hasThoughtIndicator
? `${shouldUseEmoji() ? '💬' : 'o'} `
: '';

const cancelAndTimerContent =
showCancelAndTimer &&
Expand All @@ -72,6 +83,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
</Box>
{primaryText && (
<Text color={theme.text.accent} wrap="truncate-end">
{thinkingIndicator}
{primaryText}
</Text>
)}
Expand Down Expand Up @@ -105,6 +117,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
</Box>
{primaryText && (
<Text color={theme.text.accent} wrap="truncate-end">
{thinkingIndicator}
{primaryText}
</Text>
)}
Expand Down
Loading
Loading