From eb9ff72b5af1c32339527f74c73f464dbd701324 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Thu, 13 Nov 2025 09:45:03 -0800 Subject: [PATCH] Support incremental update experiment flag. (#12926) --- docs/get-started/configuration.md | 7 +++++ integration-tests/read_many_files.test.ts | 2 +- package-lock.json | 10 +++---- package.json | 4 +-- packages/cli/package.json | 2 +- packages/cli/src/config/settingsSchema.ts | 10 +++++++ packages/cli/src/gemini.test.tsx | 3 ++ packages/cli/src/gemini.tsx | 34 ++++++++--------------- schemas/settings.schema.json | 7 +++++ 9 files changed, 48 insertions(+), 31 deletions(-) diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 7a433caf8a..633c10b6c7 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -235,6 +235,13 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `true` - **Requires restart:** Yes +- **`ui.incrementalRendering`** (boolean): + - **Description:** Enable incremental rendering for the UI. This option will + reduce flickering but may cause rendering artifacts. Only supported when + useAlternateBuffer is enabled. + - **Default:** `true` + - **Requires restart:** Yes + - **`ui.customWittyPhrases`** (array): - **Description:** Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults. diff --git a/integration-tests/read_many_files.test.ts b/integration-tests/read_many_files.test.ts index 7a78589bdc..1d80092ce6 100644 --- a/integration-tests/read_many_files.test.ts +++ b/integration-tests/read_many_files.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect } from 'vitest'; import { TestRig, printDebugInfo, validateModelOutput } from './test-helper.js'; describe('read_many_files', () => { - it('should be able to read multiple files', async () => { + it.skip('should be able to read multiple files', async () => { const rig = new TestRig(); await rig.setup('should be able to read multiple files'); rig.createFile('file1.txt', 'file 1 content'); diff --git a/package-lock.json b/package-lock.json index 1c2710d526..ec434b94c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "packages/*" ], "dependencies": { - "ink": "npm:@jrichman/ink@6.4.2", + "ink": "npm:@jrichman/ink@6.4.3", "latest-version": "^9.0.0", "simple-git": "^3.28.0" }, @@ -9887,9 +9887,9 @@ }, "node_modules/ink": { "name": "@jrichman/ink", - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.2.tgz", - "integrity": "sha512-jfne1I/8+kVhzY/aoIWUKS0adPNRUhnN/wEsdBtSheyAp0b3c94zVsWWyDxnfXKL3RqOd40/H1FFaPLTUwjLXQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.3.tgz", + "integrity": "sha512-2qm05tjtdia+d1gD7LQjPJyCPJluKDuR5B+FI3ZZXshFoU1igZBFvXs2++x9OT6d9755q+gkRPOdtH8jzx5MiQ==", "license": "MIT", "peer": true, "dependencies": { @@ -17276,7 +17276,7 @@ "fzf": "^0.5.2", "glob": "^10.4.5", "highlight.js": "^11.11.1", - "ink": "npm:@jrichman/ink@6.4.2", + "ink": "npm:@jrichman/ink@6.4.3", "ink-gradient": "^3.0.0", "ink-spinner": "^5.0.0", "latest-version": "^9.0.0", diff --git a/package.json b/package.json index 25a4a94c99..3fb9b05724 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "pre-commit": "node scripts/pre-commit.js" }, "overrides": { - "ink": "npm:@jrichman/ink@6.4.2", + "ink": "npm:@jrichman/ink@6.4.3", "wrap-ansi": "9.0.2", "cliui": { "wrap-ansi": "7.0.0" @@ -119,7 +119,7 @@ "yargs": "^17.7.2" }, "dependencies": { - "ink": "npm:@jrichman/ink@6.4.2", + "ink": "npm:@jrichman/ink@6.4.3", "latest-version": "^9.0.0", "simple-git": "^3.28.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 54dc83d6b8..45d61285c9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,7 +42,7 @@ "fzf": "^0.5.2", "glob": "^10.4.5", "highlight.js": "^11.11.1", - "ink": "npm:@jrichman/ink@6.4.2", + "ink": "npm:@jrichman/ink@6.4.3", "ink-gradient": "^3.0.0", "ink-spinner": "^5.0.0", "latest-version": "^9.0.0", diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 5f070d1a26..fe5670fd16 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -502,6 +502,16 @@ const SETTINGS_SCHEMA = { 'Use an alternate screen buffer for the UI, preserving shell history.', showInDialog: true, }, + incrementalRendering: { + type: 'boolean', + label: 'Incremental Rendering', + category: 'UI', + requiresRestart: true, + default: true, + description: + 'Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled.', + showInDialog: true, + }, customWittyPhrases: { type: 'array', label: 'Custom Witty Phrases', diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index ecb117de4c..bc225c1ba1 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -474,6 +474,8 @@ describe('startInteractiveUI', () => { vi.mock('./ui/utils/kittyProtocolDetector.js', () => ({ detectAndEnableKittyProtocol: vi.fn(() => Promise.resolve(true)), + isKittyProtocolSupported: vi.fn(() => true), + isKittyProtocolEnabled: vi.fn(() => true), })); vi.mock('./ui/utils/updateCheck.js', () => ({ @@ -531,6 +533,7 @@ describe('startInteractiveUI', () => { expect(options).toEqual({ alternateBuffer: true, exitOnCtrlC: false, + incrementalRendering: true, isScreenReaderEnabled: false, onRender: expect.any(Function), }); diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index c9225bbf69..f7a2f48064 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -158,32 +158,19 @@ export async function startInteractiveUI( resumedSessionData: ResumedSessionData | undefined, initializationResult: InitializationResult, ) { - // When not in screen reader mode, disable line wrapping. - // We rely on Ink to manage all line wrapping by forcing all content to be - // narrower than the terminal width so there is no need for the terminal to - // also attempt line wrapping. - // Disabling line wrapping reduces Ink rendering artifacts particularly when - // the terminal is resized on terminals that full respect this escape code - // such as Ghostty. Some terminals such as Iterm2 only respect line wrapping - // when using the alternate buffer, which Gemini CLI does not use because we - // do not yet have support for scrolling in that mode. - if (!config.getScreenReader()) { - process.stdout.write('\x1b[?7l'); - } - - const useAlternateBuffer = isAlternateBufferEnabled(settings); + // Never enter Ink alternate buffer mode when screen reader mode is enabled + // as there is no benefit of alternate buffer mode when using a screen reader + // and the Ink alternate buffer mode requires line wrapping harmful to + // screen readers. + const useAlternateBuffer = + isAlternateBufferEnabled(settings) && !config.getScreenReader(); const mouseEventsEnabled = useAlternateBuffer; if (mouseEventsEnabled) { enableMouseEvents(); - } - - registerCleanup(() => { - // Re-enable line wrapping on exit. - process.stdout.write('\x1b[?7h'); - if (mouseEventsEnabled) { + registerCleanup(() => { disableMouseEvents(); - } - }); + }); + } const version = await getCliVersion(); setWindowTitle(basename(workspaceRoot), settings); @@ -239,6 +226,9 @@ export async function startInteractiveUI( } }, alternateBuffer: useAlternateBuffer, + incrementalRendering: + settings.merged.ui?.incrementalRendering !== false && + useAlternateBuffer, }, ); diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 66314d3704..81611447c5 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -282,6 +282,13 @@ "default": true, "type": "boolean" }, + "incrementalRendering": { + "title": "Incremental Rendering", + "description": "Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled.", + "markdownDescription": "Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled.\n\n- Category: `UI`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, "customWittyPhrases": { "title": "Custom Witty Phrases", "description": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.",