feat(ui): standardize semantic focus colors and enhance history visibility (#20745)

Co-authored-by: jacob314 <jacob314@gmail.com>
This commit is contained in:
Keith Guerin
2026-03-03 16:10:09 -08:00
committed by GitHub
parent 75737c1b44
commit d25088956d
70 changed files with 1427 additions and 406 deletions
+1 -1
View File
@@ -98,7 +98,7 @@ export function ApiAuthDialog({
return (
<Box
borderStyle="round"
borderColor={theme.border.focused}
borderColor={theme.ui.focus}
flexDirection="column"
padding={1}
width="100%"
+2 -2
View File
@@ -193,7 +193,7 @@ export function AuthDialog({
return (
<Box
borderStyle="round"
borderColor={theme.border.focused}
borderColor={theme.ui.focus}
flexDirection="row"
padding={1}
width="100%"
@@ -209,7 +209,7 @@ export function AuthDialog({
return (
<Box
borderStyle="round"
borderColor={theme.border.focused}
borderColor={theme.ui.focus}
flexDirection="row"
padding={1}
width="100%"
@@ -427,7 +427,7 @@ export const BackgroundShellDisplay = ({
height="100%"
width="100%"
borderStyle="single"
borderColor={isFocused ? theme.border.focused : undefined}
borderColor={isFocused ? theme.ui.focus : undefined}
>
<Box
flexDirection="row"
@@ -438,7 +438,7 @@ export const BackgroundShellDisplay = ({
borderRight={false}
borderTop={false}
paddingX={1}
borderColor={isFocused ? theme.border.focused : undefined}
borderColor={isFocused ? theme.ui.focus : undefined}
>
<Box flexDirection="row">
{renderTabs()}
@@ -0,0 +1,118 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { renderWithProviders } from '../../test-utils/render.js';
import { ColorsDisplay } from './ColorsDisplay.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { themeManager } from '../themes/theme-manager.js';
import type { Theme, ColorsTheme } from '../themes/theme.js';
import type { SemanticColors } from '../themes/semantic-tokens.js';
describe('ColorsDisplay', () => {
beforeEach(() => {
vi.spyOn(themeManager, 'getSemanticColors').mockReturnValue({
text: {
primary: '#ffffff',
secondary: '#cccccc',
link: '#0000ff',
accent: '#ff00ff',
response: '#ffffff',
},
background: {
primary: '#000000',
message: '#111111',
input: '#222222',
focus: '#333333',
diff: {
added: '#003300',
removed: '#330000',
},
},
border: {
default: '#555555',
},
ui: {
comment: '#666666',
symbol: '#cccccc',
active: '#0000ff',
dark: '#333333',
focus: '#0000ff',
gradient: undefined,
},
status: {
error: '#ff0000',
success: '#00ff00',
warning: '#ffff00',
},
});
vi.spyOn(themeManager, 'getActiveTheme').mockReturnValue({
name: 'Test Theme',
type: 'dark',
colors: {} as unknown as ColorsTheme,
semanticColors: {
text: {
primary: '#ffffff',
secondary: '#cccccc',
link: '#0000ff',
accent: '#ff00ff',
response: '#ffffff',
},
background: {
primary: '#000000',
message: '#111111',
input: '#222222',
diff: {
added: '#003300',
removed: '#330000',
},
},
border: {
default: '#555555',
},
ui: {
comment: '#666666',
symbol: '#cccccc',
active: '#0000ff',
dark: '#333333',
focus: '#0000ff',
gradient: undefined,
},
status: {
error: '#ff0000',
success: '#00ff00',
warning: '#ffff00',
},
} as unknown as SemanticColors,
} as unknown as Theme);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('renders correctly', async () => {
const mockTheme = themeManager.getActiveTheme();
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ColorsDisplay activeTheme={mockTheme} />,
);
await waitUntilReady();
const output = lastFrame();
// Check for title and description
expect(output).toContain('How do colors get applied?');
expect(output).toContain('Hex:');
// Check for some color names and values expect(output).toContain('text.primary');
expect(output).toContain('#ffffff');
expect(output).toContain('background.diff.added');
expect(output).toContain('#003300');
expect(output).toContain('border.default');
expect(output).toContain('#555555');
unmount();
});
});
@@ -0,0 +1,277 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { Box, Text } from 'ink';
import Gradient from 'ink-gradient';
import { theme } from '../semantic-colors.js';
import type { Theme } from '../themes/theme.js';
interface StandardColorRow {
type: 'standard';
name: string;
value: string;
}
interface GradientColorRow {
type: 'gradient';
name: string;
value: string[];
}
interface BackgroundColorRow {
type: 'background';
name: string;
value: string;
}
type ColorRow = StandardColorRow | GradientColorRow | BackgroundColorRow;
const VALUE_COLUMN_WIDTH = 10;
const COLOR_DESCRIPTIONS: Record<string, string> = {
'text.primary': 'Primary text color (uses terminal default if blank)',
'text.secondary': 'Secondary/dimmed text color',
'text.link': 'Hyperlink and highlighting color',
'text.accent': 'Accent color for emphasis',
'text.response':
'Color for model response text (uses terminal default if blank)',
'background.primary': 'Main terminal background color',
'background.message': 'Subtle background for message blocks',
'background.input': 'Background for the input prompt',
'background.focus': 'Background highlight for selected/focused items',
'background.diff.added': 'Background for added lines in diffs',
'background.diff.removed': 'Background for removed lines in diffs',
'border.default': 'Standard border color',
'ui.comment': 'Color for code comments and metadata',
'ui.symbol': 'Color for technical symbols and UI icons',
'ui.active': 'Border color for active or running elements',
'ui.dark': 'Deeply dimmed color for subtle UI elements',
'ui.focus':
'Color for focused elements (e.g. selected menu items, focused borders)',
'status.error': 'Color for error messages and critical status',
'status.success': 'Color for success messages and positive status',
'status.warning': 'Color for warnings and cautionary status',
};
interface ColorsDisplayProps {
activeTheme: Theme;
}
/**
* Determines a contrasting text color (black or white) based on the background color's luminance.
*/
function getContrastingTextColor(hex: string): string {
if (!hex || !hex.startsWith('#') || hex.length < 7) {
// Fallback for invalid hex codes or named colors
return theme.text.primary;
}
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// Using YIQ formula to determine luminance
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? '#000000' : '#FFFFFF';
}
export const ColorsDisplay: React.FC<ColorsDisplayProps> = ({
activeTheme,
}) => {
const semanticColors = activeTheme.semanticColors;
const backgroundRows: BackgroundColorRow[] = [];
const standardRows: StandardColorRow[] = [];
let gradientRow: GradientColorRow | null = null;
if (semanticColors.ui.gradient && semanticColors.ui.gradient.length > 0) {
gradientRow = {
type: 'gradient',
name: 'ui.gradient',
value: semanticColors.ui.gradient,
};
}
/**
* Recursively flattens the semanticColors object.
*/
const flattenColors = (obj: object, path: string = '') => {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined || value === null) continue;
const newPath = path ? `${path}.${key}` : key;
if (key === 'gradient' && Array.isArray(value)) {
// Gradient handled separately
continue;
}
if (typeof value === 'object' && !Array.isArray(value)) {
flattenColors(value, newPath);
} else if (typeof value === 'string') {
if (newPath.startsWith('background.')) {
backgroundRows.push({
type: 'background',
name: newPath,
value,
});
} else {
standardRows.push({
type: 'standard',
name: newPath,
value,
});
}
}
}
};
flattenColors(semanticColors);
// Final order: Backgrounds first, then Standards, then Gradient
const allRows: ColorRow[] = [
...backgroundRows,
...standardRows,
...(gradientRow ? [gradientRow] : []),
];
return (
<Box
flexDirection="column"
paddingX={1}
paddingY={0}
borderStyle="round"
borderColor={theme.border.default}
>
<Box marginBottom={1} flexDirection="column">
<Text bold color={theme.text.accent}>
DEVELOPER TOOLS (Not visible to users)
</Text>
<Box marginTop={1} flexDirection="column">
<Text color={theme.text.primary}>
<Text bold>How do colors get applied?</Text>
</Text>
<Box marginLeft={2} flexDirection="column">
<Text color={theme.text.primary}>
<Text bold>Hex:</Text> Rendered exactly by modern terminals. Not
overridden by app themes.
</Text>
<Text color={theme.text.primary}>
<Text bold>Blank:</Text> Uses your terminal&apos;s default
foreground/background.
</Text>
<Text color={theme.text.primary}>
<Text bold>Compatibility:</Text> On older terminals, hex is
approximated to the nearest ANSI color.
</Text>
<Text color={theme.text.primary}>
<Text bold>ANSI Names:</Text> &apos;red&apos;,
&apos;green&apos;, etc. are mapped to your terminal app&apos;s
palette.
</Text>
</Box>
</Box>
</Box>
{/* Header */}
<Box flexDirection="row" marginBottom={0} paddingX={1}>
<Box width={VALUE_COLUMN_WIDTH}>
<Text bold color={theme.text.link} dimColor>
Value
</Text>
</Box>
<Box flexGrow={1}>
<Text bold color={theme.text.link} dimColor>
Name
</Text>
</Box>
</Box>
{/* All Rows */}
<Box flexDirection="column">
{allRows.map((row) => {
if (row.type === 'standard') return renderStandardRow(row);
if (row.type === 'gradient') return renderGradientRow(row);
if (row.type === 'background') return renderBackgroundRow(row);
return null;
})}
</Box>
</Box>
);
};
function renderStandardRow({ name, value }: StandardColorRow) {
const isHex = value.startsWith('#');
const displayColor = isHex ? value : theme.text.primary;
const description = COLOR_DESCRIPTIONS[name] || '';
return (
<Box key={name} flexDirection="row" paddingX={1}>
<Box width={VALUE_COLUMN_WIDTH}>
<Text color={displayColor}>{value || '(blank)'}</Text>
</Box>
<Box flexGrow={1} flexDirection="row">
<Box width="30%">
<Text color={displayColor}>{name}</Text>
</Box>
<Box flexGrow={1} paddingLeft={1}>
<Text color={theme.text.secondary}>{description}</Text>
</Box>
</Box>
</Box>
);
}
function renderGradientRow({ name, value }: GradientColorRow) {
const description = COLOR_DESCRIPTIONS[name] || '';
return (
<Box key={name} flexDirection="row" paddingX={1}>
<Box width={VALUE_COLUMN_WIDTH} flexDirection="column">
{value.map((c, i) => (
<Text key={i} color={c}>
{c}
</Text>
))}
</Box>
<Box flexGrow={1} flexDirection="row">
<Box width="30%">
<Gradient colors={value}>
<Text>{name}</Text>
</Gradient>
</Box>
<Box flexGrow={1} paddingLeft={1}>
<Text color={theme.text.secondary}>{description}</Text>
</Box>
</Box>
</Box>
);
}
function renderBackgroundRow({ name, value }: BackgroundColorRow) {
const description = COLOR_DESCRIPTIONS[name] || '';
return (
<Box key={name} flexDirection="row" paddingX={1}>
<Box
width={VALUE_COLUMN_WIDTH}
backgroundColor={value}
justifyContent="center"
paddingX={1}
>
<Text color={getContrastingTextColor(value)} bold wrap="truncate">
{value || 'default'}
</Text>
</Box>
<Box flexGrow={1} flexDirection="row" paddingLeft={1}>
<Box width="30%">
<Text color={theme.text.primary}>{name}</Text>
</Box>
<Box flexGrow={1} paddingLeft={1}>
<Text color={theme.text.secondary}>{description}</Text>
</Box>
</Box>
</Box>
);
}
@@ -22,8 +22,13 @@ vi.mock('../semantic-colors.js', async (importOriginal) => {
...original,
theme: {
...original.theme,
background: {
...original.theme.background,
focus: '#004000',
},
ui: {
...original.theme.ui,
focus: '#00ff00',
gradient: [], // Empty array to potentially trigger the crash
},
},
@@ -98,16 +98,18 @@ describe('<Header />', () => {
primary: '',
message: '',
input: '',
focus: '',
diff: { added: '', removed: '' },
},
border: {
default: '',
focused: '',
},
ui: {
comment: '',
symbol: '',
active: '',
dark: '',
focus: '',
gradient: undefined,
},
status: {
@@ -1427,7 +1427,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
const borderColor =
isShellFocused && !isEmbeddedShellFocused
? (statusColor ?? theme.border.focused)
? (statusColor ?? theme.ui.focus)
: theme.border.default;
return (
@@ -79,10 +79,18 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
/>
</Box>
{primaryText && (
<Text color={theme.text.primary} italic wrap="truncate-end">
{thinkingIndicator}
{primaryText}
</Text>
<Box flexShrink={1}>
<Text color={theme.text.primary} italic wrap="truncate-end">
{thinkingIndicator}
{primaryText}
</Text>
{primaryText === INTERACTIVE_SHELL_WAITING_PHRASE && (
<Text color={theme.ui.active} italic>
{' '}
(press tab to focus)
</Text>
)}
</Box>
)}
{cancelAndTimerContent && (
<>
@@ -113,10 +121,18 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
/>
</Box>
{primaryText && (
<Text color={theme.text.primary} italic wrap="truncate-end">
{thinkingIndicator}
{primaryText}
</Text>
<Box flexShrink={1}>
<Text color={theme.text.primary} italic wrap="truncate-end">
{thinkingIndicator}
{primaryText}
</Text>
{primaryText === INTERACTIVE_SHELL_WAITING_PHRASE && (
<Text color={theme.ui.active} italic>
{' '}
(press tab to focus)
</Text>
)}
</Box>
)}
{!isNarrow && cancelAndTimerContent && (
<>
@@ -53,7 +53,7 @@ export const LogoutConfirmationDialog: React.FC<
<Box
flexDirection="column"
borderStyle="round"
borderColor={theme.border.focused}
borderColor={theme.ui.focus}
padding={1}
flexGrow={1}
marginLeft={1}
@@ -203,7 +203,7 @@ describe('getToolGroupBorderAppearance', () => {
});
});
it('returns symbol border for executing shell commands', () => {
it('returns active border for executing shell commands', () => {
const item = {
type: 'tool_group' as const,
tools: [
@@ -219,7 +219,37 @@ describe('getToolGroupBorderAppearance', () => {
],
id: 1,
};
// While executing shell commands, it's dim false, border symbol
// While executing shell commands, it's dim false, border active
const result = getToolGroupBorderAppearance(
item,
activeShellPtyId,
false,
[],
mockBackgroundShells,
);
expect(result).toEqual({
borderColor: theme.ui.active,
borderDimColor: true,
});
});
it('returns focus border for focused executing shell commands', () => {
const item = {
type: 'tool_group' as const,
tools: [
{
callId: '1',
name: SHELL_COMMAND_NAME,
description: '',
status: CoreToolCallStatus.Executing,
ptyId: activeShellPtyId,
resultDisplay: undefined,
confirmationDetails: undefined,
} as IndividualToolCallDisplay,
],
id: 1,
};
// When focused, it's dim false, border focus
const result = getToolGroupBorderAppearance(
item,
activeShellPtyId,
@@ -228,12 +258,12 @@ describe('getToolGroupBorderAppearance', () => {
mockBackgroundShells,
);
expect(result).toEqual({
borderColor: theme.ui.symbol,
borderColor: theme.ui.focus,
borderDimColor: false,
});
});
it('returns symbol border and dims color for background executing shell command when another shell is active', () => {
it('returns active border and dims color for background executing shell command when another shell is active', () => {
const item = {
type: 'tool_group' as const,
tools: [
@@ -257,7 +287,7 @@ describe('getToolGroupBorderAppearance', () => {
mockBackgroundShells,
);
expect(result).toEqual({
borderColor: theme.ui.symbol,
borderColor: theme.ui.active,
borderDimColor: true,
});
});
@@ -275,7 +305,7 @@ describe('getToolGroupBorderAppearance', () => {
);
// Since there are no tools to inspect, it falls back to empty pending, but isCurrentlyInShellTurn=true
// so it counts as pending shell.
expect(result.borderColor).toEqual(theme.ui.symbol);
expect(result.borderColor).toEqual(theme.ui.focus);
// It shouldn't be dim because there are no tools to say it isEmbeddedShellFocused = false
expect(result.borderDimColor).toBe(false);
});
@@ -7,6 +7,7 @@
import type React from 'react';
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { Colors } from '../colors.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { useKeypress } from '../hooks/useKeypress.js';
@@ -436,7 +437,7 @@ const SessionItem = ({
if (isDisabled) {
return Colors.Gray;
}
return isActive ? Colors.AccentPurple : c;
return isActive ? theme.ui.focus : c;
};
const prefix = isActive ? ' ' : ' ';
@@ -483,7 +484,10 @@ const SessionItem = ({
));
return (
<Box flexDirection="row">
<Box
flexDirection="row"
backgroundColor={isActive ? theme.background.focus : undefined}
>
<Text color={textColor()} dimColor={isDisabled}>
{prefix}
</Text>
@@ -84,7 +84,7 @@ export function SuggestionsDisplay({
const originalIndex = startIndex + index;
const isActive = originalIndex === activeIndex;
const isExpanded = originalIndex === expandedIndex;
const textColor = isActive ? theme.text.accent : theme.text.secondary;
const textColor = isActive ? theme.ui.focus : theme.text.secondary;
const isLong = suggestion.value.length >= MAX_WIDTH;
const labelElement = (
<ExpandableText
@@ -97,7 +97,11 @@ export function SuggestionsDisplay({
);
return (
<Box key={`${suggestion.value}-${originalIndex}`} flexDirection="row">
<Box
key={`${suggestion.value}-${originalIndex}`}
flexDirection="row"
backgroundColor={isActive ? theme.background.focus : undefined}
>
<Box
{...(mode === 'slash'
? { width: commandColumnWidth, flexShrink: 0 as const }
@@ -8,6 +8,22 @@ import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ThemeDialog } from './ThemeDialog.js';
const { mockIsDevelopment } = vi.hoisted(() => ({
mockIsDevelopment: { value: false },
}));
vi.mock('../../utils/installationInfo.js', async (importOriginal) => {
const actual =
await importOriginal<typeof import('../../utils/installationInfo.js')>();
return {
...actual,
get isDevelopment() {
return mockIsDevelopment.value;
},
};
});
import { createMockSettings } from '../../test-utils/settings.js';
import { DEFAULT_THEME, themeManager } from '../themes/theme-manager.js';
import { act } from 'react';
@@ -30,17 +46,21 @@ describe('ThemeDialog Snapshots', () => {
vi.restoreAllMocks();
});
it('should render correctly in theme selection mode', async () => {
const settings = createMockSettings();
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThemeDialog {...baseProps} settings={settings} />,
{ settings },
);
await waitUntilReady();
it.each([true, false])(
'should render correctly in theme selection mode (isDevelopment: %s)',
async (isDev) => {
mockIsDevelopment.value = isDev;
const settings = createMockSettings();
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThemeDialog {...baseProps} settings={settings} />,
{ settings },
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
expect(lastFrame()).toMatchSnapshot();
unmount();
},
);
it('should render correctly in scope selector mode', async () => {
const settings = createMockSettings();
+39 -37
View File
@@ -23,6 +23,8 @@ import { useKeypress } from '../hooks/useKeypress.js';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import { ScopeSelector } from './shared/ScopeSelector.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { ColorsDisplay } from './ColorsDisplay.js';
import { isDevelopment } from '../../utils/installationInfo.js';
interface ThemeDialogProps {
/** Callback function when a theme is selected */
@@ -245,6 +247,11 @@ export function ThemeDialog({
// The code block is slightly longer than the diff, so give it more space.
const codeBlockHeight = Math.ceil(availableHeightForPanes * 0.6);
const diffHeight = Math.floor(availableHeightForPanes * 0.4);
const previewTheme =
themeManager.getTheme(highlightedThemeName || DEFAULT_THEME.name) ||
DEFAULT_THEME;
return (
<Box
borderStyle="round"
@@ -328,53 +335,48 @@ export function ThemeDialog({
<Text bold color={theme.text.primary}>
Preview
</Text>
{/* Get the Theme object for the highlighted theme, fall back to default if not found */}
{(() => {
const previewTheme =
themeManager.getTheme(
highlightedThemeName || DEFAULT_THEME.name,
) || DEFAULT_THEME;
return (
<Box
borderStyle="single"
borderColor={theme.border.default}
paddingTop={includePadding ? 1 : 0}
paddingBottom={includePadding ? 1 : 0}
paddingLeft={1}
paddingRight={1}
flexDirection="column"
>
{colorizeCode({
code: `# function
<Box
borderStyle="single"
borderColor={theme.border.default}
paddingTop={includePadding ? 1 : 0}
paddingBottom={includePadding ? 1 : 0}
paddingLeft={1}
paddingRight={1}
flexDirection="column"
>
{colorizeCode({
code: `# function
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a`,
language: 'python',
availableHeight:
isAlternateBuffer === false ? codeBlockHeight : undefined,
maxWidth: colorizeCodeWidth,
settings,
})}
<Box marginTop={1} />
<DiffRenderer
diffContent={`--- a/util.py
language: 'python',
availableHeight:
isAlternateBuffer === false ? codeBlockHeight : undefined,
maxWidth: colorizeCodeWidth,
settings,
})}
<Box marginTop={1} />
<DiffRenderer
diffContent={`--- a/util.py
+++ b/util.py
@@ -1,2 +1,2 @@
- print("Hello, " + name)
+ print(f"Hello, {name}!")
`}
availableTerminalHeight={
isAlternateBuffer === false ? diffHeight : undefined
}
terminalWidth={colorizeCodeWidth}
theme={previewTheme}
/>
</Box>
);
})()}
availableTerminalHeight={
isAlternateBuffer === false ? diffHeight : undefined
}
terminalWidth={colorizeCodeWidth}
theme={previewTheme}
/>
</Box>
{isDevelopment && (
<Box marginTop={1}>
<ColorsDisplay activeTheme={previewTheme} />
</Box>
)}
</Box>
</Box>
) : (
@@ -13,6 +13,10 @@ vi.mock('../semantic-colors.js', () => ({
theme: {
ui: {
gradient: ['red', 'blue'],
focus: 'green',
},
background: {
focus: 'darkgreen',
},
text: {
accent: 'cyan',
@@ -1,6 +1,17 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 1`] = `
"Select your preferred language:
1. TypeScript
2. JavaScript
● 3. Enter a custom value
Enter to submit · Esc to cancel
"
`;
exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 2`] = `
"Select your preferred language:
1. TypeScript
@@ -12,6 +23,17 @@ Enter to submit · Esc to cancel
`;
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = `
"Select your preferred language:
1. TypeScript
2. JavaScript
● 3. Type another language...
Enter to submit · Esc to cancel
"
`;
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 2`] = `
"Select your preferred language:
1. TypeScript
@@ -25,6 +47,20 @@ Enter to submit · Esc to cancel
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
Description 2
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 2`] = `
"Choose an option
● 1. Option 1
Description 1
@@ -39,6 +75,45 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
Description 2
3. Option 3
Description 3
4. Option 4
Description 4
5. Option 5
Description 5
6. Option 6
Description 6
7. Option 7
Description 7
8. Option 8
Description 8
9. Option 9
Description 9
10. Option 10
Description 10
11. Option 11
Description 11
12. Option 12
Description 12
13. Option 13
Description 13
14. Option 14
Description 14
15. Option 15
Description 15
16. Enter a custom value
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 2`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
@@ -122,8 +197,8 @@ Enter to submit · Tab/Shift+Tab to edit answers · Esc to cancel
exports[`AskUserDialog > hides progress header for single question 1`] = `
"Which authentication method should we use?
● 1. OAuth 2.0
Industry standard, supports SSO
● 1. OAuth 2.0
Industry standard, supports SSO
2. JWT tokens
Stateless, good for APIs
3. Enter a custom value
@@ -135,8 +210,8 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
exports[`AskUserDialog > renders question and options 1`] = `
"Which authentication method should we use?
● 1. OAuth 2.0
Industry standard, supports SSO
● 1. OAuth 2.0
Industry standard, supports SSO
2. JWT tokens
Stateless, good for APIs
3. Enter a custom value
@@ -150,8 +225,8 @@ exports[`AskUserDialog > shows Review tab in progress header for multiple questi
Which framework?
● 1. React
Component library
● 1. React
Component library
2. Vue
Progressive framework
3. Enter a custom value
@@ -163,8 +238,8 @@ Enter to select · ←/→ to switch questions · Esc to cancel
exports[`AskUserDialog > shows keyboard hints 1`] = `
"Which authentication method should we use?
● 1. OAuth 2.0
Industry standard, supports SSO
● 1. OAuth 2.0
Industry standard, supports SSO
2. JWT tokens
Stateless, good for APIs
3. Enter a custom value
@@ -178,8 +253,8 @@ exports[`AskUserDialog > shows progress header for multiple questions 1`] = `
Which database should we use?
● 1. PostgreSQL
Relational database
● 1. PostgreSQL
Relational database
2. MongoDB
Document database
3. Enter a custom value
@@ -19,14 +19,41 @@ Files to Modify
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Type your feedback...
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 1`] = `
"Overview
@@ -44,8 +71,8 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
@@ -54,6 +81,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Add tests
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > displays error state when file read fails 1`] = `
" Error reading plan: File not found
"
@@ -76,8 +130,8 @@ Implementation Steps
8. Add multi-factor authentication in src/auth/MFAService.ts
... last 22 lines hidden (Ctrl+O to show) ...
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
@@ -103,8 +157,8 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
@@ -132,14 +186,41 @@ Files to Modify
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Type your feedback...
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = `
"Overview
@@ -157,8 +238,8 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
@@ -167,6 +248,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Add tests
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > displays error state when file read fails 1`] = `
" Error reading plan: File not found
"
@@ -210,8 +318,8 @@ Testing Strategy
- Security penetration testing
- Load testing for session management
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
@@ -237,8 +345,8 @@ Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
3. Type your feedback...
@@ -12,8 +12,8 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and c
(r:) Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll →
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
...
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
...
"
`;
@@ -22,8 +22,8 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and c
(r:) Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ←
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
llllllllllllllllllllllllllllllllllllllllllllllllll
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
llllllllllllllllllllllllllllllllllllllllllllllllll
"
`;
@@ -31,7 +31,7 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
(r:) commit
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
git commit -m "feat: add search" in src/app
git commit -m "feat: add search" in src/app
"
`;
@@ -39,7 +39,7 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
(r:) commit
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
git commit -m "feat: add search" in src/app
git commit -m "feat: add search" in src/app
"
`;
@@ -78,6 +78,27 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file
@@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<LoadingIndicator /> > should truncate long primary text instead of wrapping 1`] = `
"MockRespondin This is an extremely long loading phrase that shoul… (esc to
"MockRespondin This is an extremely long loading phrase that shoul…(esc to
gSpinner cancel, 5s)
"
`;
@@ -4,7 +4,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Focuse
"ScrollableList
AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ Line 10 │
│ Line 11 │
@@ -26,7 +26,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Unfocu
"ScrollableList
AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ Line 10 │
│ Line 11 │
@@ -47,7 +47,7 @@ ShowMoreLines
exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Constrained height' 1`] = `
"AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ ... first 11 lines hidden (Ctrl+O to show) ... │
│ Line 12 │
@@ -67,7 +67,7 @@ ShowMoreLines
exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unconstrained height' 1`] = `
"AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ Line 1 │
│ Line 2 │
@@ -6,7 +6,7 @@ exports[`SessionBrowser component > enters search mode, filters sessions, and re
Search: query (Esc to cancel)
Index │ Msgs │ Age │ Match
#1 │ 1 │ 10mo │ You: Query is here a… (+1 more)
#1 │ 1 │ 10mo │ You: Query is here a… (+1 more)
"
`;
@@ -17,7 +17,7 @@ exports[`SessionBrowser component > renders a list of sessions and marks current
Sort: s Reverse: r First/Last: g/G
Index │ Msgs │ Age │ Name
#1 │ 5 │ 10mo │ Second conversation about dogs (current)
#1 │ 5 │ 10mo │ Second conversation about dogs (current)
#2 │ 2 │ 10mo │ First conversation about cats
"
@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="711" height="17" fill="#394545" />
<rect x="828" y="153" width="45" height="17" fill="#394545" />
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -112,8 +120,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="711" height="17" fill="#394545" />
<rect x="828" y="153" width="45" height="17" fill="#394545" />
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">true*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -112,8 +120,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="702" height="17" fill="#394545" />
<rect x="819" y="153" width="54" height="17" fill="#394545" />
<text x="819" y="155" fill="#a6e3a1" textLength="54" lengthAdjust="spacingAndGlyphs">false*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -110,8 +118,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="711" height="17" fill="#394545" />
<rect x="828" y="153" width="45" height="17" fill="#394545" />
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -112,8 +120,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="711" height="17" fill="#394545" />
<rect x="828" y="153" width="45" height="17" fill="#394545" />
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -112,8 +120,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -109,9 +109,15 @@
<text x="27" y="597" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">&gt; Apply To</text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="18" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
<rect x="63" y="612" width="9" height="17" fill="#394545" />
<rect x="72" y="612" width="117" height="17" fill="#394545" />
<text x="72" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="189" y="612" width="684" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> 2. Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="702" height="17" fill="#394545" />
<rect x="819" y="153" width="54" height="17" fill="#394545" />
<text x="819" y="155" fill="#a6e3a1" textLength="54" lengthAdjust="spacingAndGlyphs">false*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -111,8 +119,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="711" height="17" fill="#394545" />
<rect x="828" y="153" width="45" height="17" fill="#394545" />
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -112,8 +120,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -13,17 +13,17 @@
<text x="0" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="53" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="18" y="70" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="891" y="70" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="85" width="9" height="17" fill="#ffffff" />
<text x="36" y="87" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">S</text>
<text x="45" y="87" fill="#6c7086" textLength="135" lengthAdjust="spacingAndGlyphs">earch to filter</text>
<text x="873" y="87" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="873" y="87" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="87" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="18" y="104" fill="#a6e3a1" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="891" y="104" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="121" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -31,12 +31,20 @@
<text x="27" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="138" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="153" width="9" height="17" fill="#394545" />
<text x="27" y="155" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="153" width="9" height="17" fill="#394545" />
<rect x="45" y="153" width="72" height="17" fill="#394545" />
<text x="45" y="155" fill="#a6e3a1" textLength="72" lengthAdjust="spacingAndGlyphs">Vim Mode</text>
<rect x="117" y="153" width="711" height="17" fill="#394545" />
<rect x="828" y="153" width="45" height="17" fill="#394545" />
<text x="828" y="155" fill="#a6e3a1" textLength="45" lengthAdjust="spacingAndGlyphs">true*</text>
<text x="891" y="155" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="170" width="18" height="17" fill="#394545" />
<rect x="45" y="170" width="198" height="17" fill="#394545" />
<text x="45" y="172" fill="#6c7086" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
<rect x="243" y="170" width="630" height="17" fill="#394545" />
<text x="891" y="172" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="189" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -110,8 +118,12 @@
<text x="9" y="597" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Apply To </text>
<text x="891" y="597" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="27" y="612" width="9" height="17" fill="#394545" />
<text x="27" y="614" fill="#a6e3a1" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="612" width="9" height="17" fill="#394545" />
<rect x="45" y="612" width="117" height="17" fill="#394545" />
<text x="45" y="614" fill="#a6e3a1" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
<rect x="162" y="612" width="711" height="17" fill="#394545" />
<text x="891" y="614" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="631" fill="#3d3f51" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="631" fill="#ffffff" textLength="882" lengthAdjust="spacingAndGlyphs"> Workspace Settings </text>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -7,7 +7,7 @@ exports[`SuggestionsDisplay > handles scrolling 1`] = `
Cmd 7 Description 7
Cmd 8 Description 8
Cmd 9 Description 9
Cmd 10 Description 10
Cmd 10 Description 10
Cmd 11 Description 11
Cmd 12 Description 12
@@ -17,13 +17,13 @@ exports[`SuggestionsDisplay > handles scrolling 1`] = `
exports[`SuggestionsDisplay > highlights active item 1`] = `
" command1 Description 1
command2 Description 2
command2 Description 2
command3 Description 3
"
`;
exports[`SuggestionsDisplay > renders MCP tag for MCP prompts 1`] = `
" mcp-tool [MCP]
" mcp-tool [MCP]
"
`;
@@ -33,7 +33,7 @@ exports[`SuggestionsDisplay > renders loading state 1`] = `
`;
exports[`SuggestionsDisplay > renders suggestions list 1`] = `
" command1 Description 1
" command1 Description 1
command2 Description 2
command3 Description 3
"
@@ -89,7 +89,7 @@ exports[`ThemeDialog Snapshots > should render correctly in scope selector mode
"
`;
exports[`ThemeDialog Snapshots > should render correctly in theme selection mode 1`] = `
exports[`ThemeDialog Snapshots > should render correctly in theme selection mode (isDevelopment: false) 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ > Select Theme Preview │
@@ -113,3 +113,90 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`ThemeDialog Snapshots > should render correctly in theme selection mode (isDevelopment: true) 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ > Select Theme Preview │
│ ▲ ┌─────────────────────────────────────────────────┐ │
│ ● 1. ANSI Dark (Matches terminal) │ │ │
│ 2. Atom One Dark │ 1 # function │ │
│ 3. Ayu Dark │ 2 def fibonacci(n): │ │
│ 4. Default Dark │ 3 a, b = 0, 1 │ │
│ 5. Dracula Dark │ 4 for _ in range(n): │ │
│ 6. GitHub Dark │ 5 a, b = b, a + b │ │
│ 7. Holiday Dark │ 6 return a │ │
│ 8. Shades Of Purple Dark │ │ │
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. Ayu Light │ │ │
│ 12. Default Light └─────────────────────────────────────────────────┘ │
│ ▼ │
│ ╭─────────────────────────────────────────────────╮ │
│ │ DEVELOPER TOOLS (Not visible to users) │ │
│ │ │ │
│ │ How do colors get applied? │ │
│ │ • Hex: Rendered exactly by modern terminals. │ │
│ │ Not overridden by app themes. │ │
│ │ • Blank: Uses your terminal's default │ │
│ │ foreground/background. │ │
│ │ • Compatibility: On older terminals, hex is │ │
│ │ approximated to the nearest ANSI color. │ │
│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │
│ │ to your terminal app's palette. │ │
│ │ │ │
│ │ Value Name │ │
│ │ #1E1E… backgroun Main terminal background │ │
│ │ d.primary color │ │
│ │ #313… backgroun Subtle background for │ │
│ │ d.message message blocks │ │
│ │ #313… backgroun Background for the input │ │
│ │ d.input prompt │ │
│ │ #39… background. Background highlight for │ │
│ │ focus selected/focused items │ │
│ │ #283… backgrou Background for added lines │ │
│ │ nd.diff. in diffs │ │
│ │ added │ │
│ │ #430… backgroun Background for removed │ │
│ │ d.diff.re lines in diffs │ │
│ │ moved │ │
│ │ (blank text.prim Primary text color (uses │ │
│ │ ) ary terminal default if blank) │ │
│ │ #6C7086 text.secon Secondary/dimmed text │ │
│ │ dary color │ │
│ │ #89B4FA text.link Hyperlink and highlighting │ │
│ │ color │ │
│ │ #CBA6F7 text.accen Accent color for │ │
│ │ t emphasis │ │
│ │ (blank) text.res Color for model response │ │
│ │ ponse text (uses terminal default │ │
│ │ if blank) │ │
│ │ #3d3f51 border.def Standard border color │ │
│ │ ault │ │
│ │ #6C7086ui.comme Color for code comments and │ │
│ │ nt metadata │ │
│ │ #6C708 ui.symbol Color for technical symbols │ │
│ │ 6 and UI icons │ │
│ │ #89B4F ui.active Border color for active or │ │
│ │ A running elements │ │
│ │ #3d3f5 ui.dark Deeply dimmed color for │ │
│ │ 1 subtle UI elements │ │
│ │ #A6E3A ui.focus Color for focused elements │ │
│ │ 1 (e.g. selected menu items, │ │
│ │ focused borders) │ │
│ │ #F38BA8status.err Color for error messages │ │
│ │ or and critical status │ │
│ │ #A6E3A1status.suc Color for success messages │ │
│ │ cess and positive status │ │
│ │ #F9E2A status.wa Color for warnings and │ │
│ │ F rning cautionary status │ │
│ │ #4796E4 ui.gradien │ │
│ │ #847ACE t │ │
│ │ #C3677F │ │
│ ╰─────────────────────────────────────────────────╯ │
│ │
│ (Use Enter to select, Tab to configure scope, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -65,7 +65,7 @@ describe('<ShellToolMessage />', () => {
['SHELL_COMMAND_NAME', SHELL_COMMAND_NAME],
['SHELL_TOOL_NAME', SHELL_TOOL_NAME],
])('clicks inside the shell area sets focus for %s', async (_, name) => {
const { lastFrame, simulateClick } = renderShell(
const { lastFrame, simulateClick, unmount } = renderShell(
{ name },
{ mouseEventsEnabled: true },
);
@@ -79,6 +79,7 @@ describe('<ShellToolMessage />', () => {
await waitFor(() => {
expect(mockSetEmbeddedShellFocused).toHaveBeenCalledWith(true);
});
unmount();
});
it('resets focus when shell finishes', async () => {
let updateStatus: (s: CoreToolCallStatus) => void = () => {};
@@ -91,7 +92,7 @@ describe('<ShellToolMessage />', () => {
return <ShellToolMessage {...baseProps} status={status} ptyId={1} />;
};
const { lastFrame } = renderWithProviders(<Wrapper />, {
const { lastFrame, unmount } = renderWithProviders(<Wrapper />, {
uiActions,
uiState: {
streamingState: StreamingState.Idle,
@@ -115,6 +116,7 @@ describe('<ShellToolMessage />', () => {
expect(mockSetEmbeddedShellFocused).toHaveBeenCalledWith(false);
expect(lastFrame()).not.toContain('(Shift+Tab to unfocus)');
});
unmount();
});
});
@@ -164,9 +166,13 @@ describe('<ShellToolMessage />', () => {
},
],
])('%s', async (_, props, options) => {
const { lastFrame, waitUntilReady } = renderShell(props, options);
const { lastFrame, waitUntilReady, unmount } = renderShell(
props,
options,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
@@ -197,7 +203,7 @@ describe('<ShellToolMessage />', () => {
false,
],
])('%s', async (_, availableTerminalHeight, expectedMaxLines, focused) => {
const { lastFrame, waitUntilReady } = renderShell(
const { lastFrame, waitUntilReady, unmount } = renderShell(
{
resultDisplay: LONG_OUTPUT,
renderOutputAsMarkdown: false,
@@ -218,10 +224,11 @@ describe('<ShellToolMessage />', () => {
const frame = lastFrame();
expect(frame.match(/Line \d+/g)?.length).toBe(expectedMaxLines);
expect(frame).toMatchSnapshot();
unmount();
});
it('fully expands in standard mode when availableTerminalHeight is undefined', async () => {
const { lastFrame } = renderShell(
const { lastFrame, unmount } = renderShell(
{
resultDisplay: LONG_OUTPUT,
renderOutputAsMarkdown: false,
@@ -236,10 +243,11 @@ describe('<ShellToolMessage />', () => {
// Should show all 100 lines
expect(frame.match(/Line \d+/g)?.length).toBe(100);
});
unmount();
});
it('fully expands in alternate buffer mode when constrainHeight is false and isExpandable is true', async () => {
const { lastFrame, waitUntilReady } = renderShell(
const { lastFrame, waitUntilReady, unmount } = renderShell(
{
resultDisplay: LONG_OUTPUT,
renderOutputAsMarkdown: false,
@@ -262,10 +270,11 @@ describe('<ShellToolMessage />', () => {
expect(frame.match(/Line \d+/g)?.length).toBe(100);
});
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('stays constrained in alternate buffer mode when isExpandable is false even if constrainHeight is false', async () => {
const { lastFrame, waitUntilReady } = renderShell(
const { lastFrame, waitUntilReady, unmount } = renderShell(
{
resultDisplay: LONG_OUTPUT,
renderOutputAsMarkdown: false,
@@ -288,6 +297,7 @@ describe('<ShellToolMessage />', () => {
expect(frame.match(/Line \d+/g)?.length).toBe(15);
});
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
});
@@ -125,7 +125,11 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
borderDimColor={borderDimColor}
containerRef={headerRef}
>
<ToolStatusIndicator status={status} name={name} />
<ToolStatusIndicator
status={status}
name={name}
isFocused={isThisShellFocused}
/>
<ToolInfo
name={name}
@@ -88,7 +88,11 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
borderColor={borderColor}
borderDimColor={borderDimColor}
>
<ToolStatusIndicator status={status} name={name} />
<ToolStatusIndicator
status={status}
name={name}
isFocused={isThisShellFocused}
/>
<ToolInfo
name={name}
status={status}
@@ -7,7 +7,7 @@
import React, { useState, useEffect } from 'react';
import { Box, Text } from 'ink';
import { ToolCallStatus, mapCoreStatusToDisplayStatus } from '../../types.js';
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import { CliSpinner } from '../CliSpinner.js';
import {
SHELL_COMMAND_NAME,
SHELL_NAME,
@@ -123,7 +123,7 @@ export const FocusHint: React.FC<{
return (
<Box marginLeft={1} flexShrink={0}>
<Text color={theme.text.accent}>
<Text color={isThisShellFocused ? theme.ui.focus : theme.ui.active}>
{isThisShellFocused
? `(${formatCommand(Command.UNFOCUS_SHELL_INPUT)} to unfocus)`
: `(${formatCommand(Command.FOCUS_SHELL_INPUT)} to focus)`}
@@ -137,15 +137,21 @@ export type TextEmphasis = 'high' | 'medium' | 'low';
type ToolStatusIndicatorProps = {
status: CoreToolCallStatus;
name: string;
isFocused?: boolean;
};
export const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
status: coreStatus,
name,
isFocused,
}) => {
const status = mapCoreStatusToDisplayStatus(coreStatus);
const isShell = isShellTool(name);
const statusColor = isShell ? theme.ui.symbol : theme.status.warning;
const statusColor = isFocused
? theme.ui.focus
: isShell
? theme.ui.active
: theme.status.warning;
return (
<Box minWidth={STATUS_INDICATOR_WIDTH}>
@@ -153,10 +159,9 @@ export const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
<Text color={theme.status.success}>{TOOL_STATUS.PENDING}</Text>
)}
{status === ToolCallStatus.Executing && (
<GeminiRespondingSpinner
spinnerType="toggle"
nonRespondingDisplay={TOOL_STATUS.EXECUTING}
/>
<Text color={statusColor}>
<CliSpinner type="toggle" />
</Text>
)}
{status === ToolCallStatus.Success && (
<Text color={theme.status.success} aria-label={'Success:'}>
@@ -29,7 +29,7 @@ export const UserMessage: React.FC<UserMessageProps> = ({ text, width }) => {
const config = useConfig();
const useBackgroundColor = config.getUseBackgroundColor();
const textColor = isSlashCommand ? theme.text.accent : theme.text.secondary;
const textColor = isSlashCommand ? theme.text.accent : theme.text.primary;
const displayText = useMemo(() => {
if (!text) return text;
@@ -7,7 +7,7 @@ Note: Command contains redirection which can be undesirable.
Tip: Toggle auto-edit (Shift+Tab) to allow redirection in the future.
Allow execution of: 'echo, redirection (>)'?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. No, suggest changes (esc)
"
@@ -2,7 +2,7 @@
exports[`<ShellToolMessage /> > Height Constraints > defaults to ACTIVE_SHELL_MAX_LINES in alternate buffer when availableTerminalHeight is undefined 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command │
Shell Command A shell command │
│ │
│ Line 86 │
│ Line 87 │
@@ -131,7 +131,7 @@ exports[`<ShellToolMessage /> > Height Constraints > fully expands in alternate
exports[`<ShellToolMessage /> > Height Constraints > respects availableTerminalHeight when it is smaller than ACTIVE_SHELL_MAX_LINES 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command │
Shell Command A shell command │
│ │
│ Line 93 │
│ Line 94 │
@@ -168,7 +168,7 @@ exports[`<ShellToolMessage /> > Height Constraints > stays constrained in altern
exports[`<ShellToolMessage /> > Height Constraints > uses ACTIVE_SHELL_MAX_LINES when availableTerminalHeight is large 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command │
Shell Command A shell command │
│ │
│ Line 86 │
│ Line 87 │
@@ -190,7 +190,7 @@ exports[`<ShellToolMessage /> > Height Constraints > uses ACTIVE_SHELL_MAX_LINES
exports[`<ShellToolMessage /> > Height Constraints > uses full availableTerminalHeight when focused in alternate buffer mode 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command (Shift+Tab to unfocus) │
Shell Command A shell command (Shift+Tab to unfocus) │
│ │
│ Line 3 │
│ Line 4 │
@@ -295,7 +295,7 @@ exports[`<ShellToolMessage /> > Height Constraints > uses full availableTerminal
exports[`<ShellToolMessage /> > Snapshots > renders in Alternate Buffer mode while focused 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command (Shift+Tab to unfocus) │
Shell Command A shell command (Shift+Tab to unfocus) │
│ │
│ Test result │
"
@@ -303,7 +303,7 @@ exports[`<ShellToolMessage /> > Snapshots > renders in Alternate Buffer mode whi
exports[`<ShellToolMessage /> > Snapshots > renders in Alternate Buffer mode while unfocused 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command │
Shell Command A shell command │
│ │
│ Test result │
"
@@ -319,7 +319,7 @@ exports[`<ShellToolMessage /> > Snapshots > renders in Error state 1`] = `
exports[`<ShellToolMessage /> > Snapshots > renders in Executing state 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A shell command │
Shell Command A shell command │
│ │
│ Test result │
"
@@ -6,7 +6,7 @@ ls -la
whoami
Allow execution of 3 commands?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. No, suggest changes (esc)
"
@@ -19,7 +19,7 @@ URLs to fetch:
- https://raw.githubusercontent.com/google/gemini-react/main/README.md
Do you want to proceed?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. No, suggest changes (esc)
"
@@ -29,7 +29,7 @@ exports[`ToolConfirmationMessage > should not display urls if prompt and url are
"https://example.com
Do you want to proceed?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. No, suggest changes (esc)
"
@@ -40,7 +40,7 @@ exports[`ToolConfirmationMessage > should strip BiDi characters from MCP tool an
Tool: testtool
Allow execution of MCP tool "testtool" from server "testserver"?
● 1. Allow once
● 1. Allow once
2. Allow tool for this session
3. Allow all server tools for this session
4. No, suggest changes (esc)
@@ -55,7 +55,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for edit confirmations'
╰──────────────────────────────────────────────────────────────────────────────╯
Apply this change?
● 1. Allow once
● 1. Allow once
2. Modify with external editor
3. No, suggest changes (esc)
"
@@ -69,7 +69,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for edit confirmations'
╰──────────────────────────────────────────────────────────────────────────────╯
Apply this change?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. Modify with external editor
4. No, suggest changes (esc)
@@ -80,7 +80,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for exec confirmations'
"echo "hello"
Allow execution of: 'echo'?
● 1. Allow once
● 1. Allow once
2. No, suggest changes (esc)
"
`;
@@ -89,7 +89,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for exec confirmations'
"echo "hello"
Allow execution of: 'echo'?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. No, suggest changes (esc)
"
@@ -99,7 +99,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for info confirmations'
"https://example.com
Do you want to proceed?
● 1. Allow once
● 1. Allow once
2. No, suggest changes (esc)
"
`;
@@ -108,7 +108,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for info confirmations'
"https://example.com
Do you want to proceed?
● 1. Allow once
● 1. Allow once
2. Allow for this session
3. No, suggest changes (esc)
"
@@ -119,7 +119,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for mcp confirmations' >
Tool: test-tool
Allow execution of MCP tool "test-tool" from server "test-server"?
● 1. Allow once
● 1. Allow once
2. No, suggest changes (esc)
"
`;
@@ -129,7 +129,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for mcp confirmations' >
Tool: test-tool
Allow execution of MCP tool "test-tool" from server "test-server"?
● 1. Allow once
● 1. Allow once
2. Allow tool for this session
3. Allow all server tools for this session
4. No, suggest changes (esc)
@@ -71,7 +71,7 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls incl
│ │
│ Test result │
│ │
run_shell_command Run command │
run_shell_command Run command │
│ │
│ Test result │
╰──────────────────────────────────────────────────────────────────────────╯
@@ -29,7 +29,7 @@ exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows - for Canceled
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows MockRespondingSpinner for Executing status when streamingState is Responding 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockRespondingSpinnertest-tool A tool for testing │
test-tool A tool for testing
│ │
│ Test result │
"
@@ -45,7 +45,7 @@ exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows o for Pending s
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows paused spinner for Executing status when streamingState is Idle 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockRespondingSpinnertest-tool A tool for testing │
test-tool A tool for testing
│ │
│ Test result │
"
@@ -53,7 +53,7 @@ exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows paused spinner
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows paused spinner for Executing status when streamingState is WaitingForConfirmation 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockRespondingSpinnertest-tool A tool for testing │
test-tool A tool for testing
│ │
│ Test result │
"
@@ -94,7 +94,7 @@ exports[`<ToolMessage /> > renders DiffRenderer for diff results 1`] = `
exports[`<ToolMessage /> > renders McpProgressIndicator with percentage and message for executing tools 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockRespondingSpinnertest-tool A tool for testing │
test-tool A tool for testing
│ │
│ ████████░░░░░░░░░░░░ 42% │
│ Working on it... │
@@ -128,7 +128,7 @@ exports[`<ToolMessage /> > renders emphasis correctly 2`] = `
exports[`<ToolMessage /> > renders indeterminate progress when total is missing 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockRespondingSpinnertest-tool A tool for testing │
test-tool A tool for testing
│ │
│ ███████░░░░░░░░░░░░░ 7 │
│ Test result │
@@ -137,7 +137,7 @@ exports[`<ToolMessage /> > renders indeterminate progress when total is missing
exports[`<ToolMessage /> > renders only percentage when progressMessage is missing 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
MockRespondingSpinnertest-tool A tool for testing │
test-tool A tool for testing
│ │
│ ███████████████░░░░░ 75% │
│ Test result │
@@ -2,63 +2,63 @@
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay even with NO output > after-delay-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing (Tab to focus) │
Shell Command A tool for testing (Tab to focus) │
│ │
"
`;
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay even with NO output > initial-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing │
Shell Command A tool for testing │
│ │
"
`;
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay with output > after-delay-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing (Tab to focus) │
Shell Command A tool for testing (Tab to focus) │
│ │
"
`;
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay with output > initial-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing │
Shell Command A tool for testing │
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay even with NO output > after-delay-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing (Tab to focus) │
Shell Command A tool for testing (Tab to focus) │
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay even with NO output > initial-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing │
Shell Command A tool for testing │
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay with output > after-delay-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing (Tab to focus) │
Shell Command A tool for testing (Tab to focus) │
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay with output > initial-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command A tool for testing │
Shell Command A tool for testing │
│ │
"
`;
exports[`Focus Hint > handles long descriptions by shrinking them to show the focus hint > long-description 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
Shell Command AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA… (Tab to focus) │
Shell Command AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA… (Tab to focus) │
│ │
"
`;
@@ -19,13 +19,15 @@ vi.mock('../../hooks/useSelectionList.js');
const mockTheme = {
text: { primary: 'COLOR_PRIMARY', secondary: 'COLOR_SECONDARY' },
status: { success: 'COLOR_SUCCESS' },
ui: { focus: 'COLOR_FOCUS' },
background: { focus: 'COLOR_FOCUS_BG' },
} as typeof theme;
vi.mock('../../semantic-colors.js', () => ({
theme: {
text: { primary: 'COLOR_PRIMARY', secondary: 'COLOR_SECONDARY' },
status: { success: 'COLOR_SUCCESS' },
ui: { focus: 'COLOR_FOCUS' },
background: { focus: 'COLOR_FOCUS_BG' },
},
}));
@@ -161,8 +163,8 @@ describe('BaseSelectionList', () => {
expect(mockRenderItem).toHaveBeenCalledWith(
items[0],
expect.objectContaining({
titleColor: mockTheme.status.success,
numberColor: mockTheme.status.success,
titleColor: mockTheme.ui.focus,
numberColor: mockTheme.ui.focus,
isSelected: true,
}),
);
@@ -207,8 +209,8 @@ describe('BaseSelectionList', () => {
expect(mockRenderItem).toHaveBeenCalledWith(
items[1],
expect.objectContaining({
titleColor: mockTheme.status.success,
numberColor: mockTheme.status.success,
titleColor: mockTheme.ui.focus,
numberColor: mockTheme.ui.focus,
isSelected: true,
}),
);
@@ -267,7 +269,7 @@ describe('BaseSelectionList', () => {
items[0],
expect.objectContaining({
isSelected: true,
titleColor: mockTheme.status.success,
titleColor: mockTheme.ui.focus,
numberColor: mockTheme.text.secondary,
}),
);
@@ -117,8 +117,8 @@ export function BaseSelectionList<
let numberColor = theme.text.primary;
if (isSelected) {
titleColor = theme.status.success;
numberColor = theme.status.success;
titleColor = theme.ui.focus;
numberColor = theme.ui.focus;
} else if (item.disabled) {
titleColor = theme.text.secondary;
numberColor = theme.text.secondary;
@@ -137,11 +137,15 @@ export function BaseSelectionList<
)}.`;
return (
<Box key={item.key} alignItems="flex-start">
<Box
key={item.key}
alignItems="flex-start"
backgroundColor={isSelected ? theme.background.focus : undefined}
>
{/* Radio button indicator */}
<Box minWidth={2} flexShrink={0}>
<Text
color={isSelected ? theme.status.success : theme.text.primary}
color={isSelected ? theme.ui.focus : theme.text.primary}
aria-hidden
>
{isSelected ? '●' : ' '}
@@ -459,7 +459,7 @@ export function BaseSettingsDialog({
editingKey
? theme.border.default
: focusSection === 'settings'
? theme.border.focused
? theme.ui.focus
: theme.border.default
}
paddingX={1}
@@ -522,12 +522,17 @@ export function BaseSettingsDialog({
return (
<React.Fragment key={item.key}>
<Box marginX={1} flexDirection="row" alignItems="flex-start">
<Box
marginX={1}
flexDirection="row"
alignItems="flex-start"
backgroundColor={
isActive ? theme.background.focus : undefined
}
>
<Box minWidth={2} flexShrink={0}>
<Text
color={
isActive ? theme.status.success : theme.text.secondary
}
color={isActive ? theme.ui.focus : theme.text.secondary}
>
{isActive ? '●' : ''}
</Text>
@@ -544,9 +549,7 @@ export function BaseSettingsDialog({
minWidth={0}
>
<Text
color={
isActive ? theme.status.success : theme.text.primary
}
color={isActive ? theme.ui.focus : theme.text.primary}
>
{item.label}
{item.scopeMessage && (
@@ -565,7 +568,7 @@ export function BaseSettingsDialog({
<Text
color={
isActive
? theme.status.success
? theme.ui.focus
: item.isGreyedOut
? theme.text.secondary
: theme.text.primary
@@ -29,6 +29,12 @@ vi.mock('../../semantic-colors.js', () => ({
primary: 'COLOR_PRIMARY',
secondary: 'COLOR_SECONDARY',
},
ui: {
focus: 'COLOR_FOCUS',
},
background: {
focus: 'COLOR_FOCUS_BG',
},
status: {
success: 'COLOR_SUCCESS',
},
@@ -27,6 +27,8 @@ vi.mock('./BaseSelectionList.js', () => ({
vi.mock('../../semantic-colors.js', () => ({
theme: {
text: { secondary: 'COLOR_SECONDARY' },
ui: { focus: 'COLOR_FOCUS' },
background: { focus: 'COLOR_FOCUS_BG' },
},
}));
+1
View File
@@ -37,6 +37,7 @@ export const EXPAND_HINT_DURATION_MS = 5000;
export const DEFAULT_BACKGROUND_OPACITY = 0.16;
export const DEFAULT_INPUT_BACKGROUND_OPACITY = 0.24;
export const DEFAULT_SELECTION_OPACITY = 0.2;
export const DEFAULT_BORDER_OPACITY = 0.4;
export const KEYBOARD_SHORTCUTS_URL =
@@ -29,6 +29,9 @@ vi.mock('../semantic-colors.js', () => ({
status: {
warning: 'mock-warning-color',
},
ui: {
focus: 'mock-focus-color',
},
},
}));
+1
View File
@@ -23,6 +23,7 @@ const ansiColors: ColorsTheme = {
Comment: 'gray',
Gray: 'gray',
DarkGray: 'gray',
FocusBackground: 'black',
GradientColors: ['cyan', 'green'],
};
+18 -137
View File
@@ -4,38 +4,25 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import tinygradient from 'tinygradient';
import tinycolor from 'tinycolor2';
import {
resolveColor,
interpolateColor,
getThemeTypeFromBackgroundColor,
INK_SUPPORTED_NAMES,
INK_NAME_TO_HEX_MAP,
getLuminance,
CSS_NAME_TO_HEX_MAP,
} from './theme.js';
// Define the set of Ink's named colors for quick lookup
export const INK_SUPPORTED_NAMES = new Set([
'black',
'red',
'green',
'yellow',
'blue',
'cyan',
'magenta',
'white',
'gray',
'grey',
'blackbright',
'redbright',
'greenbright',
'yellowbright',
'bluebright',
'cyanbright',
'magentabright',
'whitebright',
]);
// Use tinycolor's built-in names map for CSS colors, excluding ones Ink supports
export const CSS_NAME_TO_HEX_MAP = Object.fromEntries(
Object.entries(tinycolor.names)
.filter(([name]) => !INK_SUPPORTED_NAMES.has(name))
.map(([name, hex]) => [name, `#${hex}`]),
);
export {
resolveColor,
interpolateColor,
getThemeTypeFromBackgroundColor,
INK_SUPPORTED_NAMES,
INK_NAME_TO_HEX_MAP,
getLuminance,
CSS_NAME_TO_HEX_MAP,
};
/**
* Checks if a color string is valid (hex, Ink-supported color name, or CSS color name).
@@ -66,45 +53,6 @@ export function isValidColor(color: string): boolean {
return false;
}
/**
* Resolves a CSS color value (name or hex) into an Ink-compatible color string.
* @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki').
* @returns An Ink-compatible color string (hex or name), or undefined if not resolvable.
*/
export function resolveColor(colorValue: string): string | undefined {
const lowerColor = colorValue.toLowerCase();
// 1. Check if it's already a hex code and valid
if (lowerColor.startsWith('#')) {
if (/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
return lowerColor;
} else {
return undefined;
}
}
// Handle hex codes without #
if (/^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
return `#${lowerColor}`;
}
// 2. Check if it's an Ink supported name (lowercase)
if (INK_SUPPORTED_NAMES.has(lowerColor)) {
return lowerColor; // Use Ink name directly
}
// 3. Check if it's a known CSS name we can map to hex
if (CSS_NAME_TO_HEX_MAP[lowerColor]) {
return CSS_NAME_TO_HEX_MAP[lowerColor]; // Use mapped hex
}
// 4. Could not resolve
debugLogger.warn(
`[ColorUtils] Could not resolve color "${colorValue}" to an Ink-compatible format.`,
);
return undefined;
}
/**
* Returns a "safe" background color to use in low-color terminals if the
* terminal background is a standard black or white.
@@ -132,73 +80,6 @@ export function getSafeLowColorBackground(
return undefined;
}
export function interpolateColor(
color1: string,
color2: string,
factor: number,
) {
if (factor <= 0 && color1) {
return color1;
}
if (factor >= 1 && color2) {
return color2;
}
if (!color1 || !color2) {
return '';
}
const gradient = tinygradient(color1, color2);
const color = gradient.rgbAt(factor);
return color.toHexString();
}
export function getThemeTypeFromBackgroundColor(
backgroundColor: string | undefined,
): 'light' | 'dark' | undefined {
if (!backgroundColor) {
return undefined;
}
const resolvedColor = resolveColor(backgroundColor);
if (!resolvedColor) {
return undefined;
}
const luminance = getLuminance(resolvedColor);
return luminance > 128 ? 'light' : 'dark';
}
// Mapping for ANSI bright colors that are not in tinycolor's standard CSS names
export const INK_NAME_TO_HEX_MAP: Readonly<Record<string, string>> = {
blackbright: '#555555',
redbright: '#ff5555',
greenbright: '#55ff55',
yellowbright: '#ffff55',
bluebright: '#5555ff',
magentabright: '#ff55ff',
cyanbright: '#55ffff',
whitebright: '#ffffff',
};
/**
* Calculates the relative luminance of a color.
* See https://www.w3.org/TR/WCAG20/#relativeluminancedef
*
* @param color Color string (hex or Ink-supported name)
* @returns Luminance value (0-255)
*/
export function getLuminance(color: string): number {
const resolved = color.toLowerCase();
const hex = INK_NAME_TO_HEX_MAP[resolved] || resolved;
const colorObj = tinycolor(hex);
if (!colorObj.isValid()) {
return 0;
}
// tinycolor returns 0-1, we need 0-255
return colorObj.getLuminance() * 255;
}
// Hysteresis thresholds to prevent flickering when the background color
// is ambiguous (near the midpoint).
export const LIGHT_THEME_LUMINANCE_THRESHOLD = 140;
@@ -23,6 +23,7 @@ const githubLightColors: ColorsTheme = {
Comment: '#998',
Gray: '#999',
DarkGray: interpolateColor('#999', '#f8f8f8', 0.5),
FocusColor: '#458', // AccentBlue for GitHub branding
GradientColors: ['#458', '#008080'],
};
+1
View File
@@ -23,6 +23,7 @@ const holidayColors: ColorsTheme = {
Comment: '#8FBC8F',
Gray: '#D7F5D3',
DarkGray: interpolateColor('#D7F5D3', '#151B18', 0.5),
FocusColor: '#33F9FF', // AccentCyan for neon pop
GradientColors: ['#FF0000', '#FFFFFF', '#008000'],
};
+4 -1
View File
@@ -26,6 +26,7 @@ const noColorColorsTheme: ColorsTheme = {
DarkGray: '',
InputBackground: '',
MessageBackground: '',
FocusBackground: '',
};
const noColorSemanticColors: SemanticColors = {
@@ -40,6 +41,7 @@ const noColorSemanticColors: SemanticColors = {
primary: '',
message: '',
input: '',
focus: '',
diff: {
added: '',
removed: '',
@@ -47,12 +49,13 @@ const noColorSemanticColors: SemanticColors = {
},
border: {
default: '',
focused: '',
},
ui: {
comment: '',
symbol: '',
active: '',
dark: '',
focus: '',
gradient: [],
},
status: {
@@ -18,6 +18,7 @@ export interface SemanticColors {
primary: string;
message: string;
input: string;
focus: string;
diff: {
added: string;
removed: string;
@@ -25,12 +26,13 @@ export interface SemanticColors {
};
border: {
default: string;
focused: string;
};
ui: {
comment: string;
symbol: string;
active: string;
dark: string;
focus: string;
gradient: string[] | undefined;
};
status: {
@@ -52,6 +54,7 @@ export const lightSemanticColors: SemanticColors = {
primary: lightTheme.Background,
message: lightTheme.MessageBackground!,
input: lightTheme.InputBackground!,
focus: lightTheme.FocusBackground!,
diff: {
added: lightTheme.DiffAdded,
removed: lightTheme.DiffRemoved,
@@ -59,12 +62,13 @@ export const lightSemanticColors: SemanticColors = {
},
border: {
default: lightTheme.DarkGray,
focused: lightTheme.AccentBlue,
},
ui: {
comment: lightTheme.Comment,
symbol: lightTheme.Gray,
active: lightTheme.AccentBlue,
dark: lightTheme.DarkGray,
focus: lightTheme.AccentGreen,
gradient: lightTheme.GradientColors,
},
status: {
@@ -86,6 +90,7 @@ export const darkSemanticColors: SemanticColors = {
primary: darkTheme.Background,
message: darkTheme.MessageBackground!,
input: darkTheme.InputBackground!,
focus: darkTheme.FocusBackground!,
diff: {
added: darkTheme.DiffAdded,
removed: darkTheme.DiffRemoved,
@@ -93,12 +98,13 @@ export const darkSemanticColors: SemanticColors = {
},
border: {
default: darkTheme.DarkGray,
focused: darkTheme.AccentBlue,
},
ui: {
comment: darkTheme.Comment,
symbol: darkTheme.Gray,
active: darkTheme.AccentBlue,
dark: darkTheme.DarkGray,
focus: darkTheme.AccentGreen,
gradient: darkTheme.GradientColors,
},
status: {
+6 -3
View File
@@ -4,8 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { type ColorsTheme, Theme } from './theme.js';
import { type ColorsTheme, Theme, interpolateColor } from './theme.js';
import { type SemanticColors } from './semantic-tokens.js';
import { DEFAULT_SELECTION_OPACITY } from '../constants.js';
const solarizedDarkColors: ColorsTheme = {
type: 'dark',
@@ -38,6 +39,7 @@ const semanticColors: SemanticColors = {
primary: '#002b36',
message: '#073642',
input: '#073642',
focus: interpolateColor('#002b36', '#859900', DEFAULT_SELECTION_OPACITY),
diff: {
added: '#00382f',
removed: '#3d0115',
@@ -45,13 +47,14 @@ const semanticColors: SemanticColors = {
},
border: {
default: '#073642',
focused: '#586e75',
},
ui: {
comment: '#586e75',
symbol: '#93a1a1',
active: '#268bd2',
dark: '#073642',
gradient: ['#268bd2', '#2aa198'],
focus: '#859900',
gradient: ['#268bd2', '#2aa198', '#859900'],
},
status: {
success: '#859900',
@@ -4,8 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { type ColorsTheme, Theme } from './theme.js';
import { type ColorsTheme, Theme, interpolateColor } from './theme.js';
import { type SemanticColors } from './semantic-tokens.js';
import { DEFAULT_SELECTION_OPACITY } from '../constants.js';
const solarizedLightColors: ColorsTheme = {
type: 'light',
@@ -38,6 +39,7 @@ const semanticColors: SemanticColors = {
primary: '#fdf6e3',
message: '#eee8d5',
input: '#eee8d5',
focus: interpolateColor('#fdf6e3', '#859900', DEFAULT_SELECTION_OPACITY),
diff: {
added: '#d7f2d7',
removed: '#f2d7d7',
@@ -45,13 +47,14 @@ const semanticColors: SemanticColors = {
},
border: {
default: '#eee8d5',
focused: '#93a1a1',
},
ui: {
comment: '#93a1a1',
symbol: '#586e75',
active: '#268bd2',
dark: '#eee8d5',
gradient: ['#268bd2', '#2aa198'],
focus: '#859900',
gradient: ['#268bd2', '#2aa198', '#859900'],
},
status: {
success: '#859900',
+12 -3
View File
@@ -22,16 +22,18 @@ import * as fs from 'node:fs';
import * as path from 'node:path';
import type { Theme, ThemeType, ColorsTheme } from './theme.js';
import type { CustomTheme } from '@google/gemini-cli-core';
import { createCustomTheme, validateCustomTheme } from './theme.js';
import type { SemanticColors } from './semantic-tokens.js';
import {
createCustomTheme,
validateCustomTheme,
interpolateColor,
getThemeTypeFromBackgroundColor,
resolveColor,
} from './color-utils.js';
} from './theme.js';
import type { SemanticColors } from './semantic-tokens.js';
import {
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
DEFAULT_SELECTION_OPACITY,
DEFAULT_BORDER_OPACITY,
} from '../constants.js';
import { ANSI } from './ansi.js';
@@ -369,6 +371,11 @@ class ThemeManager {
colors.Gray,
DEFAULT_BACKGROUND_OPACITY,
),
FocusBackground: interpolateColor(
this.terminalBackground,
activeTheme.colors.FocusColor ?? activeTheme.colors.AccentGreen,
DEFAULT_SELECTION_OPACITY,
),
};
} else {
this.cachedColors = colors;
@@ -402,6 +409,7 @@ class ThemeManager {
primary: this.terminalBackground,
message: colors.MessageBackground!,
input: colors.InputBackground!,
focus: colors.FocusBackground!,
},
border: {
...semanticColors.border,
@@ -410,6 +418,7 @@ class ThemeManager {
ui: {
...semanticColors.ui,
dark: colors.DarkGray,
focus: colors.FocusColor ?? colors.AccentGreen,
},
};
} else {
+177 -13
View File
@@ -8,18 +8,153 @@ import type { CSSProperties } from 'react';
import type { SemanticColors } from './semantic-tokens.js';
import {
resolveColor,
interpolateColor,
getThemeTypeFromBackgroundColor,
} from './color-utils.js';
import type { CustomTheme } from '@google/gemini-cli-core';
import {
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
DEFAULT_SELECTION_OPACITY,
DEFAULT_BORDER_OPACITY,
} from '../constants.js';
import tinygradient from 'tinygradient';
import tinycolor from 'tinycolor2';
// Define the set of Ink's named colors for quick lookup
export const INK_SUPPORTED_NAMES = new Set([
'black',
'red',
'green',
'yellow',
'blue',
'cyan',
'magenta',
'white',
'gray',
'grey',
'blackbright',
'redbright',
'greenbright',
'yellowbright',
'bluebright',
'cyanbright',
'magentabright',
'whitebright',
]);
// Use tinycolor's built-in names map for CSS colors, excluding ones Ink supports
export const CSS_NAME_TO_HEX_MAP = Object.fromEntries(
Object.entries(tinycolor.names)
.filter(([name]) => !INK_SUPPORTED_NAMES.has(name))
.map(([name, hex]) => [name, `#${hex}`]),
);
// Mapping for ANSI bright colors that are not in tinycolor's standard CSS names
export const INK_NAME_TO_HEX_MAP: Readonly<Record<string, string>> = {
blackbright: '#555555',
redbright: '#ff5555',
greenbright: '#55ff55',
yellowbright: '#ffff55',
bluebright: '#5555ff',
magentabright: '#ff55ff',
cyanbright: '#55ffff',
whitebright: '#ffffff',
};
/**
* Calculates the relative luminance of a color.
* See https://www.w3.org/TR/WCAG20/#relativeluminancedef
*
* @param color Color string (hex or Ink-supported name)
* @returns Luminance value (0-255)
*/
export function getLuminance(color: string): number {
const resolved = color.toLowerCase();
const hex = INK_NAME_TO_HEX_MAP[resolved] || resolved;
const colorObj = tinycolor(hex);
if (!colorObj.isValid()) {
return 0;
}
// tinycolor returns 0-1, we need 0-255
return colorObj.getLuminance() * 255;
}
/**
* Resolves a CSS color value (name or hex) into an Ink-compatible color string.
* @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki').
* @returns An Ink-compatible color string (hex or name), or undefined if not resolvable.
*/
export function resolveColor(colorValue: string): string | undefined {
const lowerColor = colorValue.toLowerCase();
// 1. Check if it's already a hex code and valid
if (lowerColor.startsWith('#')) {
if (/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
return lowerColor;
} else {
return undefined;
}
}
// Handle hex codes without #
if (/^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
return `#${lowerColor}`;
}
// 2. Check if it's an Ink supported name (lowercase)
if (INK_SUPPORTED_NAMES.has(lowerColor)) {
return lowerColor; // Use Ink name directly
}
// 3. Check if it's a known CSS name we can map to hex
// We can't import CSS_NAME_TO_HEX_MAP here due to circular deps,
// but we can use tinycolor directly for named colors.
const colorObj = tinycolor(lowerColor);
if (colorObj.isValid()) {
return colorObj.toHexString();
}
// 4. Could not resolve
return undefined;
}
export function interpolateColor(
color1: string,
color2: string,
factor: number,
) {
if (factor <= 0 && color1) {
return color1;
}
if (factor >= 1 && color2) {
return color2;
}
if (!color1 || !color2) {
return '';
}
try {
const gradient = tinygradient(color1, color2);
const color = gradient.rgbAt(factor);
return color.toHexString();
} catch (_e) {
return color1;
}
}
export function getThemeTypeFromBackgroundColor(
backgroundColor: string | undefined,
): 'light' | 'dark' | undefined {
if (!backgroundColor) {
return undefined;
}
const resolvedColor = resolveColor(backgroundColor);
if (!resolvedColor) {
return undefined;
}
const luminance = getLuminance(resolvedColor);
return luminance > 128 ? 'light' : 'dark';
}
export type { CustomTheme };
@@ -43,6 +178,8 @@ export interface ColorsTheme {
DarkGray: string;
InputBackground?: string;
MessageBackground?: string;
FocusBackground?: string;
FocusColor?: string;
GradientColors?: string[];
}
@@ -70,7 +207,12 @@ export const lightTheme: ColorsTheme = {
MessageBackground: interpolateColor(
'#FAFAFA',
'#97a0b0',
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
FocusBackground: interpolateColor(
'#FAFAFA',
'#3CA84B',
DEFAULT_SELECTION_OPACITY,
),
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
@@ -99,7 +241,12 @@ export const darkTheme: ColorsTheme = {
MessageBackground: interpolateColor(
'#1E1E2E',
'#6C7086',
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
FocusBackground: interpolateColor(
'#1E1E2E',
'#A6E3A1',
DEFAULT_SELECTION_OPACITY,
),
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
@@ -122,6 +269,7 @@ export const ansiTheme: ColorsTheme = {
DarkGray: 'gray',
InputBackground: 'black',
MessageBackground: 'black',
FocusBackground: 'black',
};
export class Theme {
@@ -164,7 +312,7 @@ export class Theme {
interpolateColor(
this.colors.Background,
this.colors.Gray,
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
input:
this.colors.InputBackground ??
@@ -173,6 +321,13 @@ export class Theme {
this.colors.Gray,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
focus:
this.colors.FocusBackground ??
interpolateColor(
this.colors.Background,
this.colors.FocusColor ?? this.colors.AccentGreen,
DEFAULT_SELECTION_OPACITY,
),
diff: {
added: this.colors.DiffAdded,
removed: this.colors.DiffRemoved,
@@ -180,12 +335,13 @@ export class Theme {
},
border: {
default: this.colors.DarkGray,
focused: this.colors.AccentBlue,
},
ui: {
comment: this.colors.Gray,
symbol: this.colors.AccentCyan,
active: this.colors.AccentBlue,
dark: this.colors.DarkGray,
focus: this.colors.FocusColor ?? this.colors.AccentGreen,
gradient: this.colors.GradientColors,
},
status: {
@@ -292,8 +448,14 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
MessageBackground: interpolateColor(
customTheme.background?.primary ?? customTheme.Background ?? '',
customTheme.text?.secondary ?? customTheme.Gray ?? '',
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
FocusBackground: interpolateColor(
customTheme.background?.primary ?? customTheme.Background ?? '',
customTheme.status?.success ?? customTheme.AccentGreen ?? '#3CA84B', // Fallback to a default green if not found
DEFAULT_SELECTION_OPACITY,
),
FocusColor: customTheme.ui?.focus ?? customTheme.AccentGreen,
GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors,
};
@@ -450,6 +612,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
primary: customTheme.background?.primary ?? colors.Background,
message: colors.MessageBackground!,
input: colors.InputBackground!,
focus: colors.FocusBackground!,
diff: {
added: customTheme.background?.diff?.added ?? colors.DiffAdded,
removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,
@@ -457,12 +620,13 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
},
border: {
default: colors.DarkGray,
focused: customTheme.border?.focused ?? colors.AccentBlue,
},
ui: {
comment: customTheme.ui?.comment ?? colors.Comment,
symbol: customTheme.ui?.symbol ?? colors.Gray,
active: customTheme.ui?.active ?? colors.AccentBlue,
dark: colors.DarkGray,
focus: colors.FocusColor ?? colors.AccentGreen,
gradient: customTheme.ui?.gradient ?? colors.GradientColors,
},
status: {
+1
View File
@@ -23,6 +23,7 @@ const xcodeColors: ColorsTheme = {
Comment: '#007400',
Gray: '#c0c0c0',
DarkGray: interpolateColor('#c0c0c0', '#fff', 0.5),
FocusColor: '#1c00cf', // AccentBlue for more vibrance
GradientColors: ['#1c00cf', '#007400'],
};
@@ -19,7 +19,7 @@
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#f9e2af" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="121" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="121" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="121" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">google_web_search</text>
<text x="855" y="121" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -17,16 +17,16 @@
<text x="45" y="53" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#6c7086" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="121" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="121" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="121" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="121" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
<text x="855" y="121" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="855" y="138" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="855" y="121" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="855" y="138" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="155" fill="#ffffff" textLength="846" lengthAdjust="spacingAndGlyphs"> Running command... </text>
<text x="855" y="155" fill="#6c7086" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#6c7086" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
<text x="855" y="155" fill="#89b4fa" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#89b4fa" textLength="864" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -19,7 +19,7 @@
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#f9e2af" textLength="864" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="121" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="9" y="121" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="45" y="121" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">google_web_search</text>
<text x="855" y="121" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#f9e2af" textLength="9" lengthAdjust="spacingAndGlyphs"></text>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -8,7 +8,7 @@ exports[`MainContent tool group border SVG snapshots > should render SVG snapsho
▝▀
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
google_web_search │
google_web_search │
│ │
│ Searching... │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯"
@@ -22,7 +22,7 @@ exports[`MainContent tool group border SVG snapshots > should render SVG snapsho
▝▀
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
run_shell_command │
run_shell_command │
│ │
│ Running command... │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯"
@@ -36,7 +36,7 @@ exports[`MainContent tool group border SVG snapshots > should render SVG snapsho
▝▀
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
google_web_search │
google_web_search │
│ │
│ Searching... │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯"
@@ -4,13 +4,18 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { getToolGroupBorderAppearance } from './borderStyles.js';
import { CoreToolCallStatus } from '@google/gemini-cli-core';
import { theme } from '../semantic-colors.js';
import type { IndividualToolCallDisplay } from '../types.js';
import { renderWithProviders } from '../../test-utils/render.js';
import { MainContent } from '../components/MainContent.js';
import { Text } from 'ink';
vi.mock('../components/CliSpinner.js', () => ({
CliSpinner: () => <Text></Text>,
}));
describe('getToolGroupBorderAppearance', () => {
it('should use warning color for pending non-shell tools', () => {
@@ -60,7 +65,7 @@ describe('getToolGroupBorderAppearance', () => {
expect(appearance.borderDimColor).toBe(true);
});
it('should use symbol color for shell tools', () => {
it('should use active color for shell tools', () => {
const item = {
type: 'tool_group' as const,
tools: [
@@ -73,9 +78,28 @@ describe('getToolGroupBorderAppearance', () => {
] as IndividualToolCallDisplay[],
};
const appearance = getToolGroupBorderAppearance(item, undefined, false, []);
expect(appearance.borderColor).toBe(theme.ui.symbol);
expect(appearance.borderColor).toBe(theme.ui.active);
expect(appearance.borderDimColor).toBe(true);
});
it('should use focus color for focused shell tools', () => {
const ptyId = 123;
const item = {
type: 'tool_group' as const,
tools: [
{
name: 'run_shell_command',
status: CoreToolCallStatus.Executing,
resultDisplay: '',
callId: 'call-1',
ptyId,
},
] as IndividualToolCallDisplay[],
};
const appearance = getToolGroupBorderAppearance(item, ptyId, true, []);
expect(appearance.borderColor).toBe(theme.ui.focus);
expect(appearance.borderDimColor).toBe(false);
});
});
describe('MainContent tool group border SVG snapshots', () => {
+4 -3
View File
@@ -113,9 +113,10 @@ export function getToolGroupBorderAppearance(
isCurrentlyInShellTurn &&
!!embeddedShellFocused);
const borderColor =
(isShell && isPending) || isEffectivelyFocused
? theme.ui.symbol
const borderColor = isEffectivelyFocused
? theme.ui.focus
: isShell && isPending
? theme.ui.active
: isPending
? theme.status.warning
: theme.border.default;
@@ -17,6 +17,9 @@ vi.mock('../semantic-colors.js', () => ({
accent: 'cyan',
link: 'blue',
},
ui: {
focus: 'green',
},
},
}));
+2 -1
View File
@@ -250,11 +250,12 @@ export interface CustomTheme {
};
border?: {
default?: string;
focused?: string;
};
ui?: {
comment?: string;
symbol?: string;
active?: string;
focus?: string;
gradient?: string[];
};
status?: {