From 9feb963cc97775305617d47846860bb06aeedb76 Mon Sep 17 00:00:00 2001 From: mgabor3141 <@mgabor3141> Date: Thu, 12 Mar 2026 18:50:49 +0100 Subject: [PATCH] fix: defer viewport DOM sync during synchronized output Defer Viewport DOM scroll updates while DEC 2026 synchronized output is active, then flush once rendering resumes so DOM and canvas update atomically. Also update demo sizing to avoid relying on removed internal viewport scrollBarWidth. --- demo/client/client.ts | 6 +++++- src/browser/Viewport.ts | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/demo/client/client.ts b/demo/client/client.ts index 77020680f..aab723b7e 100644 --- a/demo/client/client.ts +++ b/demo/client/client.ts @@ -617,8 +617,12 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a } function updateTerminalSize(): void { + const showScrollbar = term!.options.scrollbar?.showScrollbar ?? true; + const scrollBarWidth = (term!.options.scrollback === 0 || !showScrollbar) + ? 0 + : (term!.options.scrollbar?.width ?? 14); const width = optionsWindow.autoResize ? '100%' - : ((term as any).dimensions.css.canvas.width + (term as any)._core.viewport.scrollBarWidth).toString() + 'px'; + : ((term as any).dimensions.css.canvas.width + scrollBarWidth).toString() + 'px'; const height = optionsWindow.autoResize ? '100%' : ((term as any).dimensions.css.canvas.height).toString() + 'px'; terminalContainer!.style.width = width; diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 115de8d3d..3b6ae8301 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -27,6 +27,7 @@ export class Viewport extends Disposable { private _isSyncing: boolean = false; private _isHandlingScroll: boolean = false; private _suppressOnScrollHandler: boolean = false; + private _needsSyncOnRender: boolean = false; constructor( element: HTMLElement, @@ -105,6 +106,16 @@ export class Viewport extends Disposable { })); this._register(this._bufferService.onScroll(() => this._sync())); + // Flush deferred viewport sync after a render completes (e.g. after ESU ends + // synchronized output mode). This ensures DOM scroll position updates atomically + // with the canvas render. + this._register(this._renderService.onRender(() => { + if (this._needsSyncOnRender) { + this._needsSyncOnRender = false; + this._sync(); + } + })); + this._register(this._scrollableElement.onScroll(e => this._handleScroll(e))); } @@ -162,8 +173,12 @@ export class Viewport extends Disposable { if (!this._renderService || this._isSyncing) { return; } - // Plumb synchronized output state into Viewport for follow-up behavior changes. - void this._coreService.decPrivateModes.synchronizedOutput; + // Defer DOM scroll updates during synchronized output to prevent visible + // scroll position flickering while the canvas content is frozen. + if (this._coreService.decPrivateModes.synchronizedOutput) { + this._needsSyncOnRender = true; + return; + } this._isSyncing = true; // Ignore any onScroll event that happens as a result of dimensions changing as this should