perf(terminal): stop painting busy TUI frames twice on non-Windows DOM terminals (#39462)

Live foreground output with background colors or in-place rewrites
requested a synchronous xterm row repair after every write, on top of
the dirty-row frame xterm had already queued while parsing. Busy
OpenCode frames all carry background SGR, so DOM panes painted each
frame twice.

The synchronous repair exists for Windows ConPTY DOM rendering. Keep it
there and let other platforms merge the repair into xterm's queued
frame. Replay and snapshot refresh policy is unchanged.

Drop tests that only asserted the private synchronous refresh call.

Fixes #22458

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Neil
2026-11-07 03:16:44 -08:00
committed by GitHub
co-authored by Copilot
parent a93741a901
commit 8ceea00464
5 changed files with 9 additions and 528 deletions
+2 -9
View File
@@ -13752,14 +13752,6 @@
"file": "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts",
"assertions": ["keeps hidden synchronized output off the global atlas recovery path"]
},
{
"file": "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-input-and-repaint.test.ts",
"assertions": [
"refreshes renderer-risk output without clearing the shared glyph atlas",
"refreshes alternate-screen redraws without clearing the shared glyph atlas",
"avoids atlas clears when one chunk enters and exits alternate screen"
]
},
{
"file": "tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts",
"assertions": [
@@ -13819,7 +13811,8 @@
"Audit1342 historical platform/provider scope before source changes: {\"coveredPlatforms\":[\"macos\"],\"coveredProviders\":[\"local\"]}. Remaining provider scope applies only to unchanged-source receipts.",
"Audit1342 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/renderer/src/lib/pane-manager/pane-webgl-context-recovery.test.ts\",\n \"assertions\": [\n \"recovers a context-lost pane on the next rendering resume\"\n ]\n }\n]",
"Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. Existing exact primary-selector receipts remain unchanged.",
"Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.spec.ts: Loses rich hidden snapshot sequence replay/grid resize and forced repaint after hidden snapshot alarms, including live-trigger deduplication. No equivalent replacement coverage or fresh result is claimed."
"Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.spec.ts: Loses rich hidden snapshot sequence replay/grid resize and forced repaint after hidden snapshot alarms, including live-trigger deduplication. No equivalent replacement coverage or fresh result is claimed.",
"Issue #22458 removes non-Windows repaint tests that only assert private synchronous refresh calls. Their renderer-risk and alternate-screen atlas assertions are no longer registered; historical results do not validate equivalent coverage."
],
"demotionRule": "Demote if ordinary PTY output re-enters shared reset/refresh recovery, pane-local redraw coverage regresses, ordinary visibility clears the atlas, real WebGL pixels regress, or the focused Electron test cannot remain deterministic."
},
@@ -135,308 +135,6 @@ export function registerPtyConnectionRendererRiskRepaintTests(): void {
expect(manager.markPaneHasComplexScriptOutput).not.toHaveBeenCalled()
})
it('forces a viewport refresh for foreground CJK output without CSI', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks3 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks3)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('没改什么(护城河)\r\n')
expect(refresh).toHaveBeenCalledWith(0, 39, true)
})
it('refreshes renderer-risk output without clearing the shared glyph atlas', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks4 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks4)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('没改什么(护城河)\r\n')
expect(refresh).not.toHaveBeenCalled()
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
})
it('refreshes alternate-screen redraws without clearing the shared glyph atlas', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks5 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
;(pane.terminal.buffer.active as { type: 'normal' | 'alternate' }).type = 'alternate'
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks5)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.(
'\x1b[2J\x1b[H{"name":"eepo"}\r\n\x1b[2;1H{"name":"expo"}\x1b[K'
)
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
} finally {
restoreNavigator()
}
})
it('refreshes a foreground rewrite entering alternate screen without an atlas clear', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks6 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks6)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('\x1b[?1049h\x1b[2J\x1b[HVim package.json')
// Why: xterm switches to the alternate buffer while parsing; the write callback observes the post-parse state.
;(pane.terminal.buffer.active as { type: 'normal' | 'alternate' }).type = 'alternate'
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
} finally {
restoreNavigator()
}
})
it('avoids atlas clears when alternate-screen entry splits across chunks', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks7 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks7)
)
await flushAsyncTicks(6)
// Why: PTY reads split CSI sequences at arbitrary byte boundaries, so the enter sequence can straddle two onData chunks.
capturedDataCallback.current?.('\x1b[?104')
parseCallback?.()
capturedDataCallback.current?.('9h\x1b[2J\x1b[H~\x1b[K')
;(pane.terminal.buffer.active as { type: 'normal' | 'alternate' }).type = 'alternate'
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
} finally {
restoreNavigator()
}
})
it('avoids atlas clears when a foreground rewrite leaves alternate screen', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks8 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
;(pane.terminal.buffer.active as { type: 'normal' | 'alternate' }).type = 'alternate'
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks8)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('\x1b[34;1H\x1b[K\x1b[34;1H\x1b[?1049l\x1b[?25h')
;(pane.terminal.buffer.active as { type: 'normal' | 'alternate' }).type = 'normal'
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
} finally {
restoreNavigator()
}
})
it('avoids atlas clears when one chunk enters and exits alternate screen', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks9 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks9)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('\x1b[?1049h\x1b[2J\x1b[Hpager frame\x1b[K\x1b[?1049l')
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
} finally {
restoreNavigator()
}
})
it('avoids atlas clears for captured redraw chunks split mid-sequence', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
@@ -70,50 +70,6 @@ export function registerPtyConnectionWindowsCjkRepaintTests(): void {
await restoreTerminalTestGlobals()
})
it('does not schedule WebGL atlas recovery for ordinary foreground shell rewrites', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
let parseCallback: (() => void) | undefined
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
parseCallback = callback
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('\r\x1b[Korca % npm test')
parseCallback?.()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expectNoGlobalAtlasRecovery()
} finally {
restoreNavigator()
}
})
it('does not schedule WebGL atlas recovery for plain synchronized foreground frames', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
@@ -151,133 +107,6 @@ export function registerPtyConnectionWindowsCjkRepaintTests(): void {
}
})
it('forces a viewport refresh when foreground background SGR is split across PTY chunks', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks3 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const manager = createManager(1)
const refresh = vi.fn()
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks3)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('\x1b[48')
expect(refresh).not.toHaveBeenCalled()
expect(manager.markPaneHasComplexScriptOutput).not.toHaveBeenCalled()
capturedDataCallback.current?.(';2;52;52;52m codex block \x1b[0m\r\n')
expect(manager.markPaneHasComplexScriptOutput).not.toHaveBeenCalled()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
})
it('forces a viewport refresh when the foreground CSI introducer is split across PTY chunks', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks4 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const manager = createManager(1)
const refresh = vi.fn()
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks4)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.('\x1b')
expect(refresh).not.toHaveBeenCalled()
capturedDataCallback.current?.('[48;2;52;52;52m codex block text \x1b[0m\r\n')
expect(manager.markPaneHasComplexScriptOutput).not.toHaveBeenCalled()
expect(refresh).toHaveBeenCalledWith(0, 39, true)
})
it('does not keep forcing viewport refresh after completed background redraws', async () => {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks5 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const manager = createManager(1)
const refresh = vi.fn()
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
connectPanePty(
pane as never,
manager as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks5)
)
await flushAsyncTicks(6)
capturedDataCallback.current?.(
'\x1b[2J\x1b[H\x1b[48;2;52;52;52m codex block text \x1b[0m\r\n'
)
expect(refresh).toHaveBeenCalledWith(0, 39, true)
expect(manager.markPaneHasComplexScriptOutput).not.toHaveBeenCalled()
refresh.mockClear()
capturedDataCallback.current?.('plain follow-up output\r\n')
expect(refresh).not.toHaveBeenCalled()
})
it('forces a viewport refresh for native Windows CJK foreground output after terminal input', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
@@ -498,49 +327,6 @@ export function registerPtyConnectionWindowsCjkRepaintTests(): void {
}
})
it('does not schedule a follow-up repaint for the Claude redraw pattern on non-Windows clients', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
const { connectPanePty } = await import('../connect-pane-pty')
const agentTitleMocks11 =
await vi.importMock<PtyConnectionAgentTitleMocks>('@/lib/agent-status')
const transport = createMockTransport()
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
transport.connect.mockImplementation(
async ({ callbacks }: { callbacks: ConnectCallbacks }) => {
capturedDataCallback.current = callbacks.onData ?? null
return 'pty-id'
}
)
transportFactoryQueue.push(transport)
const pane = createPane(1)
const refresh = vi.fn()
const terminal = pane.terminal as typeof pane.terminal & {
_core?: { refresh: typeof refresh }
}
terminal._core = { refresh }
terminal.write = vi.fn((_data: string | Uint8Array, callback?: () => void) => {
callback?.()
})
connectPanePty(
pane as never,
createManager(1) as never,
bindPaneAgentTitleMocks(createDeps() as never, agentTitleMocks11)
)
await flushAsyncTicks(6)
// CR redraw still forces one synchronous refresh, but no native-Windows follow-up repaint is scheduled.
capturedDataCallback.current?.('\r\x1b[3Gzzzx\x1b[K')
expect(refresh).toHaveBeenCalledTimes(1)
expect(refresh).toHaveBeenCalledWith(0, 39, true)
} finally {
restoreNavigator()
}
})
it('coalesces forced foreground refreshes when WebGL is live', async () => {
const restoreNavigator = temporarilySetNavigatorUserAgent('Mozilla/5.0 (Macintosh)')
try {
@@ -4,6 +4,7 @@ import { takeCurrentTerminalDeliveryCredit } from '@/lib/pane-manager/terminal-d
import { nativeWindowsRewriteNeedsFollowupRenderRefresh } from '@/lib/pane-manager/terminal-complex-script'
import { writeTerminalOutput } from '@/lib/pane-manager/pane-terminal-output-scheduler'
import { forceFullViewportPresent } from '@/lib/pane-manager/terminal-render-pause-release'
import { CLIENT_PLATFORM } from '@/lib/new-workspace'
import { FOREGROUND_SYNCHRONIZED_FRAME_INTERACTIVE_WINDOW_MS } from './foreground-output-budgets'
import { scanSynchronizedOutput } from '../../../../../../shared/terminal/protocol/escape/terminal-synchronized-output-scan'
@@ -18,6 +19,9 @@ import {
import type { ConnectPanePtySession } from '../connect-pane-pty-session'
export function bindWritePtyOutputToXterm(session: ConnectPanePtySession): void {
const shouldRefreshForegroundSynchronously = (): boolean =>
CLIENT_PLATFORM === 'win32' && session.shouldRefreshForegroundSynchronously()
session.writePtyOutputToXterm = function (
data: string,
foreground: boolean,
@@ -124,8 +128,8 @@ export function bindWritePtyOutputToXterm(session: ConnectPanePtySession): void
nativeWindowsCursorRestore ||
foregroundRenderRefreshNeeded),
followupForegroundRefresh: nativeWindowsCursorRestore || nativeWindowsInPlaceRewriteFollowup,
// Why: xterm already queued a WebGL frame parsing this chunk; merge the repair into it instead of rendering the grid twice.
shouldRefreshForegroundSynchronously: session.shouldRefreshForegroundSynchronously,
// Merge repairs into xterm's queued frame except for Windows DOM compatibility.
shouldRefreshForegroundSynchronously,
stripTransientCursorShows: session.shouldProtectNativeWindowsSynchronizedOutput && foreground,
coalesceForeground: synchronizedForegroundOutput && synchronizedOutputEnded,
holdForeground: synchronizedForegroundOutput && nextSynchronizedForegroundOutputActive
@@ -64,7 +64,7 @@ function refreshVisibleRows(
// xterm unions dirty ranges; use this parse’s span to avoid a full-grid walk on every frame, retaining null as full repaint.
const start = span ? Math.min(Math.max(span.start, 0), lastRow) : 0
const end = span ? Math.min(Math.max(span.end, start), lastRow) : lastRow
// Windows DOM rewrites need immediate repair; WebGL merges into its queued frame.
// Windows DOM rewrites need immediate repair; other renderers merge into their queued frame.
if (synchronously && typeof terminal._core?.refresh === 'function') {
terminal._core.refresh(start, end, true)
return