From acdf95591872cb802dff1324fa55f17be0f49095 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 14 Sep 2026 01:10:57 +0800 Subject: [PATCH] fix: debounce pty resize and skip no-op resizes (kills duplicated TUI frames on maximize/restore) --- AGENTS.md | 5 ++++ src/renderer/src/terminal/TerminalView.tsx | 27 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bcc6072..9e7abca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,3 +41,8 @@ Electron + electron-vite + React 终端工具(本地终端 / SSH / SFTP)。 - 终端主题由 xterm 主题派生 UI 配色:`src/renderer/src/theme/chrome.ts` 的 `applyChromeTheme` 写入 `--chrome-bg/-bg-deep/-border/-hover` CSS 变量,antd token 在 `main.tsx` ThemedConfigProvider 派生 - 标签强调色:`settings.tabAccentColor`(默认 `#3fb950`),经 `--tab-accent` CSS 变量生效 + +## 终端尺寸同步 + +- `TerminalView.scheduleFit`:fit 后**去抖 100ms** 再把 cols/rows 发给 PTY,并跳过与上次相同的尺寸。每次 ResizeObserver 都戳 PTY 会让全屏 TUI(Claude Code 等)在最大化/还原的中间尺寸上反复重绘,留下重复帧 +- 拖动窗口期间 xterm 网格立即更新,PTY 尺寸在停止后 100ms 生效 diff --git a/src/renderer/src/terminal/TerminalView.tsx b/src/renderer/src/terminal/TerminalView.tsx index cc16863..a7abdb8 100644 --- a/src/renderer/src/terminal/TerminalView.tsx +++ b/src/renderer/src/terminal/TerminalView.tsx @@ -226,6 +226,13 @@ export const TerminalView: ForwardRefExoticComponent(null) const observerRef = useRef(null) const timerRef = useRef(0) + // PTY resize bookkeeping: `ptyTimerRef` debounces the resize while a window + // drag/maximize settles, `ptySizeRef` drops the no-op resizes in between. + // Poking the pty on every intermediate layout tick makes full-screen TUIs + // (Claude Code et al.) redraw repeatedly at half-settled sizes, which is what + // leaves duplicated frames behind in the scrollback. + const ptyTimerRef = useRef(0) + const ptySizeRef = useRef<{ cols: number; rows: number } | null>(null) const deadRef = useRef(false) const searchOpenRef = useRef(false) const searchTermRef = useRef('') @@ -337,9 +344,21 @@ export const TerminalView: ForwardRefExoticComponent { + const live = termRef.current + if (!live || deadRef.current) return + if (live.cols === ptySizeRef.current?.cols && live.rows === ptySizeRef.current?.rows) return + ptySizeRef.current = { cols: live.cols, rows: live.rows } + window.api.resizePty(sessionId, live.cols, live.rows) + // 100ms mirrors what other Electron terminals use: long enough to swallow + // a maximize/restore animation tick storm, short enough that a plain + // window drag does not visibly wrap against a stale width. + }, 100) }) }, [sessionId]) @@ -616,6 +635,7 @@ export const TerminalView: ForwardRefExoticComponent { if (carryTimer !== undefined) window.clearTimeout(carryTimer) + if (ptyTimerRef.current) window.clearTimeout(ptyTimerRef.current) for (const unsubscribe of unsubscribes) unsubscribe() for (const disposable of disposables) disposable.dispose() observer?.disconnect()