diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 6da2c3eb77b..437effdd180 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -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." }, diff --git a/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-renderer-risk-repaint.spec.ts b/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-renderer-risk-repaint.spec.ts index 0503f27d088..e5df7a8d640 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-renderer-risk-repaint.spec.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-renderer-risk-repaint.spec.ts @@ -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('@/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('@/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('@/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('@/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('@/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('@/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('@/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 { diff --git a/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-windows-cjk-repaint.spec.ts b/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-windows-cjk-repaint.spec.ts index 57f317c152a..ce2cd9039fa 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-windows-cjk-repaint.spec.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/output/pty-connection-windows-cjk-repaint.spec.ts @@ -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('@/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('@/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('@/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('@/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('@/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 { diff --git a/src/renderer/src/components/terminal-pane/pty-connection/output/write-pty-output-to-xterm.ts b/src/renderer/src/components/terminal-pane/pty-connection/output/write-pty-output-to-xterm.ts index 42828c02b5c..d422d9382bb 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/output/write-pty-output-to-xterm.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/output/write-pty-output-to-xterm.ts @@ -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 diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.ts b/src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.ts index 78e6435ed10..9c6468ee076 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.ts @@ -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