From fa88f76b3c29c8e2021da53f830ce07b5b586a5f Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Sun, 19 Apr 2026 23:24:03 -0400 Subject: [PATCH] refactor: extract scheduleSplitScrollRestore to stay under max-lines lint Moves the two-phase scroll restoration schedule (double-rAF + 200ms timeout) into pane-split-scroll.ts so both pane-manager.ts and pane-tree-ops.ts stay under the 300-line oxlint limit. --- .../src/lib/pane-manager/pane-manager.ts | 42 ++++--------------- .../src/lib/pane-manager/pane-split-scroll.ts | 37 ++++++++++++++++ 2 files changed, 44 insertions(+), 35 deletions(-) create mode 100644 src/renderer/src/lib/pane-manager/pane-split-scroll.ts diff --git a/src/renderer/src/lib/pane-manager/pane-manager.ts b/src/renderer/src/lib/pane-manager/pane-manager.ts index d6737f46..0765551a 100644 --- a/src/renderer/src/lib/pane-manager/pane-manager.ts +++ b/src/renderer/src/lib/pane-manager/pane-manager.ts @@ -33,9 +33,9 @@ import { safeFit, fitAllPanesInternal, captureScrollState, - restoreScrollState, refitPanesUnder } from './pane-tree-ops' +import { scheduleSplitScrollRestore } from './pane-split-scroll' export type { PaneManagerOptions, PaneStyleOptions, ManagedPane, DropZone } @@ -129,40 +129,12 @@ export class PaneManager { void this.options.onPaneCreated?.(this.toPublic(newPane)) this.options.onLayoutChanged?.() - // Why: reparenting the container resets the viewport scroll position - // immediately (browser clears scrollTop on DOM move). The early - // double-rAF restore fires after the first fit settles (~2 frames / - // ~32ms) to minimise the visible flash. The lock stays in place so - // intermediate fits don't interfere. The later 200ms timeout is the - // authoritative final restore that also clears the lock — it catches - // any late async reflows (ResizeObserver 150ms debounce, WebGL - // context-loss rAFs) that might shift the position after the early - // restore. - const existingPaneId = existing.id - - requestAnimationFrame(() => { - requestAnimationFrame(() => { - if (this.destroyed) { - return - } - const live = this.panes.get(existingPaneId) - if (live?.pendingSplitScrollState) { - restoreScrollState(live.terminal, scrollState) - } - }) - }) - - setTimeout(() => { - if (this.destroyed) { - return - } - const live = this.panes.get(existingPaneId) - if (!live) { - return - } - live.pendingSplitScrollState = null - restoreScrollState(live.terminal, scrollState) - }, 200) + scheduleSplitScrollRestore( + (id) => this.panes.get(id), + existing.id, + scrollState, + () => this.destroyed + ) return this.toPublic(newPane) } diff --git a/src/renderer/src/lib/pane-manager/pane-split-scroll.ts b/src/renderer/src/lib/pane-manager/pane-split-scroll.ts new file mode 100644 index 00000000..e405fb5c --- /dev/null +++ b/src/renderer/src/lib/pane-manager/pane-split-scroll.ts @@ -0,0 +1,37 @@ +import type { ManagedPaneInternal, ScrollState } from './pane-manager-types' +import { restoreScrollState } from './pane-tree-ops' + +// Why: reparenting a terminal container during split resets the viewport +// scroll position (browser clears scrollTop on DOM move). This schedules a +// two-phase restore: an early double-rAF (~32ms) to minimise the visible +// flash, plus a 200ms authoritative restore that also clears the scroll lock. +export function scheduleSplitScrollRestore( + getPaneById: (id: number) => ManagedPaneInternal | undefined, + paneId: number, + scrollState: ScrollState, + isDestroyed: () => boolean +): void { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + if (isDestroyed()) { + return + } + const live = getPaneById(paneId) + if (live?.pendingSplitScrollState) { + restoreScrollState(live.terminal, scrollState) + } + }) + }) + + setTimeout(() => { + if (isDestroyed()) { + return + } + const live = getPaneById(paneId) + if (!live) { + return + } + live.pendingSplitScrollState = null + restoreScrollState(live.terminal, scrollState) + }, 200) +}