Skip to content

Commit c623406

Browse files
authored
feat(core): centralize compatibility checks and add TrueColor detection (#19478)
1 parent ef65498 commit c623406

10 files changed

Lines changed: 301 additions & 13 deletions

File tree

docs/cli/settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ they appear in the UI.
5050
| Show Thoughts in Title | `ui.showStatusInTitle` | Show Gemini CLI model thoughts in the terminal window title during the working phase | `false` |
5151
| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` |
5252
| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` |
53+
| Show Compatibility Warnings | `ui.showCompatibilityWarnings` | Show warnings about terminal or OS compatibility issues. | `true` |
5354
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
5455
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
5556
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |

docs/get-started/configuration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ their corresponding top-level category object in your `settings.json` file.
233233
- **Default:** `true`
234234
- **Requires restart:** Yes
235235

236+
- **`ui.showCompatibilityWarnings`** (boolean):
237+
- **Description:** Show warnings about terminal or OS compatibility issues.
238+
- **Default:** `true`
239+
- **Requires restart:** Yes
240+
236241
- **`ui.hideTips`** (boolean):
237242
- **Description:** Hide helpful tips in the UI
238243
- **Default:** `false`

packages/cli/src/config/settingsSchema.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,15 @@ const SETTINGS_SCHEMA = {
485485
'Show a warning when running Gemini CLI in the home directory.',
486486
showInDialog: true,
487487
},
488+
showCompatibilityWarnings: {
489+
type: 'boolean',
490+
label: 'Show Compatibility Warnings',
491+
category: 'UI',
492+
requiresRestart: true,
493+
default: true,
494+
description: 'Show warnings about terminal or OS compatibility issues.',
495+
showInDialog: true,
496+
},
488497
hideTips: {
489498
type: 'boolean',
490499
label: 'Hide Tips',

packages/cli/src/ui/components/Notifications.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,16 @@ export const Notifications = () => {
8888
)}
8989
{updateInfo && <UpdateNotification message={updateInfo.message} />}
9090
{showStartupWarnings && (
91-
<Box
92-
borderStyle="round"
93-
borderColor={theme.status.warning}
94-
paddingX={1}
95-
marginY={1}
96-
flexDirection="column"
97-
>
91+
<Box marginY={1} flexDirection="column">
9892
{startupWarnings.map((warning, index) => (
99-
<Text key={index} color={theme.status.warning}>
100-
{warning}
101-
</Text>
93+
<Box key={index} flexDirection="row">
94+
<Box width={3}>
95+
<Text color={theme.status.warning}></Text>
96+
</Box>
97+
<Box flexGrow={1}>
98+
<Text color={theme.status.warning}>{warning}</Text>
99+
</Box>
100+
</Box>
102101
))}
103102
</Box>
104103
)}

packages/cli/src/utils/userStartupWarnings.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
isFolderTrustEnabled,
1414
isWorkspaceTrusted,
1515
} from '../config/trustedFolders.js';
16+
import { getCompatibilityWarnings } from '@google/gemini-cli-core';
1617

1718
// Mock os.homedir to control the home directory in tests
1819
vi.mock('os', async (importOriginal) => {
@@ -29,6 +30,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
2930
return {
3031
...actual,
3132
homedir: () => os.homedir(),
33+
getCompatibilityWarnings: vi.fn().mockReturnValue([]),
3234
};
3335
});
3436

@@ -51,11 +53,12 @@ describe('getUserStartupWarnings', () => {
5153
isTrusted: false,
5254
source: undefined,
5355
});
56+
vi.mocked(getCompatibilityWarnings).mockReturnValue([]);
5457
});
5558

5659
afterEach(async () => {
5760
await fs.rm(testRootDir, { recursive: true, force: true });
58-
vi.clearAllMocks();
61+
vi.restoreAllMocks();
5962
});
6063

6164
describe('home directory check', () => {
@@ -135,4 +138,27 @@ describe('getUserStartupWarnings', () => {
135138
expect(warnings).toEqual([expectedWarning, expectedWarning]);
136139
});
137140
});
141+
142+
describe('compatibility warnings', () => {
143+
it('should include compatibility warnings by default', async () => {
144+
vi.mocked(getCompatibilityWarnings).mockReturnValue(['Comp warning 1']);
145+
const projectDir = path.join(testRootDir, 'project');
146+
await fs.mkdir(projectDir);
147+
148+
const warnings = await getUserStartupWarnings({}, projectDir);
149+
expect(warnings).toContain('Comp warning 1');
150+
});
151+
152+
it('should not include compatibility warnings when showCompatibilityWarnings is false', async () => {
153+
vi.mocked(getCompatibilityWarnings).mockReturnValue(['Comp warning 1']);
154+
const projectDir = path.join(testRootDir, 'project');
155+
await fs.mkdir(projectDir);
156+
157+
const warnings = await getUserStartupWarnings(
158+
{ ui: { showCompatibilityWarnings: false } },
159+
projectDir,
160+
);
161+
expect(warnings).not.toContain('Comp warning 1');
162+
});
163+
});
138164
});

