diff --git a/docs/cli/settings.md b/docs/cli/settings.md index ea5ea1ef93..faf3fca3f0 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -72,6 +72,7 @@ they appear in the UI. | Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` | | Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` | | Loading Phrases | `ui.loadingPhrases` | What to show while the model is working: tips, witty comments, both, or nothing. | `"tips"` | +| Error Verbosity | `ui.errorVerbosity` | Controls whether recoverable errors are hidden (low) or fully shown (full). | `"low"` | | Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` | ### IDE diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 5e7e7abacb..6bfd69203b 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -322,6 +322,12 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `"tips"` - **Values:** `"tips"`, `"witty"`, `"all"`, `"off"` +- **`ui.errorVerbosity`** (enum): + - **Description:** Controls whether recoverable errors are hidden (low) or + fully shown (full). + - **Default:** `"low"` + - **Values:** `"low"`, `"full"` + - **`ui.customWittyPhrases`** (array): - **Description:** Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults. diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index cf9dfc992f..17a916213f 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -96,6 +96,14 @@ describe('SettingsSchema', () => { ]); }); + it('should have errorVerbosity enum property', () => { + const definition = getSettingsSchema().ui?.properties?.errorVerbosity; + expect(definition).toBeDefined(); + expect(definition?.type).toBe('enum'); + expect(definition?.default).toBe('low'); + expect(definition?.options?.map((o) => o.value)).toEqual(['low', 'full']); + }); + it('should have checkpointing nested properties', () => { expect( getSettingsSchema().general?.properties?.checkpointing.properties diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index ca538c6a5a..599c8e586b 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -719,6 +719,20 @@ const SETTINGS_SCHEMA = { { value: 'off', label: 'Off' }, ], }, + errorVerbosity: { + type: 'enum', + label: 'Error Verbosity', + category: 'UI', + requiresRestart: false, + default: 'low', + description: + 'Controls whether recoverable errors are hidden (low) or fully shown (full).', + showInDialog: true, + options: [ + { value: 'low', label: 'Low' }, + { value: 'full', label: 'Full' }, + ], + }, customWittyPhrases: { type: 'array', label: 'Custom Witty Phrases', diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index df6f40abc4..d42cad8495 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -712,6 +712,7 @@ export const AppContainer = (props: AppContainerProps) => { settings, setModelSwitchedFromQuotaError, onShowAuthSelection: () => setAuthState(AuthState.Updating), + errorVerbosity: settings.merged.ui.errorVerbosity, }); // Derive auth state variables for backward compatibility with UIStateContext @@ -1688,6 +1689,7 @@ Logging in with Google... Restarting Gemini CLI to continue. retryStatus, loadingPhrasesMode: settings.merged.ui.loadingPhrases, customWittyPhrases: settings.merged.ui.customWittyPhrases, + errorVerbosity: settings.merged.ui.errorVerbosity, }); const handleGlobalKeypress = useCallback( diff --git a/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx b/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx index 108db073d5..6e6a4ce48c 100644 --- a/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx +++ b/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx @@ -4,12 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { render } from '../../test-utils/render.js'; +import { renderWithProviders } from '../../test-utils/render.js'; import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js'; import { describe, it, expect, vi } from 'vitest'; import type { ConsoleMessageItem } from '../types.js'; import { Box } from 'ink'; import type React from 'react'; +import { createMockSettings } from '../../test-utils/settings.js'; vi.mock('./shared/ScrollableList.js', () => ({ ScrollableList: ({ @@ -29,13 +30,18 @@ vi.mock('./shared/ScrollableList.js', () => ({ describe('DetailedMessagesDisplay', () => { it('renders nothing when messages are empty', async () => { - const { lastFrame, waitUntilReady, unmount } = render( + const { lastFrame, waitUntilReady, unmount } = renderWithProviders( , + { + settings: createMockSettings({ + merged: { ui: { errorVerbosity: 'full' } }, + }), + }, ); await waitUntilReady(); expect(lastFrame({ allowEmpty: true })).toBe(''); @@ -50,13 +56,18 @@ describe('DetailedMessagesDisplay', () => { { type: 'debug', content: 'Debug message', count: 1 }, ]; - const { lastFrame, waitUntilReady, unmount } = render( + const { lastFrame, waitUntilReady, unmount } = renderWithProviders( , + { + settings: createMockSettings({ + merged: { ui: { errorVerbosity: 'full' } }, + }), + }, ); await waitUntilReady(); const output = lastFrame(); @@ -65,18 +76,69 @@ describe('DetailedMessagesDisplay', () => { unmount(); }); + it('hides the F12 hint in low error verbosity mode', async () => { + const messages: ConsoleMessageItem[] = [ + { type: 'error', content: 'Error message', count: 1 }, + ]; + + const { lastFrame, waitUntilReady, unmount } = renderWithProviders( + , + { + settings: createMockSettings({ + merged: { ui: { errorVerbosity: 'low' } }, + }), + }, + ); + await waitUntilReady(); + expect(lastFrame()).not.toContain('(F12 to close)'); + unmount(); + }); + + it('shows the F12 hint in full error verbosity mode', async () => { + const messages: ConsoleMessageItem[] = [ + { type: 'error', content: 'Error message', count: 1 }, + ]; + + const { lastFrame, waitUntilReady, unmount } = renderWithProviders( + , + { + settings: createMockSettings({ + merged: { ui: { errorVerbosity: 'full' } }, + }), + }, + ); + await waitUntilReady(); + expect(lastFrame()).toContain('(F12 to close)'); + unmount(); + }); + it('renders message counts', async () => { const messages: ConsoleMessageItem[] = [ { type: 'log', content: 'Repeated message', count: 5 }, ]; - const { lastFrame, waitUntilReady, unmount } = render( + const { lastFrame, waitUntilReady, unmount } = renderWithProviders( , + { + settings: createMockSettings({ + merged: { ui: { errorVerbosity: 'full' } }, + }), + }, ); await waitUntilReady(); const output = lastFrame(); diff --git a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx index ff88afa888..097ebe1378 100644 --- a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx +++ b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx @@ -13,6 +13,8 @@ import { ScrollableList, type ScrollableListRef, } from './shared/ScrollableList.js'; +import { useConfig } from '../contexts/ConfigContext.js'; +import { useSettings } from '../contexts/SettingsContext.js'; interface DetailedMessagesDisplayProps { messages: ConsoleMessageItem[]; @@ -27,6 +29,10 @@ export const DetailedMessagesDisplay: React.FC< DetailedMessagesDisplayProps > = ({ messages, maxHeight, width, hasFocus }) => { const scrollableListRef = useRef>(null); + const config = useConfig(); + const settings = useSettings(); + const showHotkeyHint = + settings.merged.ui.errorVerbosity === 'full' || config.getDebugMode(); const borderAndPadding = 3; @@ -65,7 +71,10 @@ export const DetailedMessagesDisplay: React.FC< > - Debug Console (F12 to close) + Debug Console{' '} + {showHotkeyHint && ( + (F12 to close) + )} diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx index 2d8662cd5d..9c253fec92 100644 --- a/packages/cli/src/ui/components/Footer.test.tsx +++ b/packages/cli/src/ui/components/Footer.test.tsx @@ -8,7 +8,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderWithProviders } from '../../test-utils/render.js'; import { createMockSettings } from '../../test-utils/settings.js'; import { Footer } from './Footer.js'; -import { tildeifyPath, ToolCallDecision } from '@google/gemini-cli-core'; +import { + makeFakeConfig, + tildeifyPath, + ToolCallDecision, +} from '@google/gemini-cli-core'; import type { SessionStatsState } from '../contexts/SessionContext.js'; vi.mock('@google/gemini-cli-core', async (importOriginal) => { @@ -503,6 +507,75 @@ describe('