diff --git a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx
index 68b662df7b..0b01f288db 100644
--- a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx
+++ b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx
@@ -8,6 +8,7 @@ import {
renderWithProviders,
persistentStateMock,
} from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AlternateBufferQuittingDisplay } from './AlternateBufferQuittingDisplay.js';
import { ToolCallStatus } from '../types.js';
@@ -90,6 +91,10 @@ const mockPendingHistoryItems: HistoryItemWithoutId[] = [
];
describe('AlternateBufferQuittingDisplay', () => {
+ const mockSettings = createMockSettings({
+ ui: { enableCompactToolOutput: false },
+ });
+
beforeEach(() => {
vi.clearAllMocks();
});
@@ -116,6 +121,7 @@ describe('AlternateBufferQuittingDisplay', () => {
history: mockHistory,
pendingHistoryItems: mockPendingHistoryItems,
},
+ settings: mockSettings,
},
);
expect(lastFrame()).toMatchSnapshot('with_history_and_pending');
@@ -131,6 +137,7 @@ describe('AlternateBufferQuittingDisplay', () => {
history: [],
pendingHistoryItems: [],
},
+ settings: mockSettings,
},
);
expect(lastFrame()).toMatchSnapshot('empty');
@@ -146,6 +153,7 @@ describe('AlternateBufferQuittingDisplay', () => {
history: mockHistory,
pendingHistoryItems: [],
},
+ settings: mockSettings,
},
);
expect(lastFrame()).toMatchSnapshot('with_history_no_pending');
@@ -161,6 +169,7 @@ describe('AlternateBufferQuittingDisplay', () => {
history: [],
pendingHistoryItems: mockPendingHistoryItems,
},
+ settings: mockSettings,
},
);
expect(lastFrame()).toMatchSnapshot('with_pending_no_history');
@@ -196,6 +205,7 @@ describe('AlternateBufferQuittingDisplay', () => {
history: [],
pendingHistoryItems,
},
+ settings: mockSettings,
},
);
const output = lastFrame();
@@ -219,6 +229,7 @@ describe('AlternateBufferQuittingDisplay', () => {
history,
pendingHistoryItems: [],
},
+ settings: mockSettings,
},
);
expect(lastFrame()).toMatchSnapshot('with_user_gemini_messages');
diff --git a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
index d108cb8911..e539cec78e 100644
--- a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
@@ -39,9 +39,15 @@ Tips for getting started:
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
- ✓ tool1 Description for tool 1
+╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool1 Description for tool 1 │
+│ │
+╰──────────────────────────────────────────────────────────────────────────╯
- ✓ tool2 Description for tool 2
+╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool2 Description for tool 2 │
+│ │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -79,9 +85,15 @@ Tips for getting started:
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
- ✓ tool1 Description for tool 1
+╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool1 Description for tool 1 │
+│ │
+╰──────────────────────────────────────────────────────────────────────────╯
- ✓ tool2 Description for tool 2
+╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool2 Description for tool 2 │
+│ │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap
index 2d05a2bf83..786867ccc0 100644
--- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap
@@ -28,12 +28,12 @@ exports[`SettingsDialog > Initial Rendering > should render settings list with v
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -74,12 +74,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -120,12 +120,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings d
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -166,12 +166,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'default state' correct
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -212,12 +212,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'file filtering setting
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -258,12 +258,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selec
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ > Apply To │
@@ -304,12 +304,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and numb
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -350,12 +350,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
@@ -396,12 +396,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Verbose Output History true │
-│ Show verbose output history. When enabled, output history will include autonomous to… │
-│ │
│ Auto Theme Switching true │
│ Automatically switch between default light and dark themes based on terminal backgro… │
│ │
+│ Terminal Background Polling Interval 60 │
+│ Interval in seconds to poll the terminal background color. │
+│ │
│ ▼ │
│ │
│ Apply To │
diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
index f255a1a690..0861c60e8e 100644
--- a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
@@ -222,6 +222,7 @@ describe('DenseToolMessage', () => {
);
const output = lastFrame();
expect(output).toContain('→ Found 2 matches');
+ // Matches are rendered in a secondary list for high-signal summaries
expect(output).toContain('file1.ts:10: match 1');
expect(output).toContain('file2.ts:20: match 2');
});
diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx
index 67af65af63..f9fb3f842c 100644
--- a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx
+++ b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx
@@ -39,13 +39,16 @@ const isFileDiff = (res: unknown): res is FileDiff =>
const isGrepResult = (res: unknown): res is GrepResult =>
typeof res === 'object' &&
res !== null &&
- 'matches' in res &&
- 'summary' in res;
+ 'summary' in res &&
+ ('matches' in res || 'payload' in res);
const isListResult = (
res: unknown,
): res is ListDirectoryResult | ReadManyFilesResult =>
- typeof res === 'object' && res !== null && 'files' in res && 'summary' in res;
+ typeof res === 'object' &&
+ res !== null &&
+ 'summary' in res &&
+ ('files' in res || 'include' in res);
const hasPayload = (
res: unknown,
@@ -63,22 +66,25 @@ const isTodoList = (res: unknown): res is { todos: unknown[] } =>
*/
const RenderItemsList: React.FC<{
- items: string[];
+ items?: string[];
maxVisible?: number;
-}> = ({ items, maxVisible = 20 }) => (
-
- {items.slice(0, maxVisible).map((item, i) => (
-
- {item}
-
- ))}
- {items.length > maxVisible && (
-
- ... and {items.length - maxVisible} more
-
- )}
-
-);
+}> = ({ items, maxVisible = 20 }) => {
+ if (!items || items.length === 0) return null;
+ return (
+
+ {items.slice(0, maxVisible).map((item, i) => (
+
+ {item}
+
+ ))}
+ {items.length > maxVisible && (
+
+ ... and {items.length - maxVisible} more
+
+ )}
+
+ );
+};
/**
* --- SCENARIO LOGIC (Pure Functions) ---
@@ -144,14 +150,15 @@ function getListResultData(
originalDescription?: string,
): ViewParts {
let description = originalDescription;
- const items: string[] = result.files;
- const maxVisible = 20;
+ const items: string[] = result.files ?? [];
+ const maxVisible = 10;
// Enhance with ReadManyFiles specific data if present
const rmf = result as ReadManyFilesResult;
if (toolName === 'ReadManyFiles' && rmf.include) {
const includePatterns = rmf.include.join(', ');
description = `Attempting to read files from ${includePatterns}`;
+ result.summary = `Read ${items.length} file(s)`;
}
const summary = → {result.summary};
@@ -165,21 +172,23 @@ function getListResultData(
? `Excluded patterns: ${rmf.excludes.slice(0, 3).join(', ')}${rmf.excludes.length > 3 ? '...' : ''}`
: undefined;
- const payload = (
-
-
- {skippedText && (
-
- {skippedText}
-
- )}
- {excludedText && (
-
- {excludedText}
-
- )}
-
- );
+ const hasItems = items.length > 0;
+ const payload =
+ hasItems || skippedText || excludedText ? (
+
+ {hasItems && }
+ {skippedText && (
+
+ {skippedText}
+
+ )}
+ {excludedText && (
+
+ {excludedText}
+
+ )}
+
+ ) : undefined;
return { description, summary, payload };
}
@@ -200,16 +209,19 @@ function getGenericSuccessData(
);
} else if (isGrepResult(resultDisplay)) {
summary = → {resultDisplay.summary};
- payload = (
-
- `${m.filePath}:${m.lineNumber}: ${m.line.trim()}`,
- )}
- maxVisible={10}
- />
-
- );
+ const matches = resultDisplay.matches ?? [];
+ if (matches.length > 0) {
+ payload = (
+
+ `${m.filePath}:${m.lineNumber}: ${m.line.trim()}`,
+ )}
+ maxVisible={10}
+ />
+
+ );
+ }
} else if (isTodoList(resultDisplay)) {
summary = (
@@ -280,6 +292,11 @@ export const DenseToolMessage: React.FC = (props) => {
if (isListResult(resultDisplay)) {
return getListResultData(resultDisplay, name, originalDescription);
}
+
+ if (isGrepResult(resultDisplay)) {
+ return getGenericSuccessData(resultDisplay, originalDescription);
+ }
+
if (status === ToolCallStatus.Success && resultDisplay) {
return getGenericSuccessData(resultDisplay, originalDescription);
}
diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
index 9063606146..c6f0a9f7df 100644
--- a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
+++ b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
@@ -45,14 +45,13 @@ index 0000000..e69de29
{ useAlternateBuffer },
);
await waitFor(() =>
- expect(mockColorizeCode).toHaveBeenCalledWith({
- code: 'print("hello world")',
- language: 'python',
- availableHeight: undefined,
- maxWidth: 80,
- theme: undefined,
- settings: expect.anything(),
- }),
+ expect(mockColorizeCode).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: 'print("hello world")',
+ language: 'python',
+ maxWidth: 80,
+ }),
+ ),
);
});
@@ -77,14 +76,13 @@ index 0000000..e69de29
{ useAlternateBuffer },
);
await waitFor(() =>
- expect(mockColorizeCode).toHaveBeenCalledWith({
- code: 'some content',
- language: null,
- availableHeight: undefined,
- maxWidth: 80,
- theme: undefined,
- settings: expect.anything(),
- }),
+ expect(mockColorizeCode).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: 'some content',
+ language: null,
+ maxWidth: 80,
+ }),
+ ),
);
});
@@ -105,14 +103,13 @@ index 0000000..e69de29
{ useAlternateBuffer },
);
await waitFor(() =>
- expect(mockColorizeCode).toHaveBeenCalledWith({
- code: 'some text content',
- language: null,
- availableHeight: undefined,
- maxWidth: 80,
- theme: undefined,
- settings: expect.anything(),
- }),
+ expect(mockColorizeCode).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: 'some text content',
+ language: null,
+ maxWidth: 80,
+ }),
+ ),
);
});
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
index 5368684ea2..23a04131c6 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
@@ -754,4 +754,140 @@ describe('', () => {
unmount();
});
});
+
+ describe('Compact Tool Output (Dense Mode)', () => {
+ const compactSettings = createMockSettings({
+ ui: { enableCompactToolOutput: true },
+ });
+
+ it('renders single tool call compactly', () => {
+ const toolCalls = [
+ createToolCall({
+ name: 'read_file',
+ description: 'packages/cli/src/app.tsx',
+ resultDisplay: 'Read 150 lines',
+ }),
+ ];
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ {
+ config: baseMockConfig,
+ settings: compactSettings,
+ uiState: {
+ pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
+ },
+ },
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ unmount();
+ });
+
+ it('renders multiple tool calls compactly without boxes', () => {
+ const toolCalls = [
+ createToolCall({
+ callId: 't1',
+ name: 'read_file',
+ description: 'file1.ts',
+ resultDisplay: 'Success',
+ }),
+ createToolCall({
+ callId: 't2',
+ name: 'grep_search',
+ description: 'search term',
+ resultDisplay: {
+ summary: 'Found 3 matches',
+ matches: [
+ { filePath: 'f1.ts', lineNumber: 10, line: 'match 1' },
+ { filePath: 'f2.ts', lineNumber: 20, line: 'match 2' },
+ { filePath: 'f3.ts', lineNumber: 30, line: 'match 3' },
+ ],
+ },
+ }),
+ ];
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ {
+ config: baseMockConfig,
+ settings: compactSettings,
+ uiState: {
+ pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
+ },
+ },
+ );
+ expect(lastFrame()).toMatchSnapshot();
+ unmount();
+ });
+
+ it('renders mixed boxed (shell) and dense tools correctly', () => {
+ const toolCalls = [
+ createToolCall({
+ callId: 'shell-1',
+ name: 'run_shell_command',
+ description: 'npm test',
+ status: ToolCallStatus.Success,
+ resultDisplay: 'All tests passed',
+ }),
+ createToolCall({
+ callId: 'file-1',
+ name: 'write_file',
+ description: 'packages/core/index.ts',
+ status: ToolCallStatus.Success,
+ resultDisplay: 'File updated',
+ }),
+ ];
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ {
+ config: baseMockConfig,
+ settings: compactSettings,
+ uiState: {
+ pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
+ },
+ },
+ );
+ // Boxed shell tool should have its own bottom border before the dense tool
+ expect(lastFrame()).toMatchSnapshot();
+ unmount();
+ });
+
+ it('renders confirming tools as boxed even in compact mode', () => {
+ const toolCalls = [
+ createToolCall({
+ callId: 'confirm-1',
+ name: 'write_file',
+ description: 'critical_file.ts',
+ status: ToolCallStatus.Confirming,
+ confirmationDetails: {
+ type: 'edit',
+ title: 'Apply edit',
+ fileName: 'critical_file.ts',
+ filePath: '/path/to/critical_file.ts',
+ fileDiff: 'diff...',
+ originalContent: 'old',
+ newContent: 'new',
+ onConfirm: vi.fn(),
+ },
+ }),
+ ];
+ // When confirming, it should be boxed for visibility/interactivity
+ const mockConfigNoEventDriven = makeFakeConfig({
+ ...baseMockConfig,
+ enableEventDrivenScheduler: false,
+ });
+
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ {
+ config: mockConfigNoEventDriven,
+ settings: compactSettings,
+ uiState: {
+ pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
+ },
+ },
+ );
+ expect(lastFrame()).toContain('Apply this change?');
+ expect(lastFrame()).toMatchSnapshot();
+ unmount();
+ });
+ });
});
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
index 5fa6a6f34f..ef580aaf64 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
@@ -190,6 +190,13 @@ export const ToolGroupMessage: React.FC = ({
!isShellToolCall &&
tool.status !== ToolCallStatus.Confirming;
+ const nextTool = visibleToolCalls[index + 1];
+ const nextIsDense =
+ nextTool &&
+ compactMode &&
+ !isShellTool(nextTool.name) &&
+ nextTool.status !== ToolCallStatus.Confirming;
+
if (useDenseView) {
return (
= ({
)}
)}
+ {/* If the NEXT tool is dense, we must close THIS tool's box now */}
+ {nextIsDense && (
+
+ )}
);
})}
@@ -336,12 +357,19 @@ export const ToolGroupMessage: React.FC = ({
/>
);
})()}
- {compactMode
- ? null
- : (borderBottomOverride ?? true) &&
- visibleToolCalls.length > 0 && (
-
- )}
+ {(() => {
+ const lastTool = visibleToolCalls[visibleToolCalls.length - 1];
+ const isShell = lastTool && isShellTool(lastTool.name);
+ const isConfirming =
+ lastTool && lastTool.status === ToolCallStatus.Confirming;
+ const isDense = compactMode && lastTool && !isShell && !isConfirming;
+
+ return isDense
+ ? null
+ : (borderBottomOverride ?? true) && visibleToolCalls.length > 0 && (
+
+ );
+ })()}
);
};
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap
index 1908088f75..1d98645ace 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap
@@ -1,12 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[` > Ask User Filtering > does NOT filter out ask_user when status is Error 1`] = `
-" x Ask User A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ x Ask User │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Ask User Filtering > does NOT filter out ask_user when status is Success 1`] = `
-" ✓ Ask User A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ Ask User │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -17,13 +25,24 @@ exports[` > Ask User Filtering > filters out ask_user when s
exports[` > Ask User Filtering > filters out ask_user when status is Pending 1`] = `""`;
exports[` > Ask User Filtering > shows other tools when ask_user is filtered out 1`] = `
-" ✓ other-tool A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ other-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Border Color Logic > uses gray border when all tools are successful and no shell commands 1`] = `
-" ✓ test-tool A tool for testing → Test result
- ✓ another-tool A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Test result │
+│ │
+│ ✓ another-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -36,7 +55,60 @@ exports[` > Border Color Logic > uses yellow border for shel
"
`;
-exports[` > Border Color Logic > uses yellow border when tools are pending 1`] = `""`;
+exports[` > Border Color Logic > uses yellow border when tools are pending 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ o test-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
+"
+`;
+
+exports[` > Compact Tool Output (Dense Mode) > renders confirming tools as boxed even in compact mode 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ? write_file critical_file.ts ← │
+│ │
+│ Test result │
+│ ╭──────────────────────────────────────────────────────────────────────╮ │
+│ │ │ │
+│ │ No changes detected. │ │
+│ │ │ │
+│ ╰──────────────────────────────────────────────────────────────────────╯ │
+│ Apply this change? │
+│ │
+│ ● 1. Allow once │
+│ 2. Allow for this session │
+│ 3. Modify with external editor │
+│ 4. No, suggest changes (esc) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────╯
+"
+`;
+
+exports[` > Compact Tool Output (Dense Mode) > renders mixed boxed (shell) and dense tools correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ run_shell_command npm test │
+│ │
+│ All tests passed │
+╰──────────────────────────────────────────────────────────────────────────╯
+ ✓ write_file packages/core/index.ts → File updated
+"
+`;
+
+exports[` > Compact Tool Output (Dense Mode) > renders multiple tool calls compactly without boxes 1`] = `
+" ✓ read_file file1.ts → Success
+ ✓ grep_search search term → Found 3 matches
+ f1.ts:10: match 1
+ f2.ts:20: match 2
+ f3.ts:30: match 3
+
+"
+`;
+
+exports[` > Compact Tool Output (Dense Mode) > renders single tool call compactly 1`] = `
+" ✓ read_file packages/cli/src/app.tsx → Read 150 lines
+"
+`;
exports[` > Confirmation Handling > renders confirmation with permanent approval disabled 1`] = `
"╭──────────────────────────────────────────────────────────────────────────╮
@@ -96,38 +168,60 @@ exports[` > Event-Driven Scheduler > hides confirming tools
exports[` > Event-Driven Scheduler > renders nothing when only tool is in-progress AskUser with borderBottom=false 1`] = `""`;
exports[` > Event-Driven Scheduler > shows only successful tools when mixed with confirming tools 1`] = `
-" ✓ success-tool A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ success-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Golden Snapshots > renders empty tool calls array 1`] = `""`;
exports[` > Golden Snapshots > renders header when scrolled 1`] = `
-" ✓ tool-1
- Description 1. This is a long description that will need to be truncate…
- → line1 line2 line3 line4 line5
- ✓ tool-2 Description 2 → line1 line2
-
-
-
-
-
-"
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-1 Description 1. This is a long description that will need to b… │
+│──────────────────────────────────────────────────────────────────────────│
+│ │ ▄
+│ ✓ tool-2 Description 2 │ █
+│ │ █
+│ line1 │ █
+│ line2 │ █
+╰──────────────────────────────────────────────────────────────────────────╯ █
+ █"
`;
exports[` > Golden Snapshots > renders mixed tool calls including shell command 1`] = `
-" ✓ read_file Read a file → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ read_file Read a file │
+│ │
+│ Test result │
│ │
│ ⊷ run_shell_command Run command │
│ │
│ Test result │
+│ │
+│ o write_file Write to file │
+│ │
+│ Test result │
╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Golden Snapshots > renders multiple tool calls with different statuses 1`] = `
-" ✓ successful-tool This tool succeeded → Test result
- x error-tool This tool failed → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ successful-tool This tool succeeded │
+│ │
+│ Test result │
+│ │
+│ o pending-tool This tool is pending │
+│ │
+│ Test result │
+│ │
+│ x error-tool This tool failed │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -141,7 +235,11 @@ exports[` > Golden Snapshots > renders shell command with ye
`;
exports[` > Golden Snapshots > renders single successful tool call 1`] = `
-" ✓ test-tool A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -162,42 +260,67 @@ exports[` > Golden Snapshots > renders tool call awaiting co
`;
exports[` > Golden Snapshots > renders tool call with outputFile 1`] = `
-" ✓ tool-with-file Tool that saved output to file → Test result
- (Output saved to: /path/to/output.txt)
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-with-file Tool that saved output to file │
+│ │
+│ Test result │
+│ Output too long and was saved to: /path/to/output.txt │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Golden Snapshots > renders two tool groups where only the last line of the previous group is visible 1`] = `
-" ✓ tool-1 Description 1 → line1 line2 line3 line4 line5
-
- ✓ tool-2 Description 2 → line1
-
-
-"
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-2 Description 2 │
+│ │
+│ line1 │ ▄
+╰──────────────────────────────────────────────────────────────────────────╯ █
+ █"
`;
exports[` > Golden Snapshots > renders when not focused 1`] = `
-" ✓ test-tool A tool for testing → Test result
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Golden Snapshots > renders with limited terminal height 1`] = `
-" ✓ tool-with-result Tool with output
- → This is a long result that might need height constraints
- ✓ another-tool Another tool → More output here
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-with-result Tool with output │
+│ │
+│ This is a long result that might need height constraints │
+│ │
+│ ✓ another-tool Another tool │
+│ │
+│ More output here │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[` > Golden Snapshots > renders with narrow terminal width 1`] = `
-" ✓ very-long-tool-name-that…
- This is a very long description…
- → Test result
+"╭──────────────────────────────────╮
+│ ✓ very-long-tool-name-that-mig… │
+│ │
+│ Test result │
+╰──────────────────────────────────╯
"
`;
exports[` > Height Calculation > calculates available height correctly with multiple tools with results 1`] = `
-" ✓ test-tool A tool for testing → Result 1
- ✓ test-tool A tool for testing → Result 2
- ✓ test-tool A tool for testing
+"╭──────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Result 1 │
+│ │
+│ ✓ test-tool A tool for testing │
+│ │
+│ Result 2 │
+│ │
+│ ✓ test-tool A tool for testing │
+│ │
+╰──────────────────────────────────────────────────────────────────────────╯
"
`;
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
index caea09beff..b188d908c9 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
+++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
@@ -9,6 +9,7 @@ import type { Mock, MockInstance } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
import { renderHookWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
import { waitFor } from '../../test-utils/async.js';
import { useGeminiStream } from './useGeminiStream.js';
import { useKeypress } from './useKeypress.js';
@@ -42,7 +43,7 @@ import type { Part, PartListUnion } from '@google/genai';
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
import type { SlashCommandProcessorResult } from '../types.js';
import { MessageType, StreamingState, ToolCallStatus } from '../types.js';
-import type { LoadedSettings } from '../../config/settings.js';
+// import type { LoadedSettings } from '../../config/settings.js';
// --- MOCKS ---
const mockSendMessageStream = vi
@@ -292,19 +293,13 @@ describe('useGeminiStream', () => {
vi.spyOn(coreEvents, 'emitFeedback');
});
- const mockLoadedSettings: LoadedSettings = {
- merged: {
- preferredEditor: 'vscode',
- ui: {
- enableCompactToolOutput: true,
- },
+ const mockLoadedSettings = createMockSettings({
+ ui: {
+ enableCompactToolOutput: false,
+ showCitations: true,
+ showModelInfoInChat: true,
},
- user: { path: '/user/settings.json', settings: {} },
- workspace: { path: '/workspace/.gemini/settings.json', settings: {} },
- errors: [],
- forScope: vi.fn(),
- setValue: vi.fn(),
- } as unknown as LoadedSettings;
+ });
const renderTestHook = (
initialToolCalls: TrackedToolCall[] = [],
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts
index c919c7923c..33528cb3d9 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.ts
+++ b/packages/cli/src/ui/hooks/useGeminiStream.ts
@@ -556,7 +556,7 @@ export const useGeminiStream = (
if (pendingHistoryItemRef.current) {
if (pendingHistoryItemRef.current.type === 'tool_group') {
// Mark all in-progress tools as Canceled when the turn is cancelled.
-
+
const toolGroup = pendingHistoryItemRef.current;
const updatedTools = toolGroup.tools.map((tool) => {
if (