fix: address PR #2770 review feedback for verbose/compact mode toggle

- Fix default value: compact mode (verboseMode=false) is now the default,
  matching PR description and intended UX
- Extract shared ToolStatusIndicator component to eliminate duplicate
  status icon rendering between ToolMessage and CompactToolGroupDisplay
- Memoize VerboseModeProvider context value to prevent unnecessary
  re-renders of all consumer components
- Clear frozenSnapshot on WaitingForConfirmation state to ensure tool
  confirmation UI remains interactive during mid-stream toggle
- Replace magic string 'Shell' with SHELL_NAME constant in ToolMessage
- Remove unused i18n translation keys (verbose/compact mode messages)
- Update snapshots for Footer and ToolGroupMessage tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chiga0 2026-04-06 15:07:59 +08:00
parent 6fd29b698b
commit 8d1866ca55
15 changed files with 145 additions and 170 deletions

View file

@ -8,14 +8,10 @@ import type React from 'react';
import { Box, Text } from 'ink';
import type { IndividualToolCallDisplay } from '../../types.js';
import { ToolCallStatus } from '../../types.js';
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import {
TOOL_STATUS,
SHELL_COMMAND_NAME,
SHELL_NAME,
} from '../../constants.js';
import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js';
import { theme } from '../../semantic-colors.js';
import { t } from '../../../i18n/index.js';
import { ToolStatusIndicator } from '../shared/ToolStatusIndicator.js';
interface CompactToolGroupDisplayProps {
toolCalls: IndividualToolCallDisplay[];
@ -50,8 +46,6 @@ function getActiveTool(
);
}
const STATUS_INDICATOR_WIDTH = 3;
export const CompactToolGroupDisplay: React.FC<
CompactToolGroupDisplayProps
> = ({ toolCalls, contentWidth }) => {
@ -80,40 +74,6 @@ export const CompactToolGroupDisplay: React.FC<
? activeTool.description.split('\n')[0]
: '';
const renderStatusIcon = () => {
switch (overallStatus) {
case ToolCallStatus.Executing:
return (
<GeminiRespondingSpinner
spinnerType="toggle"
nonRespondingDisplay={TOOL_STATUS.EXECUTING}
/>
);
case ToolCallStatus.Success:
return <Text color={theme.status.success}>{TOOL_STATUS.SUCCESS}</Text>;
case ToolCallStatus.Error:
return (
<Text color={theme.status.error} bold>
{TOOL_STATUS.ERROR}
</Text>
);
case ToolCallStatus.Confirming:
return (
<Text color={theme.status.warning}>{TOOL_STATUS.CONFIRMING}</Text>
);
case ToolCallStatus.Canceled:
return (
<Text color={theme.status.warning} bold>
{TOOL_STATUS.CANCELED}
</Text>
);
case ToolCallStatus.Pending:
return <Text color={theme.text.secondary}>{TOOL_STATUS.PENDING}</Text>;
default:
return <Text>{TOOL_STATUS.PENDING}</Text>;
}
};
return (
<Box
flexDirection="column"
@ -125,7 +85,7 @@ export const CompactToolGroupDisplay: React.FC<
>
{/* Status line: icon + tool name + description */}
<Box flexDirection="row">
<Box minWidth={STATUS_INDICATOR_WIDTH}>{renderStatusIcon()}</Box>
<ToolStatusIndicator status={overallStatus} name={activeTool.name} />
<Box flexGrow={1}>
<Text wrap="truncate-end">
<Text bold>{activeTool.name}</Text>

View file

@ -11,7 +11,6 @@ import { ToolCallStatus } from '../../types.js';
import { DiffRenderer } from './DiffRenderer.js';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import { AnsiOutputText } from '../AnsiOutput.js';
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import { TodoDisplay } from '../TodoDisplay.js';
import type {
@ -25,19 +24,19 @@ import type {
import { AgentExecutionDisplay } from '../subagents/index.js';
import { PlanSummaryDisplay } from '../PlanSummaryDisplay.js';
import { ShellInputPrompt } from '../ShellInputPrompt.js';
import {
SHELL_COMMAND_NAME,
SHELL_NAME,
TOOL_STATUS,
} from '../../constants.js';
import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js';
import { theme } from '../../semantic-colors.js';
import { useSettings } from '../../contexts/SettingsContext.js';
import type { LoadedSettings } from '../../../config/settings.js';
import { useVerboseMode } from '../../contexts/VerboseModeContext.js';
import {
ToolStatusIndicator,
STATUS_INDICATOR_WIDTH,
} from '../shared/ToolStatusIndicator.js';
const STATIC_HEIGHT = 1;
const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc.
const STATUS_INDICATOR_WIDTH = 3;
const MIN_LINES_SHOWN = 2; // show at least this many lines
// Large threshold to ensure we don't cause performance issues for very large
@ -269,7 +268,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
}) => {
const settings = useSettings();
const isThisShellFocused =
(name === SHELL_COMMAND_NAME || name === 'Shell') &&
(name === SHELL_COMMAND_NAME || name === SHELL_NAME) &&
status === ToolCallStatus.Executing &&
ptyId === activeShellPtyId &&
embeddedShellFocused;
@ -303,7 +302,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
}, [isThisShellFocused]);
const isThisShellFocusable =
(name === SHELL_COMMAND_NAME || name === 'Shell') &&
(name === SHELL_COMMAND_NAME || name === SHELL_NAME) &&
status === ToolCallStatus.Executing &&
config?.getShouldUseNodePtyShell();
@ -410,53 +409,6 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
);
};
type ToolStatusIndicatorProps = {
status: ToolCallStatus;
name: string;
};
const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
status,
name,
}) => {
const isShell = name === SHELL_COMMAND_NAME || name === SHELL_NAME;
const statusColor = isShell ? theme.ui.symbol : theme.status.warning;
return (
<Box minWidth={STATUS_INDICATOR_WIDTH}>
{status === ToolCallStatus.Pending && (
<Text color={theme.status.success}>{TOOL_STATUS.PENDING}</Text>
)}
{status === ToolCallStatus.Executing && (
<GeminiRespondingSpinner
spinnerType="toggle"
nonRespondingDisplay={TOOL_STATUS.EXECUTING}
/>
)}
{status === ToolCallStatus.Success && (
<Text color={theme.status.success} aria-label={'Success:'}>
{TOOL_STATUS.SUCCESS}
</Text>
)}
{status === ToolCallStatus.Confirming && (
<Text color={statusColor} aria-label={'Confirming:'}>
{TOOL_STATUS.CONFIRMING}
</Text>
)}
{status === ToolCallStatus.Canceled && (
<Text color={statusColor} aria-label={'Canceled:'} bold>
{TOOL_STATUS.CANCELED}
</Text>
)}
{status === ToolCallStatus.Error && (
<Text color={theme.status.error} aria-label={'Error:'} bold>
{TOOL_STATUS.ERROR}
</Text>
)}
</Box>
);
};
type ToolInfo = {
name: string;
description: string;

View file

@ -2,21 +2,22 @@
exports[`<ToolGroupMessage /> > Border Color Logic > uses gray border when all tools are successful and no shell commands 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-123]: ✓ test-tool - A tool for testing (medium) │
│ │
│MockTool[tool-2]: ✓ another-tool - A tool for testing (medium) │
│✓ another-tool A tool for testing │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Border Color Logic > uses yellow border for shell commands even when successful 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-123]: ✓ run_shell_command - A tool for testing (medium) │
│✓ run_shell_command A tool for testing │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Border Color Logic > uses yellow border when tools are pending 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-123]: o test-tool - A tool for testing (medium) │
│o test-tool A tool for testing │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
@ -29,40 +30,55 @@ exports[`<ToolGroupMessage /> > Confirmation Handling > shows confirmation dialo
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders empty tool calls array 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders empty tool calls array 1`] = `""`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls including shell command 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-1]: ✓ read_file - Read a file (medium) │
│ │
│MockTool[tool-2]: ⊷ run_shell_command - Run command (medium) │
│ │
│MockTool[tool-3]: o write_file - Write to file (medium) │
╰──────────────────────────────────────────────────────────────────────────────╯"
"
ERROR useStreamingContext must be used within a StreamingContextProvider
packages/cli/src/ui/contexts/StreamingContext.tsx:17:11
14: export const useStreamingContext = (): StreamingState => {
15: const context = React.useContext(StreamingContext);
16: if (context === undefined) {
17: throw new Error(
18: 'useStreamingContext must be used within a StreamingContextProvider',
19: );
20: }
- useStreamingContext (packages/cli/src/ui/contexts/StreamingContext.tsx:17:11)
- GeminiRespondingSpinner (packages/cli/src/ui/components/GeminiRespondingSpinner.tsx:31:26)
-Object.react-stack-bottom-fr (node_modules/react-reconciler/cjs/react-reconciler.development.js:1
me 5859:20)
- renderWithHooks (node_modules/react-reconciler/cjs/react-reconciler.development.js:3221:22)
-updateFunctionComponent (node_modules/react-reconciler/cjs/react-reconciler.development.js:6475:1
9)
- beginWork (node_modules/react-reconciler/cjs/react-reconciler.development.js:8009:18)
- runWithFiberInDEV (node_modules/react-reconciler/cjs/react-reconciler.development.js:1738:13)
- performUnitOfWork (node_modules/react-reconciler/cjs/react-reconciler.development.js:12834:22)
- workLoopSync (node_modules/react-reconciler/cjs/react-reconciler.development.js:12644:41)
- renderRootSync (node_modules/react-reconciler/cjs/react-reconciler.development.js:12624:11)
"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders multiple tool calls with different statuses 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-1]: ✓ successful-tool - This tool succeeded (medium) │
│ │
│MockTool[tool-2]: o pending-tool - This tool is pending (medium) │
│ │
│MockTool[tool-3]: x error-tool - This tool failed (medium) │
│x error-tool This tool failed │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders shell command with yellow border 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[shell-1]: ✓ run_shell_command - Execute shell command (medium) │
│✓ run_shell_command Execute shell command │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders single successful tool call 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-123]: ✓ test-tool - A tool for testing (medium) │
│✓ test-tool A tool for testing │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
@ -76,33 +92,28 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders tool call awaiting co
exports[`<ToolGroupMessage /> > Golden Snapshots > renders when not focused 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-123]: ✓ test-tool - A tool for testing (medium) │
│✓ test-tool A tool for testing │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders with limited terminal height 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-1]: ✓ tool-with-result - Tool with output (medium) │
│ │
│MockTool[tool-2]: ✓ another-tool - Another tool (medium) │
│✓ another-tool Another tool │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders with narrow terminal width 1`] = `
"╭──────────────────────────────────────╮
│MockTool[tool-123]: ✓ │
│very-long-tool-name-that-might-wrap - │
│This is a very long description that │
│might cause wrapping issues (medium) │
│✓ very-long-tool-name-that-might-wra…│
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────╯"
`;
exports[`<ToolGroupMessage /> > Height Calculation > calculates available height correctly with multiple tools with results 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│MockTool[tool-1]: ✓ test-tool - A tool for testing (medium) │
│ │
│MockTool[tool-2]: ✓ test-tool - A tool for testing (medium) │
│ │
│MockTool[tool-3]: ✓ test-tool - A tool for testing (medium) │
│✓ test-tool A tool for testing │
│Press Ctrl+O to show full tool output │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;