diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 83b92fb3c8..54dcec335e 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -60,6 +60,7 @@ they appear in the UI. | Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` | | Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` | | 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` | | Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` | | Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` | diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index d7885df084..fac2845d60 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -261,6 +261,10 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `true` - **Requires restart:** Yes +- **`ui.showSpinner`** (boolean): + - **Description:** Show the spinner during operations. + - **Default:** `true` + - **`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.ts b/packages/cli/src/config/settingsSchema.ts index 6a881fdeff..7ad6673768 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -555,6 +555,15 @@ const SETTINGS_SCHEMA = { 'Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled.', showInDialog: true, }, + showSpinner: { + type: 'boolean', + label: 'Show Spinner', + category: 'UI', + requiresRestart: false, + default: true, + description: 'Show the spinner during operations.', + showInDialog: true, + }, customWittyPhrases: { type: 'array', label: 'Custom Witty Phrases', diff --git a/packages/cli/src/ui/components/CliSpinner.test.tsx b/packages/cli/src/ui/components/CliSpinner.test.tsx index bbea23ab5d..76522c41c1 100644 --- a/packages/cli/src/ui/components/CliSpinner.test.tsx +++ b/packages/cli/src/ui/components/CliSpinner.test.tsx @@ -4,7 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { render } from '../../test-utils/render.js'; +import { + renderWithProviders, + createMockSettings, +} from '../../test-utils/render.js'; import { CliSpinner } from './CliSpinner.js'; import { debugState } from '../debug.js'; import { describe, it, expect, beforeEach } from 'vitest'; @@ -16,9 +19,15 @@ describe('', () => { it('should increment debugNumAnimatedComponents on mount and decrement on unmount', () => { expect(debugState.debugNumAnimatedComponents).toBe(0); - const { unmount } = render(); + const { unmount } = renderWithProviders(); expect(debugState.debugNumAnimatedComponents).toBe(1); unmount(); expect(debugState.debugNumAnimatedComponents).toBe(0); }); + + it('should not render when showSpinner is false', () => { + const settings = createMockSettings({ ui: { showSpinner: false } }); + const { lastFrame } = renderWithProviders(, { settings }); + expect(lastFrame()).toBe(''); + }); }); diff --git a/packages/cli/src/ui/components/CliSpinner.tsx b/packages/cli/src/ui/components/CliSpinner.tsx index 6795bf2670..66cb7a0281 100644 --- a/packages/cli/src/ui/components/CliSpinner.tsx +++ b/packages/cli/src/ui/components/CliSpinner.tsx @@ -7,16 +7,27 @@ import Spinner from 'ink-spinner'; import { type ComponentProps, useEffect } from 'react'; import { debugState } from '../debug.js'; +import { useSettings } from '../contexts/SettingsContext.js'; export type SpinnerProps = ComponentProps; export const CliSpinner = (props: SpinnerProps) => { + const settings = useSettings(); + const shouldShow = settings.merged.ui?.showSpinner !== false; + useEffect(() => { - debugState.debugNumAnimatedComponents++; - return () => { - debugState.debugNumAnimatedComponents--; - }; - }, []); + if (shouldShow) { + debugState.debugNumAnimatedComponents++; + return () => { + debugState.debugNumAnimatedComponents--; + }; + } + return undefined; + }, [shouldShow]); + + if (!shouldShow) { + return null; + } return ; }; diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx index 88c3fb2197..622daa834d 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { render } from '../../../test-utils/render.js'; +import { renderWithProviders } from '../../../test-utils/render.js'; import type { CompressionDisplayProps } from './CompressionMessage.js'; import { CompressionMessage } from './CompressionMessage.js'; import { CompressionStatus } from '@google/gemini-cli-core'; @@ -27,7 +27,9 @@ describe('', () => { describe('pending state', () => { it('renders pending message when compression is in progress', () => { const props = createCompressionProps({ isPending: true }); - const { lastFrame, unmount } = render(); + const { lastFrame, unmount } = renderWithProviders( + , + ); const output = lastFrame(); expect(output).toContain('Compressing chat history'); @@ -43,7 +45,9 @@ describe('', () => { newTokenCount: 50, compressionStatus: CompressionStatus.COMPRESSED, }); - const { lastFrame, unmount } = render(); + const { lastFrame, unmount } = renderWithProviders( + , + ); const output = lastFrame(); expect(output).toContain('✦'); @@ -66,7 +70,7 @@ describe('', () => { newTokenCount: newTokens, compressionStatus: CompressionStatus.COMPRESSED, }); - const { lastFrame, unmount } = render( + const { lastFrame, unmount } = renderWithProviders( , ); const output = lastFrame(); @@ -91,7 +95,9 @@ describe('', () => { compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); - const { lastFrame, unmount } = render(); + const { lastFrame, unmount } = renderWithProviders( + , + ); const output = lastFrame(); expect(output).toContain('✦'); @@ -109,7 +115,9 @@ describe('', () => { compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); - const { lastFrame, unmount } = render(); + const { lastFrame, unmount } = renderWithProviders( + , + ); const output = lastFrame(); expect(output).toContain( @@ -146,7 +154,7 @@ describe('', () => { newTokenCount: newTokens, compressionStatus: CompressionStatus.COMPRESSED, }); - const { lastFrame, unmount } = render( + const { lastFrame, unmount } = renderWithProviders( , ); const output = lastFrame(); @@ -171,7 +179,7 @@ describe('', () => { compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); - const { lastFrame, unmount } = render( + const { lastFrame, unmount } = renderWithProviders( , ); const output = lastFrame(); @@ -199,7 +207,7 @@ describe('', () => { compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); - const { lastFrame, unmount } = render( + const { lastFrame, unmount } = renderWithProviders( , ); const output = lastFrame(); @@ -218,7 +226,9 @@ describe('', () => { isPending: false, compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY, }); - const { lastFrame, unmount } = render(); + const { lastFrame, unmount } = renderWithProviders( + , + ); const output = lastFrame(); expect(output).toContain('✦'); @@ -234,7 +244,9 @@ describe('', () => { compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR, }); - const { lastFrame, unmount } = render(); + const { lastFrame, unmount } = renderWithProviders( + , + ); const output = lastFrame(); expect(output).toContain( diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index bd0b406eaf..fdaf50e933 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -323,6 +323,13 @@ "default": true, "type": "boolean" }, + "showSpinner": { + "title": "Show Spinner", + "description": "Show the spinner during operations.", + "markdownDescription": "Show the spinner during operations.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`", + "default": true, + "type": "boolean" + }, "customWittyPhrases": { "title": "Custom Witty Phrases", "description": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.",