diff --git a/docs/cli/settings.md b/docs/cli/settings.md index ea7d5c9f8d..83b92fb3c8 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -57,8 +57,8 @@ they appear in the UI. | Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `true` | | Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` | | Show Model Info In Chat | `ui.showModelInfoInChat` | Show the model name in the chat for each model turn. | `false` | -| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` | | Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` | +| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` | | Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` | | Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` | | Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` | diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 9dc13a10d2..d7885df084 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -244,16 +244,16 @@ their corresponding top-level category object in your `settings.json` file. - **Description:** Show the model name in the chat for each model turn. - **Default:** `false` -- **`ui.useFullWidth`** (boolean): - - **Description:** Use the entire width of the terminal for output. - - **Default:** `true` - - **`ui.useAlternateBuffer`** (boolean): - **Description:** Use an alternate screen buffer for the UI, preserving shell history. - **Default:** `false` - **Requires restart:** Yes +- **`ui.useBackgroundColor`** (boolean): + - **Description:** Whether to use background colors in the UI. + - **Default:** `true` + - **`ui.incrementalRendering`** (boolean): - **Description:** Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when diff --git a/package-lock.json b/package-lock.json index f89bac2f1c..6da192364b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "packages/*" ], "dependencies": { - "ink": "npm:@jrichman/ink@6.4.7", + "ink": "npm:@jrichman/ink@6.4.8", "latest-version": "^9.0.0", "simple-git": "^3.28.0" }, @@ -2251,6 +2251,7 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -2431,6 +2432,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2464,6 +2466,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2832,6 +2835,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2865,6 +2869,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" @@ -2917,6 +2922,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", @@ -4122,6 +4128,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4399,6 +4406,7 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -5391,6 +5399,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8400,6 +8409,7 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8940,6 +8950,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -10537,10 +10548,11 @@ }, "node_modules/ink": { "name": "@jrichman/ink", - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.7.tgz", - "integrity": "sha512-QHyxhNF5VonF5cRmdAJD/UPucB9nRx3FozWMjQrDGfBxfAL9lpyu72/MlFPgloS1TMTGsOt7YN6dTPPA6mh0Aw==", + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.8.tgz", + "integrity": "sha512-v0thcXIKl9hqF/1w4HqA6MKxIcMoWSP3YtEZIAA+eeJngXpN5lGnMkb6rllB7FnOdwyEyYaFTcu1ZVr4/JZpWQ==", "license": "MIT", + "peer": true, "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.0.0", @@ -14299,6 +14311,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14309,6 +14322,7 @@ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -16545,6 +16559,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16768,7 +16783,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.20.3", @@ -16776,6 +16792,7 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -16948,6 +16965,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17155,6 +17173,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -17268,6 +17287,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17280,6 +17300,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -17984,6 +18005,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -18075,7 +18097,7 @@ "fzf": "^0.5.2", "glob": "^12.0.0", "highlight.js": "^11.11.1", - "ink": "npm:@jrichman/ink@6.4.7", + "ink": "npm:@jrichman/ink@6.4.8", "ink-gradient": "^3.0.0", "ink-spinner": "^5.0.0", "latest-version": "^9.0.0", @@ -18278,6 +18300,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/package.json b/package.json index 08c7a7ccd6..4a570180ff 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "pre-commit": "node scripts/pre-commit.js" }, "overrides": { - "ink": "npm:@jrichman/ink@6.4.7", + "ink": "npm:@jrichman/ink@6.4.8", "wrap-ansi": "9.0.2", "cliui": { "wrap-ansi": "7.0.0" @@ -124,7 +124,7 @@ "yargs": "^17.7.2" }, "dependencies": { - "ink": "npm:@jrichman/ink@6.4.7", + "ink": "npm:@jrichman/ink@6.4.8", "latest-version": "^9.0.0", "simple-git": "^3.28.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 9eccec9e67..e4159590b0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -46,7 +46,7 @@ "fzf": "^0.5.2", "glob": "^12.0.0", "highlight.js": "^11.11.1", - "ink": "npm:@jrichman/ink@6.4.7", + "ink": "npm:@jrichman/ink@6.4.8", "ink-gradient": "^3.0.0", "ink-spinner": "^5.0.0", "latest-version": "^9.0.0", diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 9b00f0ea33..1a622f7d0c 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -766,6 +766,7 @@ export async function loadCliConfig( folderTrust, interactive, trustedFolder, + useBackgroundColor: settings.ui?.useBackgroundColor, useRipgrep: settings.tools?.useRipgrep, enableInteractiveShell: settings.tools?.shell?.enableInteractiveShell, shellToolInactivityTimeout: settings.tools?.shell?.inactivityTimeout, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index fbd72cec36..6a881fdeff 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -526,15 +526,6 @@ const SETTINGS_SCHEMA = { description: 'Show the model name in the chat for each model turn.', showInDialog: true, }, - useFullWidth: { - type: 'boolean', - label: 'Use Full Width', - category: 'UI', - requiresRestart: false, - default: true, - description: 'Use the entire width of the terminal for output.', - showInDialog: true, - }, useAlternateBuffer: { type: 'boolean', label: 'Use Alternate Screen Buffer', @@ -545,6 +536,15 @@ const SETTINGS_SCHEMA = { 'Use an alternate screen buffer for the UI, preserving shell history.', showInDialog: true, }, + useBackgroundColor: { + type: 'boolean', + label: 'Use Background Color', + category: 'UI', + requiresRestart: false, + default: true, + description: 'Whether to use background colors in the UI.', + showInDialog: true, + }, incrementalRendering: { type: 'boolean', label: 'Incremental Rendering', diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx index 717aa668d1..85c3e0f305 100644 --- a/packages/cli/src/test-utils/render.tsx +++ b/packages/cli/src/test-utils/render.tsx @@ -16,7 +16,6 @@ import { SettingsContext } from '../ui/contexts/SettingsContext.js'; import { ShellFocusContext } from '../ui/contexts/ShellFocusContext.js'; import { UIStateContext, type UIState } from '../ui/contexts/UIStateContext.js'; import { ConfigContext } from '../ui/contexts/ConfigContext.js'; -import { calculateMainAreaWidth } from '../ui/utils/ui-sizing.js'; import { VimModeProvider } from '../ui/contexts/VimModeContext.js'; import { MouseProvider } from '../ui/contexts/MouseContext.js'; import { ScrollProvider } from '../ui/contexts/ScrollProvider.js'; @@ -38,6 +37,11 @@ vi.mock('../utils/persistentState.js', () => ({ persistentState: persistentStateMock, })); +vi.mock('../ui/utils/terminalUtils.js', () => ({ + isLowColorDepth: vi.fn(() => false), + getColorDepth: vi.fn(() => 24), +})); + // Wrapper around ink-testing-library's render that ensures act() is called export const render = ( tree: React.ReactElement, @@ -147,7 +151,6 @@ export const createMockSettings = ( const baseMockUiState = { renderMarkdown: true, streamingState: StreamingState.Idle, - mainAreaWidth: 100, terminalWidth: 120, terminalHeight: 40, currentModel: 'gemini-pro', @@ -269,7 +272,7 @@ export const renderWithProviders = ( }); } - const mainAreaWidth = calculateMainAreaWidth(terminalWidth, finalSettings); + const mainAreaWidth = terminalWidth; const finalUiState = { ...baseState, diff --git a/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx b/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx index ea67bdcf6c..ddcf301268 100644 --- a/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx +++ b/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx @@ -34,7 +34,7 @@ vi.mock('../components/shared/text-buffer.js', () => ({ vi.mock('../contexts/UIStateContext.js', () => ({ useUIState: vi.fn(() => ({ - mainAreaWidth: 80, + terminalWidth: 80, })), })); diff --git a/packages/cli/src/ui/auth/ApiAuthDialog.tsx b/packages/cli/src/ui/auth/ApiAuthDialog.tsx index 6345599634..f76fb90edb 100644 --- a/packages/cli/src/ui/auth/ApiAuthDialog.tsx +++ b/packages/cli/src/ui/auth/ApiAuthDialog.tsx @@ -28,8 +28,8 @@ export function ApiAuthDialog({ error, defaultValue = '', }: ApiAuthDialogProps): React.JSX.Element { - const { mainAreaWidth } = useUIState(); - const viewportWidth = mainAreaWidth - 8; + const { terminalWidth } = useUIState(); + const viewportWidth = terminalWidth - 8; const pendingPromise = useRef<{ cancel: () => void } | null>(null); diff --git a/packages/cli/src/ui/components/AppHeader.tsx b/packages/cli/src/ui/components/AppHeader.tsx index 5efe1ed81f..77042c6e3a 100644 --- a/packages/cli/src/ui/components/AppHeader.tsx +++ b/packages/cli/src/ui/components/AppHeader.tsx @@ -21,7 +21,7 @@ interface AppHeaderProps { export const AppHeader = ({ version }: AppHeaderProps) => { const settings = useSettings(); const config = useConfig(); - const { nightly, mainAreaWidth, bannerData, bannerVisible } = useUIState(); + const { nightly, terminalWidth, bannerData, bannerVisible } = useUIState(); const { bannerText } = useBanner(bannerData, config); const { showTips } = useTips(); @@ -33,7 +33,7 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
{bannerVisible && bannerText && ( diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index de3ecebd19..8f6c807de7 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -50,7 +50,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { return ( @@ -113,7 +113,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { maxHeight={ uiState.constrainHeight ? debugConsoleMaxHeight : undefined } - width={uiState.mainAreaWidth} + width={uiState.terminalWidth} hasFocus={uiState.showErrorDetails} /> diff --git a/packages/cli/src/ui/components/DialogManager.test.tsx b/packages/cli/src/ui/components/DialogManager.test.tsx index 5ac33794cc..85cc050b21 100644 --- a/packages/cli/src/ui/components/DialogManager.test.tsx +++ b/packages/cli/src/ui/components/DialogManager.test.tsx @@ -72,7 +72,7 @@ describe('DialogManager', () => { constrainHeight: false, terminalHeight: 24, staticExtraHeight: 0, - mainAreaWidth: 80, + terminalWidth: 80, confirmUpdateExtensionRequests: [], showIdeRestartPrompt: false, proQuotaRequest: null, diff --git a/packages/cli/src/ui/components/DialogManager.tsx b/packages/cli/src/ui/components/DialogManager.tsx index 305f2333f1..b8bf51a81e 100644 --- a/packages/cli/src/ui/components/DialogManager.tsx +++ b/packages/cli/src/ui/components/DialogManager.tsx @@ -50,8 +50,12 @@ export const DialogManager = ({ const uiState = useUIState(); const uiActions = useUIActions(); - const { constrainHeight, terminalHeight, staticExtraHeight, mainAreaWidth } = - uiState; + const { + constrainHeight, + terminalHeight, + staticExtraHeight, + terminalWidth: uiTerminalWidth, + } = uiState; if (uiState.adminSettingsChanged) { return ; @@ -147,7 +151,7 @@ export const DialogManager = ({ availableTerminalHeight={ constrainHeight ? terminalHeight - staticExtraHeight : undefined } - terminalWidth={mainAreaWidth} + terminalWidth={uiTerminalWidth} /> ); diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index 44bab56f45..c488568e7d 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -42,7 +42,7 @@ export const Footer: React.FC = () => { promptTokenCount, nightly, isTrustedFolder, - mainAreaWidth, + terminalWidth, } = { model: uiState.currentModel, targetDir: config.getTargetDir(), @@ -55,7 +55,7 @@ export const Footer: React.FC = () => { promptTokenCount: uiState.sessionStats.lastPromptTokenCount, nightly: uiState.nightly, isTrustedFolder: uiState.isTrustedFolder, - mainAreaWidth: uiState.mainAreaWidth, + terminalWidth: uiState.terminalWidth, }; const showMemoryUsage = @@ -65,7 +65,7 @@ export const Footer: React.FC = () => { const hideModelInfo = settings.merged.ui.footer.hideModelInfo; const hideContextPercentage = settings.merged.ui.footer.hideContextPercentage; - const pathLength = Math.max(20, Math.floor(mainAreaWidth * 0.25)); + const pathLength = Math.max(20, Math.floor(terminalWidth * 0.25)); const displayPath = shortenPath(tildeifyPath(targetDir), pathLength); const justifyContent = hideCWD && hideModelInfo ? 'center' : 'space-between'; @@ -76,7 +76,7 @@ export const Footer: React.FC = () => { return ( { ) : ( no sandbox - {mainAreaWidth >= 100 && ( + {terminalWidth >= 100 && ( (see /docs) )} @@ -155,7 +155,7 @@ export const Footer: React.FC = () => { )} diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx index 7a72dc6120..3814e603d8 100644 --- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx +++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx @@ -67,7 +67,7 @@ export const HistoryItemDisplay: React.FC = ({ )} {itemForDisplay.type === 'user_shell' && ( - + )} {itemForDisplay.type === 'gemini' && ( ({ + isLowColorDepth: vi.fn(() => false), +})); const mockSlashCommands: SlashCommand[] = [ { @@ -260,6 +265,8 @@ describe('InputPrompt', () => { getProjectRoot: () => path.join('test', 'project'), getTargetDir: () => path.join('test', 'project', 'src'), getVimMode: () => false, + getUseBackgroundColor: () => true, + getTerminalBackground: () => undefined, getWorkspaceContext: () => ({ getDirectories: () => ['/test/project/src'], }), @@ -1320,6 +1327,168 @@ describe('InputPrompt', () => { unmount(); }); + describe('Background Color Styles', () => { + beforeEach(() => { + vi.mocked(isLowColorDepth).mockReturnValue(false); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should render with background color by default', async () => { + const { stdout, unmount } = renderWithProviders( + , + ); + + await waitFor(() => { + const frame = stdout.lastFrame(); + expect(frame).toContain('▀'); + expect(frame).toContain('▄'); + }); + unmount(); + }); + + it.each([ + { color: 'black', name: 'black' }, + { color: '#000000', name: '#000000' }, + { color: '#000', name: '#000' }, + { color: undefined, name: 'default (black)' }, + { color: 'white', name: 'white' }, + { color: '#ffffff', name: '#ffffff' }, + { color: '#fff', name: '#fff' }, + ])( + 'should render with safe grey background but NO side borders in 8-bit mode when background is $name', + async ({ color }) => { + vi.mocked(isLowColorDepth).mockReturnValue(true); + + const { stdout, unmount } = renderWithProviders( + , + { + uiState: { + terminalBackgroundColor: color, + } as Partial, + }, + ); + + const isWhite = + color === 'white' || color === '#ffffff' || color === '#fff'; + const expectedBgColor = isWhite ? '#eeeeee' : '#1c1c1c'; + + await waitFor(() => { + const frame = stdout.lastFrame(); + + // Use chalk to get the expected background color escape sequence + const bgCheck = chalk.bgHex(expectedBgColor)(' '); + const bgCode = bgCheck.substring(0, bgCheck.indexOf(' ')); + + // Background color code should be present + expect(frame).toContain(bgCode); + // Background characters should be rendered + expect(frame).toContain('▀'); + expect(frame).toContain('▄'); + // Side borders should STILL be removed + expect(frame).not.toContain('│'); + }); + + unmount(); + }, + ); + + it('should NOT render with background color but SHOULD render horizontal lines when color depth is < 24 and background is NOT black', async () => { + vi.mocked(isLowColorDepth).mockReturnValue(true); + + const { stdout, unmount } = renderWithProviders( + , + { + uiState: { + terminalBackgroundColor: '#333333', + } as Partial, + }, + ); + + await waitFor(() => { + const frame = stdout.lastFrame(); + expect(frame).not.toContain('▀'); + expect(frame).not.toContain('▄'); + // It SHOULD have horizontal fallback lines + expect(frame).toContain('─'); + // It SHOULD NOT have vertical side borders (standard Box borders have │) + expect(frame).not.toContain('│'); + }); + unmount(); + }); + it('should handle 4-bit color mode (16 colors) as low color depth', async () => { + vi.mocked(isLowColorDepth).mockReturnValue(true); + + const { stdout, unmount } = renderWithProviders( + , + ); + + await waitFor(() => { + const frame = stdout.lastFrame(); + + expect(frame).toContain('▀'); + + expect(frame).not.toContain('│'); + }); + + unmount(); + }); + + it('should render horizontal lines (but NO background) in 8-bit mode when background is blue', async () => { + vi.mocked(isLowColorDepth).mockReturnValue(true); + + const { stdout, unmount } = renderWithProviders( + , + + { + uiState: { + terminalBackgroundColor: 'blue', + } as Partial, + }, + ); + + await waitFor(() => { + const frame = stdout.lastFrame(); + + // Should NOT have background characters + + expect(frame).not.toContain('▀'); + + expect(frame).not.toContain('▄'); + + // Should HAVE horizontal lines from the fallback Box borders + + // Box style "round" uses these for top/bottom + + expect(frame).toContain('─'); + + // Should NOT have vertical side borders + + expect(frame).not.toContain('│'); + }); + + unmount(); + }); + + it('should render with plain borders when useBackgroundColor is false', async () => { + props.config.getUseBackgroundColor = () => false; + const { stdout, unmount } = renderWithProviders( + , + ); + + await waitFor(() => { + const frame = stdout.lastFrame(); + expect(frame).not.toContain('▀'); + expect(frame).not.toContain('▄'); + // Check for Box borders (round style uses unicode box chars) + expect(frame).toMatch(/[─│┐└┘┌]/); + }); + unmount(); + }); + }); + describe('cursor-based completion trigger', () => { it.each([ { @@ -1564,11 +1733,11 @@ describe('InputPrompt', () => { mockBuffer.lines = [text]; mockBuffer.viewportVisualLines = [text]; mockBuffer.visualCursor = visualCursor as [number, number]; + props.config.getUseBackgroundColor = () => false; const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => { const frame = stdout.lastFrame(); expect(frame).toContain(expected); @@ -1621,11 +1790,11 @@ describe('InputPrompt', () => { mockBuffer.visualToLogicalMap = visualToLogicalMap as Array< [number, number] >; + props.config.getUseBackgroundColor = () => false; const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => { const frame = stdout.lastFrame(); expect(frame).toContain(expected); @@ -1645,11 +1814,11 @@ describe('InputPrompt', () => { [1, 0], [2, 0], ]; + props.config.getUseBackgroundColor = () => false; const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => { const frame = stdout.lastFrame(); const lines = frame!.split('\n'); @@ -1673,15 +1842,15 @@ describe('InputPrompt', () => { mockBuffer.visualCursor = [2, 5]; // cursor at the end of "world" // Provide a visual-to-logical mapping for each visual line mockBuffer.visualToLogicalMap = [ - [0, 0], // 'hello' starts at col 0 of logical line 0 - [1, 0], // '' (blank) is logical line 1, col 0 - [2, 0], // 'world' is logical line 2, col 0 + [0, 0], + [1, 0], + [2, 0], ]; + props.config.getUseBackgroundColor = () => false; const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => { const frame = stdout.lastFrame(); // Check that all lines, including the empty one, are rendered. @@ -2505,20 +2674,23 @@ describe('InputPrompt', () => { stdin.write('\x12'); }); await waitFor(() => { - expect(stdout.lastFrame()).toMatchSnapshot( - 'command-search-render-collapsed-match', - ); + expect(stdout.lastFrame()).toContain('(r:)'); }); + expect(stdout.lastFrame()).toMatchSnapshot( + 'command-search-render-collapsed-match', + ); await act(async () => { stdin.write('\u001B[C'); }); await waitFor(() => { - expect(stdout.lastFrame()).toMatchSnapshot( - 'command-search-render-expanded-match', - ); + // Just wait for any update to ensure it is stable. + // We could also wait for specific text if we knew it. + expect(stdout.lastFrame()).toContain('(r:)'); }); - + expect(stdout.lastFrame()).toMatchSnapshot( + 'command-search-render-expanded-match', + ); unmount(); }); @@ -2637,28 +2809,28 @@ describe('InputPrompt', () => { name: 'first line, first char', relX: 0, relY: 0, - mouseCol: 5, + mouseCol: 4, mouseRow: 2, }, { name: 'first line, middle char', relX: 6, relY: 0, - mouseCol: 11, + mouseCol: 10, mouseRow: 2, }, { name: 'second line, first char', relX: 0, relY: 1, - mouseCol: 5, + mouseCol: 4, mouseRow: 3, }, { name: 'second line, end char', relX: 5, relY: 1, - mouseCol: 10, + mouseCol: 9, mouseRow: 3, }, ])( @@ -2685,7 +2857,7 @@ describe('InputPrompt', () => { }); // Simulate left mouse press at calculated coordinates. - // Assumes inner box is at x=4, y=1 based on border(1)+padding(1)+prompt(2) and border-top(1). + // Without left border: inner box is at x=3, y=1 based on padding(1)+prompt(2) and border-top(1). await act(async () => { stdin.write(`\x1b[<0;${mouseCol};${mouseRow}M`); }); @@ -2727,6 +2899,37 @@ describe('InputPrompt', () => { unmount(); }); + + it('should move cursor on mouse click with plain borders', async () => { + props.config.getUseBackgroundColor = () => false; + props.buffer.text = 'hello world'; + props.buffer.lines = ['hello world']; + props.buffer.viewportVisualLines = ['hello world']; + props.buffer.visualToLogicalMap = [[0, 0]]; + props.buffer.visualCursor = [0, 11]; + props.buffer.visualScrollRow = 0; + + const { stdin, stdout, unmount } = renderWithProviders( + , + { mouseEventsEnabled: true, uiActions }, + ); + + // Wait for initial render + await waitFor(() => { + expect(stdout.lastFrame()).toContain('hello world'); + }); + + // With plain borders: 1(border) + 1(padding) + 2(prompt) = 4 offset (x=4, col=5) + await act(async () => { + stdin.write(`\x1b[<0;5;2M`); // Click at col 5, row 2 + }); + + await waitFor(() => { + expect(props.buffer.moveToVisualPosition).toHaveBeenCalledWith(0, 0); + }); + + unmount(); + }); }); describe('queued message editing', () => { @@ -2889,7 +3092,8 @@ describe('InputPrompt', () => { const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot()); + await waitFor(() => expect(stdout.lastFrame()).toContain('!')); + expect(stdout.lastFrame()).toMatchSnapshot(); unmount(); }); @@ -2898,7 +3102,8 @@ describe('InputPrompt', () => { const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot()); + await waitFor(() => expect(stdout.lastFrame()).toContain('>')); + expect(stdout.lastFrame()).toMatchSnapshot(); unmount(); }); @@ -2907,10 +3112,10 @@ describe('InputPrompt', () => { const { stdout, unmount } = renderWithProviders( , ); - await waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot()); + await waitFor(() => expect(stdout.lastFrame()).toContain('*')); + expect(stdout.lastFrame()).toMatchSnapshot(); unmount(); }); - it('should not show inverted cursor when shell is focused', async () => { props.isEmbeddedShellFocused = true; props.focus = false; @@ -2919,8 +3124,8 @@ describe('InputPrompt', () => { ); await waitFor(() => { expect(stdout.lastFrame()).not.toContain(`{chalk.inverse(' ')}`); - expect(stdout.lastFrame()).toMatchSnapshot(); }); + expect(stdout.lastFrame()).toMatchSnapshot(); unmount(); }); }); @@ -3022,8 +3227,9 @@ describe('InputPrompt', () => { , ); await waitFor(() => { - expect(stdout.lastFrame()).toMatchSnapshot(); + expect(stdout.lastFrame()).toContain('[Image'); }); + expect(stdout.lastFrame()).toMatchSnapshot(); unmount(); }); @@ -3040,8 +3246,9 @@ describe('InputPrompt', () => { , ); await waitFor(() => { - expect(stdout.lastFrame()).toMatchSnapshot(); + expect(stdout.lastFrame()).toContain('@/path/to/screenshots'); }); + expect(stdout.lastFrame()).toMatchSnapshot(); unmount(); }); }); diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index c1c3644f20..e0199b8630 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -6,11 +6,12 @@ import type React from 'react'; import clipboardy from 'clipboardy'; -import { useCallback, useEffect, useState, useRef } from 'react'; +import { useCallback, useEffect, useState, useRef, useMemo } from 'react'; import { Box, Text, useStdout, type DOMElement } from 'ink'; import { SuggestionsDisplay, MAX_WIDTH } from './SuggestionsDisplay.js'; import { theme } from '../semantic-colors.js'; import { useInputHistory } from '../hooks/useInputHistory.js'; +import { HalfLinePaddedBox } from './shared/HalfLinePaddedBox.js'; import type { TextBuffer } from './shared/text-buffer.js'; import { logicalPosToOffset, @@ -47,6 +48,9 @@ import { } from '../utils/commandUtils.js'; import * as path from 'node:path'; import { SCREEN_READER_USER_PREFIX } from '../textConstants.js'; +import { DEFAULT_BACKGROUND_OPACITY } from '../constants.js'; +import { getSafeLowColorBackground } from '../themes/color-utils.js'; +import { isLowColorDepth } from '../utils/terminalUtils.js'; import { useShellFocusState } from '../contexts/ShellFocusContext.js'; import { useUIState } from '../contexts/UIStateContext.js'; import { useSettings } from '../contexts/SettingsContext.js'; @@ -141,7 +145,8 @@ export const InputPrompt: React.FC = ({ const kittyProtocol = useKittyKeyboardProtocol(); const isShellFocused = useShellFocusState(); const { setEmbeddedShellFocused } = useUIActions(); - const { mainAreaWidth, activePtyId, history } = useUIState(); + const { terminalWidth, activePtyId, history, terminalBackgroundColor } = + useUIState(); const [justNavigatedHistory, setJustNavigatedHistory] = useState(false); const escPressCount = useRef(0); const [showEscapePrompt, setShowEscapePrompt] = useState(false); @@ -321,6 +326,7 @@ export const InputPrompt: React.FC = ({ const allMessages = popAllMessages(); if (allMessages) { buffer.setText(allMessages); + return true; } else { // No queued messages, proceed with input history inputHistory.navigateUp(); @@ -1033,6 +1039,23 @@ export const InputPrompt: React.FC = ({ const activeCompletion = getActiveCompletion(); const shouldShowSuggestions = activeCompletion.showSuggestions; + const useBackgroundColor = config.getUseBackgroundColor(); + const isLowColor = isLowColorDepth(); + const terminalBg = terminalBackgroundColor || 'black'; + + // We should fallback to lines if the background color is disabled OR if it is + // enabled but we are in a low color depth terminal where we don't have a safe + // background color to use. + const useLineFallback = useMemo(() => { + if (!useBackgroundColor) { + return true; + } + if (isLowColor) { + return !getSafeLowColorBackground(terminalBg); + } + return false; + }, [useBackgroundColor, isLowColor, terminalBg]); + useEffect(() => { if (onSuggestionsVisibilityChange) { onSuggestionsVisibilityChange(shouldShowSuggestions); @@ -1085,198 +1108,241 @@ export const InputPrompt: React.FC = ({ ) : null; + const borderColor = + isShellFocused && !isEmbeddedShellFocused + ? (statusColor ?? theme.border.focused) + : theme.border.default; + return ( <> {suggestionsPosition === 'above' && suggestionsNode} - + ) : null} + - - {shellModeActive ? ( - reverseSearchActive ? ( - - (r:){' '} - + + {shellModeActive ? ( + reverseSearchActive ? ( + + (r:){' '} + + ) : ( + '!' + ) + ) : commandSearchActive ? ( + (r:) + ) : showYoloStyling ? ( + '*' ) : ( - '!' - ) - ) : commandSearchActive ? ( - (r:) - ) : showYoloStyling ? ( - '*' - ) : ( - '>' - )}{' '} - - - {buffer.text.length === 0 && placeholder ? ( - showCursor ? ( - - {chalk.inverse(placeholder.slice(0, 1))} - {placeholder.slice(1)} - + '>' + )}{' '} + + + {buffer.text.length === 0 && placeholder ? ( + showCursor ? ( + + {chalk.inverse(placeholder.slice(0, 1))} + + {placeholder.slice(1)} + + + ) : ( + {placeholder} + ) ) : ( - {placeholder} - ) - ) : ( - linesToRender - .map((lineText, visualIdxInRenderedSet) => { - const absoluteVisualIdx = - scrollVisualRow + visualIdxInRenderedSet; - const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx]; - const cursorVisualRow = - cursorVisualRowAbsolute - scrollVisualRow; - const isOnCursorLine = - focus && visualIdxInRenderedSet === cursorVisualRow; + linesToRender + .map((lineText: string, visualIdxInRenderedSet: number) => { + const absoluteVisualIdx = + scrollVisualRow + visualIdxInRenderedSet; + const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx]; + const cursorVisualRow = + cursorVisualRowAbsolute - scrollVisualRow; + const isOnCursorLine = + focus && visualIdxInRenderedSet === cursorVisualRow; - const renderedLine: React.ReactNode[] = []; + const renderedLine: React.ReactNode[] = []; - const [logicalLineIdx] = mapEntry; - const logicalLine = buffer.lines[logicalLineIdx] || ''; - const transformations = - buffer.transformationsByLine[logicalLineIdx] ?? []; - const tokens = parseInputForHighlighting( - logicalLine, - logicalLineIdx, - transformations, - ...(focus && buffer.cursor[0] === logicalLineIdx - ? [buffer.cursor[1]] - : []), - ); - const startColInTransformed = - buffer.visualToTransformedMap[absoluteVisualIdx] ?? 0; - const visualStartCol = startColInTransformed; - const visualEndCol = visualStartCol + cpLen(lineText); - const segments = parseSegmentsFromTokens( - tokens, - visualStartCol, - visualEndCol, - ); - let charCount = 0; - segments.forEach((seg, segIdx) => { - const segLen = cpLen(seg.text); - let display = seg.text; + const [logicalLineIdx] = mapEntry; + const logicalLine = buffer.lines[logicalLineIdx] || ''; + const transformations = + buffer.transformationsByLine[logicalLineIdx] ?? []; + const tokens = parseInputForHighlighting( + logicalLine, + logicalLineIdx, + transformations, + ...(focus && buffer.cursor[0] === logicalLineIdx + ? [buffer.cursor[1]] + : []), + ); + const startColInTransformed = + buffer.visualToTransformedMap[absoluteVisualIdx] ?? 0; + const visualStartCol = startColInTransformed; + const visualEndCol = visualStartCol + cpLen(lineText); + const segments = parseSegmentsFromTokens( + tokens, + visualStartCol, + visualEndCol, + ); + let charCount = 0; + segments.forEach((seg, segIdx) => { + const segLen = cpLen(seg.text); + let display = seg.text; - if (isOnCursorLine) { - const relativeVisualColForHighlight = - cursorVisualColAbsolute; - const segStart = charCount; - const segEnd = segStart + segLen; - if ( - relativeVisualColForHighlight >= segStart && - relativeVisualColForHighlight < segEnd - ) { - const charToHighlight = cpSlice( - display, - relativeVisualColForHighlight - segStart, - relativeVisualColForHighlight - segStart + 1, - ); - const highlighted = showCursor - ? chalk.inverse(charToHighlight) - : charToHighlight; - display = - cpSlice( + if (isOnCursorLine) { + const relativeVisualColForHighlight = + cursorVisualColAbsolute; + const segStart = charCount; + const segEnd = segStart + segLen; + if ( + relativeVisualColForHighlight >= segStart && + relativeVisualColForHighlight < segEnd + ) { + const charToHighlight = cpSlice( display, - 0, relativeVisualColForHighlight - segStart, - ) + - highlighted + - cpSlice( - display, relativeVisualColForHighlight - segStart + 1, ); + const highlighted = showCursor + ? chalk.inverse(charToHighlight) + : charToHighlight; + display = + cpSlice( + display, + 0, + relativeVisualColForHighlight - segStart, + ) + + highlighted + + cpSlice( + display, + relativeVisualColForHighlight - segStart + 1, + ); + } + charCount = segEnd; + } else { + // Advance the running counter even when not on cursor line + charCount += segLen; } - charCount = segEnd; - } else { - // Advance the running counter even when not on cursor line - charCount += segLen; - } - const color = - seg.type === 'command' || - seg.type === 'file' || - seg.type === 'paste' - ? theme.text.accent - : theme.text.primary; + const color = + seg.type === 'command' || + seg.type === 'file' || + seg.type === 'paste' + ? theme.text.accent + : theme.text.primary; - renderedLine.push( - - {display} - , - ); - }); - - const currentLineGhost = isOnCursorLine ? inlineGhost : ''; - if ( - isOnCursorLine && - cursorVisualColAbsolute === cpLen(lineText) - ) { - if (!currentLineGhost) { renderedLine.push( - - {showCursor ? chalk.inverse(' ') : ' '} + + {display} , ); + }); + + const currentLineGhost = isOnCursorLine ? inlineGhost : ''; + if ( + isOnCursorLine && + cursorVisualColAbsolute === cpLen(lineText) + ) { + if (!currentLineGhost) { + renderedLine.push( + + {showCursor ? chalk.inverse(' ') : ' '} + , + ); + } } - } - const showCursorBeforeGhost = - focus && - isOnCursorLine && - cursorVisualColAbsolute === cpLen(lineText) && - currentLineGhost; + const showCursorBeforeGhost = + focus && + isOnCursorLine && + cursorVisualColAbsolute === cpLen(lineText) && + currentLineGhost; - return ( - - - {renderedLine} - {showCursorBeforeGhost && - (showCursor ? chalk.inverse(' ') : ' ')} - {currentLineGhost && ( - - {currentLineGhost} - - )} - - - ); - }) - .concat( - additionalLines.map((ghostLine, index) => { - const padding = Math.max( - 0, - inputWidth - stringWidth(ghostLine), - ); return ( - - {ghostLine} - {' '.repeat(padding)} - + + + {renderedLine} + {showCursorBeforeGhost && + (showCursor ? chalk.inverse(' ') : ' ')} + {currentLineGhost && ( + + {currentLineGhost} + + )} + + ); - }), - ) - )} + }) + .concat( + additionalLines.map((ghostLine, index) => { + const padding = Math.max( + 0, + inputWidth - stringWidth(ghostLine), + ); + return ( + + {ghostLine} + {' '.repeat(padding)} + + ); + }), + ) + )} + - + + {useLineFallback ? ( + + ) : null} {suggestionsPosition === 'below' && suggestionsNode} ); diff --git a/packages/cli/src/ui/components/MainContent.tsx b/packages/cli/src/ui/components/MainContent.tsx index 7f3982eec0..5239ec040a 100644 --- a/packages/cli/src/ui/components/MainContent.tsx +++ b/packages/cli/src/ui/components/MainContent.tsx @@ -129,6 +129,7 @@ export const MainContent = () => { return ( 100} diff --git a/packages/cli/src/ui/components/SettingsDialog.test.tsx b/packages/cli/src/ui/components/SettingsDialog.test.tsx index c58910628f..9bc8f05298 100644 --- a/packages/cli/src/ui/components/SettingsDialog.test.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.test.tsx @@ -43,7 +43,7 @@ const mockSetVimMode = vi.fn(); vi.mock('../contexts/UIStateContext.js', () => ({ useUIState: () => ({ - mainAreaWidth: 100, // Fixed width for consistent snapshots + terminalWidth: 100, // Fixed width for consistent snapshots }), })); diff --git a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap index fa02687659..c112a83117 100644 --- a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap @@ -39,14 +39,14 @@ Tips for getting started: 2. Be specific for the best results. 3. Create GEMINI.md files to customize your interactions with Gemini. 4. /help for more information. -╭─────────────────────────────────────────────────────────────────────────────╮ -│ ✓ tool1 Description for tool 1 │ -│ │ -╰─────────────────────────────────────────────────────────────────────────────╯ -╭─────────────────────────────────────────────────────────────────────────────╮ -│ ✓ tool2 Description for tool 2 │ -│ │ -╰─────────────────────────────────────────────────────────────────────────────╯" +╭──────────────────────────────────────────────────────────────────────────────╮ +│ ✓ tool1 Description for tool 1 │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────╮ +│ ✓ tool2 Description for tool 2 │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────╯" `; exports[`AlternateBufferQuittingDisplay > renders with empty history and no pending items > empty 1`] = ` @@ -83,14 +83,14 @@ Tips for getting started: 2. Be specific for the best results. 3. Create GEMINI.md files to customize your interactions with Gemini. 4. /help for more information. -╭─────────────────────────────────────────────────────────────────────────────╮ -│ ✓ tool1 Description for tool 1 │ -│ │ -╰─────────────────────────────────────────────────────────────────────────────╯ -╭─────────────────────────────────────────────────────────────────────────────╮ -│ ✓ tool2 Description for tool 2 │ -│ │ -╰─────────────────────────────────────────────────────────────────────────────╯" +╭──────────────────────────────────────────────────────────────────────────────╮ +│ ✓ tool1 Description for tool 1 │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────╮ +│ ✓ tool2 Description for tool 2 │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────╯" `; exports[`AlternateBufferQuittingDisplay > renders with pending items but no history > with_pending_no_history 1`] = ` @@ -127,8 +127,8 @@ Tips for getting started: 2. Be specific for the best results. 3. Create GEMINI.md files to customize your interactions with Gemini. 4. /help for more information. - -> Hello Gemini - +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ + > Hello Gemini +▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ✦ Hello User!" `; diff --git a/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap index 6da8b523f2..bb28344103 100644 --- a/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap @@ -65,9 +65,9 @@ exports[` > should render the banner when previewFeatures is disabl ███░ ░░█████████ ░░░ ░░░░░░░░░ -╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ This is the default banner │ -╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ This is the default banner │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ Tips for getting started: 1. Ask questions, edit files, or run commands. 2. Be specific for the best results. @@ -86,9 +86,9 @@ exports[` > should render the banner with default text 1`] = ` ███░ ░░█████████ ░░░ ░░░░░░░░░ -╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ This is the default banner │ -╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ This is the default banner │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ Tips for getting started: 1. Ask questions, edit files, or run commands. 2. Be specific for the best results. @@ -107,9 +107,9 @@ exports[` > should render the banner with warning text 1`] = ` ███░ ░░█████████ ░░░ ░░░░░░░░░ -╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ -│ There are capacity issues │ -╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ There are capacity issues │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ Tips for getting started: 1. Ask questions, edit files, or run commands. 2. Be specific for the best results. diff --git a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap index 495446eb0a..4c870387ae 100644 --- a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap @@ -1,11 +1,11 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`