packages/cli/src/utils/userStartupWarnings.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import fs from 'node:fs/promises';
88
import path from 'node:path';
99
import process from 'node:process';
10-
import { homedir } from '@google/gemini-cli-core';
10+
import { homedir, getCompatibilityWarnings } from '@google/gemini-cli-core';
1111
import type { Settings } from '../config/settingsSchema.js';
1212
import {
1313
isFolderTrustEnabled,
@@ -84,5 +84,11 @@ export async function getUserStartupWarnings(
8484
const results = await Promise.all(
8585
WARNING_CHECKS.map((check) => check.check(workspaceRoot, settings)),
8686
);
87-
return results.filter((msg) => msg !== null);
87+
const warnings = results.filter((msg) => msg !== null);
88+
89+
if (settings.ui?.showCompatibilityWarnings !== false) {
90+
warnings.push(...getCompatibilityWarnings());
91+
}
92+
93+
return warnings;
8894
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export { OAuthUtils } from './mcp/oauth-utils.js';
180180
// Export telemetry functions
181181
export * from './telemetry/index.js';
182182
export { sessionId } from './utils/session.js';
183+
export * from './utils/compatibility.js';
183184
export * from './utils/browser.js';
184185
export { Storage } from './config/storage.js';
185186

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8+
import os from 'node:os';
9+
import {
10+
isWindows10,
11+
isJetBrainsTerminal,
12+
supportsTrueColor,
13+
getCompatibilityWarnings,
14+
} from './compatibility.js';
15+
16+
vi.mock('node:os', () => ({
17+
default: {
18+
platform: vi.fn(),
19+
release: vi.fn(),
20+
},
21+
}));
22+
23+
describe('compatibility', () => {
24+
const originalGetColorDepth = process.stdout.getColorDepth;
25+
26+
afterEach(() => {
27+
process.stdout.getColorDepth = originalGetColorDepth;
28+
vi.restoreAllMocks();
29+
vi.unstubAllEnvs();
30+
});
31+
32+
describe('isWindows10', () => {
33+
it('should return true for Windows 10 (build < 22000)', () => {
34+
vi.mocked(os.platform).mockReturnValue('win32');
35+
vi.mocked(os.release).mockReturnValue('10.0.19041');
36+
expect(isWindows10()).toBe(true);
37+
});
38+
39+
it('should return false for Windows 11 (build >= 22000)', () => {
40+
vi.mocked(os.platform).mockReturnValue('win32');
41+
vi.mocked(os.release).mockReturnValue('10.0.22000');
42+
expect(isWindows10()).toBe(false);
43+
});
44+
45+
it('should return false for non-Windows platforms', () => {
46+
vi.mocked(os.platform).mockReturnValue('darwin');
47+
vi.mocked(os.release).mockReturnValue('20.6.0');
48+
expect(isWindows10()).toBe(false);
49+
});
50+
});
51+
52+
describe('isJetBrainsTerminal', () => {
53+
it('should return true when TERMINAL_EMULATOR is JetBrains-JediTerm', () => {
54+
vi.stubEnv('TERMINAL_EMULATOR', 'JetBrains-JediTerm');
55+
expect(isJetBrainsTerminal()).toBe(true);
56+
});
57+
58+
it('should return false for other terminals', () => {
59+
vi.stubEnv('TERMINAL_EMULATOR', 'something-else');
60+
expect(isJetBrainsTerminal()).toBe(false);
61+
});
62+
63+
it('should return false when TERMINAL_EMULATOR is not set', () => {
64+
vi.stubEnv('TERMINAL_EMULATOR', '');
65+
expect(isJetBrainsTerminal()).toBe(false);
66+
});
67+
});
68+
69+
describe('supportsTrueColor', () => {
70+
it('should return true when COLORTERM is truecolor', () => {
71+
vi.stubEnv('COLORTERM', 'truecolor');
72+
expect(supportsTrueColor()).toBe(true);
73+
});
74+
75+
it('should return true when COLORTERM is 24bit', () => {
76+
vi.stubEnv('COLORTERM', '24bit');
77+
expect(supportsTrueColor()).toBe(true);
78+
});
79+
80+
it('should return true when getColorDepth returns >= 24', () => {
81+
vi.stubEnv('COLORTERM', '');
82+
process.stdout.getColorDepth = vi.fn().mockReturnValue(24);
83+
expect(supportsTrueColor()).toBe(true);
84+
});
85+
86+
it('should return false when true color is not supported', () => {
87+
vi.stubEnv('COLORTERM', '');
88+
process.stdout.getColorDepth = vi.fn().mockReturnValue(8);
89+
expect(supportsTrueColor()).toBe(false);
90+
});
91+
});
92+
93+
describe('getCompatibilityWarnings', () => {
94+
beforeEach(() => {
95+
// Default to supporting true color to keep existing tests simple
96+
vi.stubEnv('COLORTERM', 'truecolor');
97+
process.stdout.getColorDepth = vi.fn().mockReturnValue(24);
98+
});
99+
100+
it('should return Windows 10 warning when detected', () => {
101+
vi.mocked(os.platform).mockReturnValue('win32');
102+
vi.mocked(os.release).mockReturnValue('10.0.19041');
103+
vi.stubEnv('TERMINAL_EMULATOR', '');
104+
105+
const warnings = getCompatibilityWarnings();
106+
expect(warnings).toContain(
107+
'Warning: Windows 10 detected. Some UI features like smooth scrolling may be degraded. Windows 11 is recommended for the best experience.',
108+
);
109+
});
110+
111+
it('should return JetBrains warning when detected', () => {
112+
vi.mocked(os.platform).mockReturnValue('darwin');
113+
vi.stubEnv('TERMINAL_EMULATOR', 'JetBrains-JediTerm');
114+
115+
const warnings = getCompatibilityWarnings();
116+
expect(warnings).toContain(
117+
'Warning: JetBrains terminal detected. You may experience rendering or scrolling issues. Using an external terminal (e.g., Windows Terminal, iTerm2) is recommended.',
118+
);
119+
});
120+
121+
it('should return true color warning when not supported', () => {
122+
vi.mocked(os.platform).mockReturnValue('darwin');
123+
vi.stubEnv('TERMINAL_EMULATOR', '');
124+
vi.stubEnv('COLORTERM', '');
125+
process.stdout.getColorDepth = vi.fn().mockReturnValue(8);
126+
127+
const warnings = getCompatibilityWarnings();
128+
expect(warnings).toContain(
129+
'Warning: True color (24-bit) support not detected. Using a terminal with true color enabled will result in a better visual experience.',
130+
);
131+
});
132+
133+
it('should return all warnings when all are detected', () => {
134+
vi.mocked(os.platform).mockReturnValue('win32');
135+
vi.mocked(os.release).mockReturnValue('10.0.19041');
136+
vi.stubEnv('TERMINAL_EMULATOR', 'JetBrains-JediTerm');
137+
vi.stubEnv('COLORTERM', '');
138+
process.stdout.getColorDepth = vi.fn().mockReturnValue(8);
139+
140+
const warnings = getCompatibilityWarnings();
141+
expect(warnings).toHaveLength(3);
142+
expect(warnings[0]).toContain('Windows 10 detected');
143+
expect(warnings[1]).toContain('JetBrains terminal detected');
144+
expect(warnings[2]).toContain('True color (24-bit) support not detected');
145+
});
146+
147+
it('should return no warnings in a standard environment with true color', () => {
148+
vi.mocked(os.platform).mockReturnValue('darwin');
149+
vi.stubEnv('TERMINAL_EMULATOR', '');
150+
vi.stubEnv('COLORTERM', 'truecolor');
151+
152+
const warnings = getCompatibilityWarnings();
153+
expect(warnings).toHaveLength(0);
154+
});
155+
});
156+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import os from 'node:os';
8+
9+
/**
10+
* Detects if the current OS is Windows 10.
11+
* Windows 11 also reports as version 10.0, but with build numbers >= 22000.
12+
*/
13+
export function isWindows10(): boolean {
14+
if (os.platform() !== 'win32') {
15+
return false;
16+
}
17+
const release = os.release();
18+
const parts = release.split('.');
19+
if (parts.length >= 3 && parts[0] === '10' && parts[1] === '0') {
20+
const build = parseInt(parts[2], 10);
21+
return build < 22000;
22+
}
23+
return false;
24+
}
25+
26+
/**
27+
* Detects if the current terminal is a JetBrains-based IDE terminal.
28+
*/
29+
export function isJetBrainsTerminal(): boolean {
30+
return process.env['TERMINAL_EMULATOR'] === 'JetBrains-JediTerm';
31+
}
32+
33+
/**
34+
* Detects if the current terminal supports true color (24-bit).
35+
*/
36+
export function supportsTrueColor(): boolean {
37+
// Check COLORTERM environment variable
38+
if (
39+
process.env['COLORTERM'] === 'truecolor' ||
40+
process.env['COLORTERM'] === '24bit'
41+
) {
42+
return true;
43+
}
44+
45+
// Check if stdout supports 24-bit color depth
46+
if (process.stdout.getColorDepth && process.stdout.getColorDepth() >= 24) {
47+
return true;
48+
}
49+
50+
return false;
51+
}
52+
53+
/**
54+
* Returns a list of compatibility warnings based on the current environment.
55+
*/
56+
export function getCompatibilityWarnings(): string[] {
57+
const warnings: string[] = [];
58+
59+
if (isWindows10()) {
60+
warnings.push(
61+
'Warning: Windows 10 detected. Some UI features like smooth scrolling may be degraded. Windows 11 is recommended for the best experience.',
62+
);
63+
}
64+
65+
if (isJetBrainsTerminal()) {
66+
warnings.push(
67+
'Warning: JetBrains terminal detected. You may experience rendering or scrolling issues. Using an external terminal (e.g., Windows Terminal, iTerm2) is recommended.',
68+
);
69+
}
70+
71+
if (!supportsTrueColor()) {
72+
warnings.push(
73+
'Warning: True color (24-bit) support not detected. Using a terminal with true color enabled will result in a better visual experience.',
74+
);
75+
}
76+
77+
return warnings;
78+
}

0 commit comments

Comments
 (0)