diff --git a/components/terminal/runtime/createXTermRuntime.ts b/components/terminal/runtime/createXTermRuntime.ts index c4e178ca..36aaa691 100644 --- a/components/terminal/runtime/createXTermRuntime.ts +++ b/components/terminal/runtime/createXTermRuntime.ts @@ -15,6 +15,7 @@ import { fontStore } from "../../../application/state/fontStore"; import { KeywordHighlighter } from "../keywordHighlight"; import { XTERM_PERFORMANCE_CONFIG, + resolveXTermScrollback, type XTermPlatform, resolveXTermPerformanceConfig, } from "../../../infrastructure/config/xtermPerformance"; @@ -270,11 +271,8 @@ export const createXTermRuntime = (ctx: CreateXTermRuntimeContext): XTermRuntime const cursorStyle = settings?.cursorShape ?? "block"; const cursorBlink = settings?.cursorBlink ?? true; - // xterm.js treats scrollback=0 as "no scrollback buffer", which breaks mouse - // wheel scrolling (events become arrow-key sequences). The UI uses 0 to mean - // "no limit", so map it to a large value instead. const rawScrollback = settings?.scrollback ?? 10000; - const scrollback = rawScrollback === 0 ? 999999 : rawScrollback; + const scrollback = resolveXTermScrollback(rawScrollback); const drawBoldTextInBrightColors = settings?.drawBoldInBrightColors ?? true; const fontWeight = resolveHostTerminalFontWeight(ctx.host, settings?.fontWeight ?? 400); const fontWeightBold = settings?.fontWeightBold ?? 700; diff --git a/components/terminal/useTerminalEffects.ts b/components/terminal/useTerminalEffects.ts index 1a8571c4..818f9d99 100644 --- a/components/terminal/useTerminalEffects.ts +++ b/components/terminal/useTerminalEffects.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps */ import { useRef } from 'react'; import { resolveFontWeightBold } from '../../lib/fontWeightAvailability'; +import { resolveXTermScrollback } from '../../infrastructure/config/xtermPerformance'; import { shouldInterceptMouseTrackingContextMenu } from './runtime/middleClickBehavior'; type TerminalEffectsContext = Record; @@ -465,7 +466,7 @@ export function useTerminalEffects(ctx: TerminalEffectsContext) { if (terminalSettings) { applyUserCursorPreference(termRef.current, terminalSettings); - termRef.current.options.scrollback = terminalSettings.scrollback === 0 ? 999999 : terminalSettings.scrollback; + termRef.current.options.scrollback = resolveXTermScrollback(terminalSettings.scrollback); termRef.current.options.fontWeight = effectiveFontWeight as | 100 | 200 diff --git a/infrastructure/config/xtermPerformance.test.ts b/infrastructure/config/xtermPerformance.test.ts new file mode 100644 index 00000000..f56b64cf --- /dev/null +++ b/infrastructure/config/xtermPerformance.test.ts @@ -0,0 +1,17 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { + XTERM_UNLIMITED_SCROLLBACK_CAP, + resolveXTermScrollback, +} from './xtermPerformance'; + +test('resolveXTermScrollback maps the unlimited sentinel to a 50000 row cap', () => { + assert.equal(XTERM_UNLIMITED_SCROLLBACK_CAP, 50000); + assert.equal(resolveXTermScrollback(0), 50000); +}); + +test('resolveXTermScrollback preserves explicit positive scrollback values', () => { + assert.equal(resolveXTermScrollback(10000), 10000); + assert.equal(resolveXTermScrollback(50000), 50000); +}); diff --git a/infrastructure/config/xtermPerformance.ts b/infrastructure/config/xtermPerformance.ts index 1086ab88..c6b652a7 100644 --- a/infrastructure/config/xtermPerformance.ts +++ b/infrastructure/config/xtermPerformance.ts @@ -8,6 +8,14 @@ * - Memory pressure handling */ +export const XTERM_UNLIMITED_SCROLLBACK_CAP = 50000; + +export function resolveXTermScrollback(scrollback: number): number { + // xterm.js treats 0 as "no scrollback". Keep the app's 0 sentinel useful + // without asking xterm to resize/reflow nearly one million buffer rows. + return scrollback === 0 ? XTERM_UNLIMITED_SCROLLBACK_CAP : scrollback; +} + export const XTERM_PERFORMANCE_CONFIG = { // Memory and Scrollback Settings scrollback: {