diff --git a/packages/cli/src/test-utils/AppRig.tsx b/packages/cli/src/test-utils/AppRig.tsx
index 548372a139..951a38cefc 100644
--- a/packages/cli/src/test-utils/AppRig.tsx
+++ b/packages/cli/src/test-utils/AppRig.tsx
@@ -131,12 +131,43 @@ class MockExtensionManager extends ExtensionLoader {
};
}
+// Mock terminalCapabilityManager to avoid terminal setup prompt during tests
+vi.mock('../ui/utils/terminalCapabilityManager.js', async (importOriginal) => {
+ const actual =
+ await importOriginal<
+ typeof import('../ui/utils/terminalCapabilityManager.js')
+ >();
+ const mockedManager = Object.create(
+ Object.getPrototypeOf(actual.terminalCapabilityManager),
+ );
+ Object.assign(mockedManager, actual.terminalCapabilityManager, {
+ isKittyProtocolEnabled: () => true,
+ enableKittyProtocol: vi.fn(),
+ disableKittyProtocol: vi.fn(),
+ enableSupportedModes: vi.fn(),
+ disableSupportedModes: vi.fn(),
+ onSupportChange: vi.fn(),
+ offSupportChange: vi.fn(),
+ });
+ return {
+ ...actual,
+ terminalCapabilityManager: mockedManager,
+ };
+});
+
+vi.mock('../ui/components/GeminiSpinner.js', async () => {
+ const React = await import('react');
+ const { Text } = await import('ink');
+ return {
+ GeminiSpinner: () => React.createElement(Text, null, '...'),
+ };
+});
+
// Mock GeminiRespondingSpinner to disable animations (avoiding 'act()' warnings) without triggering screen reader mode.
vi.mock('../ui/components/GeminiRespondingSpinner.js', async () => {
const React = await import('react');
const { Text } = await import('ink');
return {
- GeminiSpinner: () => React.createElement(Text, null, '...'),
GeminiRespondingSpinner: ({
nonRespondingDisplay,
}: {
@@ -203,6 +234,9 @@ export class AppRig {
resetSettingsCacheForTesting();
this.settings = this.createRigSettings();
+ // Disable the terminal setup prompt globally for AppRig tests.
+ persistentStateMock.set('terminalSetupPromptShown', true);
+
const approvalMode =
this.options.configOverrides?.approvalMode ?? ApprovalMode.DEFAULT;
const policyEngineConfig = await createPolicyEngineConfig(
@@ -280,6 +314,10 @@ export class AppRig {
enabled: false,
hasSeenNudge: true,
},
+ ui: {
+ hasSeenTerminalSetupPrompt: true,
+ showSpinner: false,
+ },
},
originalSettings: {},
},
@@ -299,6 +337,8 @@ export class AppRig {
},
ui: {
useAlternateBuffer: false,
+ hasSeenTerminalSetupPrompt: true,
+ showSpinner: false,
},
},
});
diff --git a/packages/cli/src/ui/auth/AuthInProgress.tsx b/packages/cli/src/ui/auth/AuthInProgress.tsx
index 03d609c444..4aacd4c08c 100644
--- a/packages/cli/src/ui/auth/AuthInProgress.tsx
+++ b/packages/cli/src/ui/auth/AuthInProgress.tsx
@@ -7,7 +7,7 @@
import type React from 'react';
import { useState, useEffect } from 'react';
import { Box, Text } from 'ink';
-import { CliSpinner } from '../components/CliSpinner.js';
+import { BrailleAnimation } from '../components/BrailleAnimation.js';
import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js';
@@ -53,8 +53,8 @@ export function AuthInProgress({
) : (
- Waiting for authentication... (Press Esc
- or Ctrl+C to cancel)
+ Waiting for authentication... (Press Esc or
+ Ctrl+C to cancel)
)}
diff --git a/packages/cli/src/ui/components/BrailleAnimation.test.tsx b/packages/cli/src/ui/components/BrailleAnimation.test.tsx
new file mode 100644
index 0000000000..3aa5583791
--- /dev/null
+++ b/packages/cli/src/ui/components/BrailleAnimation.test.tsx
@@ -0,0 +1,103 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { renderWithProviders } from '../../test-utils/render.js';
+import { BrailleAnimation } from './BrailleAnimation.js';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { act } from 'react';
+import { createMockSettings } from '../../test-utils/settings.js';
+
+describe('BrailleAnimation', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('should grow from length 1 to 5 and match verification frames', async () => {
+ const settings = createMockSettings({
+ merged: {
+ ui: {
+ showSpinner: true,
+ },
+ },
+ });
+
+ // renderWithProviders will call waitUntilReady once.
+ const renderResult = await renderWithProviders(
+ ,
+ { settings },
+ );
+
+ const { lastFrameRaw } = renderResult;
+
+ const verificationFrames = [
+ '⢎⠁', // 0
+ '⠎⠑', // 1
+ '⠊⠱', // 2
+ '⠈⡱', // 3
+ '⢀⡱', // 4
+ '⢄⡰', // 5
+ '⢆⡠', // 6
+ '⢎⡀', // 7
+ ];
+
+ // Advance 16 ticks to reach length 5.
+ for (let i = 0; i < 16; i++) {
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(100);
+ });
+ }
+
+ // Now check the sequence.
+ let current = lastFrameRaw();
+ let startIdx = verificationFrames.findIndex((f) => current.includes(f));
+
+ if (startIdx === -1) {
+ for (let attempt = 0; attempt < 8; attempt++) {
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(100);
+ });
+ current = lastFrameRaw();
+ startIdx = verificationFrames.findIndex((f) => current.includes(f));
+ if (startIdx !== -1) break;
+ }
+ }
+
+ expect(
+ startIdx,
+ `Should have reached length 5 frames. Current: ${current}`,
+ ).not.toBe(-1);
+
+ // Verify the sequence.
+ for (let i = 0; i < 8; i++) {
+ const idx = (startIdx + i) % 8;
+ expect(lastFrameRaw()).toContain(verificationFrames[idx]);
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(100);
+ });
+ }
+
+ act(() => {
+ renderResult.unmount();
+ });
+ });
+
+ it('should support "Composite" variant with dynamic lengths', async () => {
+ const renderResult = await renderWithProviders(
+ ,
+ );
+
+ // Just verify it renders something
+ expect(renderResult.lastFrameRaw()).toBeTruthy();
+
+ act(() => {
+ renderResult.unmount();
+ });
+ });
+});
diff --git a/packages/cli/src/ui/components/BrailleAnimation.tsx b/packages/cli/src/ui/components/BrailleAnimation.tsx
new file mode 100644
index 0000000000..56597ab075
--- /dev/null
+++ b/packages/cli/src/ui/components/BrailleAnimation.tsx
@@ -0,0 +1,115 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type React from 'react';
+import { useState, useEffect } from 'react';
+import { Text } from 'ink';
+import { debugState } from '../debug.js';
+import { useSettings } from '../contexts/SettingsContext.js';
+
+// Dot bitmasks and character assignments for the 4x4 circle perimeter
+// Char 0 corresponds to the first Braille character (c1), Char 1 to the second (c2).
+const DOTS = [
+ { char: 1, bit: 1 }, // Dot 1 (c2)
+ { char: 1, bit: 16 }, // Dot 5 (c2)
+ { char: 1, bit: 32 }, // Dot 6 (c2)
+ { char: 1, bit: 64 }, // Dot 7 (c2)
+ { char: 0, bit: 128 }, // Dot 8 (c1)
+ { char: 0, bit: 4 }, // Dot 3 (c1)
+ { char: 0, bit: 2 }, // Dot 2 (c1)
+ { char: 0, bit: 8 }, // Dot 4 (c1)
+];
+
+const COMPOSITE_SEQUENCE = [2, 3, 4, 5, 4, 3];
+
+export type BrailleVariant =
+ | 'Static'
+ | 'Small'
+ | 'Medium'
+ | 'Long'
+ | 'Composite';
+
+interface BrailleAnimationProps {
+ variant?: BrailleVariant;
+ interval?: number;
+ animate?: boolean;
+}
+
+/**
+ * Braille Snake Animation Component
+ *
+ * Variants match the prototype style:
+ * - 'Static': Fixed frame '⢎⡱'
+ * - 'Small': Fixed length 2
+ * - 'Medium': Fixed length 3
+ * - 'Long': Phased growth (len 1, 3, 5) changing every 8 ticks
+ * - 'Composite': Dynamic length [2, 3, 4, 5, 4, 3] changing every 8 ticks
+ */
+export const BrailleAnimation: React.FC = ({
+ variant = 'Composite',
+ interval = 80,
+ animate = !process.env['VITEST'],
+}) => {
+ const [tick, setTick] = useState(0);
+ const settings = useSettings();
+ const shouldShow = settings.merged.ui?.showSpinner !== false;
+
+ useEffect(() => {
+ if (!shouldShow || !animate) return;
+
+ debugState.debugNumAnimatedComponents++;
+
+ const timer = setInterval(() => {
+ setTick((t) => t + 1);
+ }, interval);
+
+ return () => {
+ debugState.debugNumAnimatedComponents--;
+ clearInterval(timer);
+ };
+ }, [interval, shouldShow, animate]);
+
+ const getLength = () => {
+ const cycle = Math.floor(tick / 8);
+ switch (variant) {
+ case 'Small':
+ return 2;
+ case 'Medium':
+ return 3;
+ case 'Long':
+ return cycle === 0 ? 1 : cycle === 1 ? 3 : 5;
+ case 'Composite':
+ return COMPOSITE_SEQUENCE[cycle % COMPOSITE_SEQUENCE.length];
+ case 'Static':
+ return 0;
+ default:
+ return 5;
+ }
+ };
+
+ const getFrame = () => {
+ if (variant === 'Static') {
+ return '⢎⡱';
+ }
+
+ const length = getLength();
+ let [c1, c2] = [0, 0];
+ const head = tick % 8;
+
+ for (let i = 0; i < length; i++) {
+ const { char, bit } = DOTS[(head - i + 80) % 8];
+ char === 0 ? (c1 |= bit) : (c2 |= bit);
+ }
+
+ return String.fromCharCode(0x2800 + c1) + String.fromCharCode(0x2800 + c2);
+ };
+
+ if (!shouldShow) {
+ return null;
+ }
+
+ return {getFrame()};
+};
diff --git a/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx b/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx
index 316438d737..79bd8c4c1d 100644
--- a/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx
+++ b/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx
@@ -15,14 +15,13 @@ import {
} from '../textConstants.js';
import { theme } from '../semantic-colors.js';
import { GeminiSpinner } from './GeminiSpinner.js';
-
interface GeminiRespondingSpinnerProps {
/**
- * Optional string to display when not in Responding state.
+ * Optional string or component to display when not in Responding state.
* If not provided and not Responding, renders null.
*/
- nonRespondingDisplay?: string;
- spinnerType?: SpinnerName;
+ nonRespondingDisplay?: React.ReactNode;
+ spinnerType?: SpinnerName | 'dynamic';
/**
* If true, we prioritize showing the nonRespondingDisplay (hook icon)
* even if the state is Responding.
@@ -35,7 +34,7 @@ export const GeminiRespondingSpinner: React.FC<
GeminiRespondingSpinnerProps
> = ({
nonRespondingDisplay,
- spinnerType = 'dots',
+ spinnerType = 'dynamic',
isHookActive = false,
color,
}) => {
@@ -54,10 +53,14 @@ export const GeminiRespondingSpinner: React.FC<
}
if (nonRespondingDisplay) {
- return isScreenReaderEnabled ? (
- {SCREEN_READER_LOADING}
- ) : (
+ if (isScreenReaderEnabled) {
+ return {SCREEN_READER_LOADING};
+ }
+
+ return typeof nonRespondingDisplay === 'string' ? (
{nonRespondingDisplay}
+ ) : (
+ <>{nonRespondingDisplay}>
);
}
diff --git a/packages/cli/src/ui/components/GeminiSpinner.test.tsx b/packages/cli/src/ui/components/GeminiSpinner.test.tsx
new file mode 100644
index 0000000000..1268ce2082
--- /dev/null
+++ b/packages/cli/src/ui/components/GeminiSpinner.test.tsx
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { renderWithProviders } from '../../test-utils/render.js';
+import { GeminiSpinner } from './GeminiSpinner.js';
+import { describe, it, expect, vi } from 'vitest';
+import { Text } from 'ink';
+import { act } from 'react';
+
+// Mock components to simplify testing
+vi.mock('./BrailleAnimation.js', () => ({
+ BrailleAnimation: ({ variant }: { variant: string }) => (
+ BrailleAnimation-{variant}
+ ),
+ GEMINI_SPINNER: { interval: 80, frames: [] },
+}));
+
+vi.mock('./CliSpinner.js', () => ({
+ CliSpinner: ({ type }: { type: string }) => CliSpinner-{type},
+}));
+
+describe('GeminiSpinner', () => {
+ it('renders BrailleAnimation with "Composite" variant by default', async () => {
+ const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ ,
+ );
+ await waitUntilReady();
+ expect(lastFrame()).toContain('BrailleAnimation-Composite');
+ act(() => {
+ unmount();
+ });
+ });
+
+ it('renders CliSpinner when a specific spinnerType string is provided', async () => {
+ const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ ,
+ );
+ await waitUntilReady();
+ expect(lastFrame()).toContain('CliSpinner-dots');
+ act(() => {
+ unmount();
+ });
+ });
+
+ it('renders screen reader text when screen reader is enabled', async () => {
+ // Note: useIsScreenReaderEnabled is used in GeminiSpinner
+ // We would need to mock it if we wanted to test this explicitly,
+ // but the default is false in our test environment.
+ });
+});
diff --git a/packages/cli/src/ui/components/GeminiSpinner.tsx b/packages/cli/src/ui/components/GeminiSpinner.tsx
index 37d1930625..4c94144e31 100644
--- a/packages/cli/src/ui/components/GeminiSpinner.tsx
+++ b/packages/cli/src/ui/components/GeminiSpinner.tsx
@@ -11,19 +11,23 @@ import { CliSpinner } from './CliSpinner.js';
import type { SpinnerName } from 'cli-spinners';
import { Colors } from '../colors.js';
import tinygradient from 'tinygradient';
+import { BrailleAnimation } from './BrailleAnimation.js';
+import { useSettings } from '../contexts/SettingsContext.js';
const COLOR_CYCLE_DURATION_MS = 4000;
interface GeminiSpinnerProps {
- spinnerType?: SpinnerName;
+ spinnerType?: SpinnerName | 'dynamic';
altText?: string;
}
export const GeminiSpinner: React.FC = ({
- spinnerType = 'dots',
+ spinnerType = 'dynamic',
altText,
}) => {
const isScreenReaderEnabled = useIsScreenReaderEnabled();
+ const settings = useSettings();
+ const shouldShow = settings.merged.ui?.showSpinner !== false;
const [time, setTime] = useState(0);
const googleGradient = useMemo(() => {
@@ -39,7 +43,7 @@ export const GeminiSpinner: React.FC = ({
}, []);
useEffect(() => {
- if (isScreenReaderEnabled) {
+ if (isScreenReaderEnabled || !shouldShow) {
return;
}
@@ -48,16 +52,22 @@ export const GeminiSpinner: React.FC = ({
}, 30); // ~33fps for smooth color transitions
return () => clearInterval(interval);
- }, [isScreenReaderEnabled]);
+ }, [isScreenReaderEnabled, shouldShow]);
const progress = (time % COLOR_CYCLE_DURATION_MS) / COLOR_CYCLE_DURATION_MS;
const currentColor = googleGradient.rgbAt(progress).toHexString();
+ const renderSpinner = () => {
+ if (spinnerType === 'dynamic') {
+ return ;
+ }
+
+ return ;
+ };
+
return isScreenReaderEnabled ? (
{altText}
) : (
-
-
-
+ {renderSpinner()}
);
};
diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
index ef2e21e132..16d6acb1d4 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
@@ -18,13 +18,13 @@ vi.mock('./GeminiRespondingSpinner.js', () => ({
GeminiRespondingSpinner: ({
nonRespondingDisplay,
}: {
- nonRespondingDisplay?: string;
+ nonRespondingDisplay?: React.ReactNode;
}) => {
const streamingState = React.useContext(StreamingContext)!;
if (streamingState === StreamingState.Responding) {
return MockRespondingSpinner;
} else if (nonRespondingDisplay) {
- return {nonRespondingDisplay};
+ return <>{nonRespondingDisplay}>;
}
return null;
},
@@ -86,7 +86,7 @@ describe('', () => {
);
await waitUntilReady();
const output = lastFrame();
- expect(output).toContain('⠏'); // Static char for WaitingForConfirmation
+ expect(output).toContain('⢎⡱'); // Static char for WaitingForConfirmation
expect(output).toContain('Confirm action');
expect(output).not.toContain('(esc to cancel)');
expect(output).not.toContain(', 10s');
@@ -208,7 +208,7 @@ describe('', () => {
});
await waitUntilReady();
output = lastFrame();
- expect(output).toContain('⠏');
+ expect(output).toContain('⢎⡱');
expect(output).toContain('Please Confirm');
expect(output).not.toContain('(esc to cancel)');
expect(output).not.toContain(', 15s');
diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx
index a48451b26c..9f203e1c06 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.tsx
@@ -11,6 +11,7 @@ import { theme } from '../semantic-colors.js';
import { useStreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js';
import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js';
+import { BrailleAnimation } from './BrailleAnimation.js';
import { formatDuration } from '../utils/formatters.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
@@ -96,9 +97,13 @@ export const LoadingIndicator: React.FC = ({
+
+
+ ) : (
+ ''
+ ))
}
isHookActive={isHookActive}
/>
@@ -140,9 +145,13 @@ export const LoadingIndicator: React.FC = ({
+
+
+ ) : (
+ ''
+ ))
}
isHookActive={isHookActive}
/>
diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx
index d5f10cc12c..bb1ab6f99c 100644
--- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx
+++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx
@@ -6,7 +6,7 @@
import { Box, Text } from 'ink';
import type { CompressionProps } from '../../types.js';
-import { CliSpinner } from '../CliSpinner.js';
+import { BrailleAnimation } from '../BrailleAnimation.js';
import { theme } from '../../semantic-colors.js';
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
import { CompressionStatus } from '@google/gemini-cli-core';
@@ -61,7 +61,7 @@ export function CompressionMessage({
{isPending ? (
-
+
) : (
✦
)}
diff --git a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx
index caed091b2b..36a997ec41 100644
--- a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx
+++ b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx
@@ -10,8 +10,8 @@ import type { SubagentProgress } from '@google/gemini-cli-core';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { Text } from 'ink';
-vi.mock('ink-spinner', () => ({
- default: () => ⠋,
+vi.mock('../BrailleAnimation.js', () => ({
+ BrailleAnimation: () => ⠋,
}));
describe('', () => {
diff --git a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx
index 5d1086c759..3d53eaa0a4 100644
--- a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx
+++ b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx
@@ -7,8 +7,8 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
-import Spinner from 'ink-spinner';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
+import { BrailleAnimation } from '../BrailleAnimation.js';
import type {
SubagentProgress,
SubagentActivityItem,
@@ -106,7 +106,7 @@ export const SubagentProgressDisplay: React.FC<
} else if (item.type === 'tool_call') {
const statusSymbol =
item.status === 'running' ? (
-
+
) : item.status === 'completed' ? (
{TOOL_STATUS.SUCCESS}
) : item.status === 'cancelled' ? (
diff --git a/packages/cli/src/ui/components/triage/TriageDuplicates.tsx b/packages/cli/src/ui/components/triage/TriageDuplicates.tsx
index d77bf45243..36b9b50378 100644
--- a/packages/cli/src/ui/components/triage/TriageDuplicates.tsx
+++ b/packages/cli/src/ui/components/triage/TriageDuplicates.tsx
@@ -6,7 +6,6 @@
import { useState, useEffect, useCallback } from 'react';
import { Box, Text } from 'ink';
-import Spinner from 'ink-spinner';
import {
debugLogger,
spawnAsync,
@@ -16,6 +15,7 @@ import {
import { useKeypress } from '../../hooks/useKeypress.js';
import { Command } from '../../key/keyMatchers.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
+import { BrailleAnimation } from '../BrailleAnimation.js';
interface Issue {
number: number;
@@ -725,7 +725,7 @@ Return a JSON object with:
if (state.status === 'loading') {
return (
-
+
{state.message}
);
@@ -921,7 +921,7 @@ Return a JSON object with:
justifyContent="center"
height={VISIBLE_CANDIDATES * 2}
>
-
+
{state.message}
) : (
diff --git a/packages/cli/src/ui/components/triage/TriageIssues.tsx b/packages/cli/src/ui/components/triage/TriageIssues.tsx
index 62c0f50e1c..bbc09f6cba 100644
--- a/packages/cli/src/ui/components/triage/TriageIssues.tsx
+++ b/packages/cli/src/ui/components/triage/TriageIssues.tsx
@@ -6,7 +6,6 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { Box, Text } from 'ink';
-import Spinner from 'ink-spinner';
import {
debugLogger,
spawnAsync,
@@ -18,6 +17,7 @@ import { Command } from '../../key/keyMatchers.js';
import { TextInput } from '../shared/TextInput.js';
import { useTextBuffer } from '../shared/text-buffer.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
+import { BrailleAnimation } from '../BrailleAnimation.js';
interface Issue {
number: number;
@@ -448,7 +448,7 @@ Return a JSON object with:
if (state.status === 'loading') {
return (
-
+
{state.message}
);
@@ -521,7 +521,7 @@ Return a JSON object with:
if (state.status === 'analyzing') {
return (
-
+
{state.message}
);
@@ -610,7 +610,7 @@ Return a JSON object with:
>
{state.status === 'analyzing' ? (
-
+
Analyzing issue with Gemini...
) : analysis ? (