From cb6a7231095cfed8b0ef1dd01eb6304a2fd279a8 Mon Sep 17 00:00:00 2001 From: wsp1911 Date: Mon, 23 Mar 2026 21:38:00 +0800 Subject: [PATCH 1/2] fix(terminal): debounce backend resize for Git Bash on Windows Prevent Windows Git Bash prompts from being truncated after shrinking and re-expanding the terminal panel. Keep xterm resizing responsive locally, but merge backend PTY resize updates so ConPTY does not replay stale intermediate widths and corrupt the prompt layout. --- BitFun@0.1.1 | 0 bitfun-mobile-web@0.1.1 | 0 node | 0 .../src/tools/terminal/utils/TerminalResizeDebouncer.ts | 8 +++++++- tsc | 0 5 files changed, 7 insertions(+), 1 deletion(-) delete mode 100644 BitFun@0.1.1 delete mode 100644 bitfun-mobile-web@0.1.1 delete mode 100644 node delete mode 100644 tsc diff --git a/BitFun@0.1.1 b/BitFun@0.1.1 deleted file mode 100644 index e69de29b..00000000 diff --git a/bitfun-mobile-web@0.1.1 b/bitfun-mobile-web@0.1.1 deleted file mode 100644 index e69de29b..00000000 diff --git a/node b/node deleted file mode 100644 index e69de29b..00000000 diff --git a/src/web-ui/src/tools/terminal/utils/TerminalResizeDebouncer.ts b/src/web-ui/src/tools/terminal/utils/TerminalResizeDebouncer.ts index 34d1562f..fb6c9e30 100644 --- a/src/web-ui/src/tools/terminal/utils/TerminalResizeDebouncer.ts +++ b/src/web-ui/src/tools/terminal/utils/TerminalResizeDebouncer.ts @@ -115,7 +115,13 @@ export class TerminalResizeDebouncer { if (immediate || bufferLength < START_DEBOUNCING_THRESHOLD) { this.clearPendingJobs(); - this.executeResize(cols, rows, true); + if (this.isNewApi) { + const opts = this.options as ResizeDebounceOptions; + opts.onXtermResize(cols, rows); + this.scheduleBackendResize(cols, rows); + } else { + this.executeResize(cols, rows, true); + } return; } diff --git a/tsc b/tsc deleted file mode 100644 index e69de29b..00000000 From c2003f3e15ad0836c3ddea9c97eece479bd1cc79 Mon Sep 17 00:00:00 2001 From: wsp1911 Date: Mon, 23 Mar 2026 21:38:00 +0800 Subject: [PATCH 2/2] fix(terminal): refresh xterm char metrics when first terminal opens before fonts load --- .../tools/terminal/components/Terminal.tsx | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/web-ui/src/tools/terminal/components/Terminal.tsx b/src/web-ui/src/tools/terminal/components/Terminal.tsx index d59e1b1f..aafbd1c1 100644 --- a/src/web-ui/src/tools/terminal/components/Terminal.tsx +++ b/src/web-ui/src/tools/terminal/components/Terminal.tsx @@ -17,6 +17,17 @@ import './Terminal.scss'; const log = createLogger('Terminal'); +type TerminalCoreWithMeasurement = XTerm & { + _core?: { + _charSizeService?: { + measure?: () => void; + }; + _renderService?: { + handleDevicePixelRatioChange?: () => void; + }; + }; +}; + /** * Clear xterm texture atlas when supported. * Used to force redraws and avoid WebGL cache artifacts. @@ -31,6 +42,12 @@ function clearTextureAtlas(terminal: XTerm): void { } } +function remeasureTerminal(terminal: XTerm): void { + const rawTerminal = terminal as TerminalCoreWithMeasurement; + rawTerminal._core?._charSizeService?.measure?.(); + rawTerminal._core?._renderService?.handleDevicePixelRatioChange?.(); +} + /** * Scroll to bottom when the cursor is below the viewport. */ @@ -460,6 +477,31 @@ const Terminal = forwardRef(({ } }); + let fontLoadCancelled = false; + if (typeof document !== 'undefined' && 'fonts' in document) { + const fontSet = document.fonts as FontFaceSet; + if (fontSet.status !== 'loaded') { + void fontSet.ready.then(() => { + if (fontLoadCancelled || !terminalRef.current) { + return; + } + + requestAnimationFrame(() => { + if (!terminalRef.current) return; + + remeasureTerminal(terminalRef.current); + fit(true); + + requestAnimationFrame(() => { + if (!terminalRef.current) return; + forceRefresh(terminalRef.current); + scrollToBottomIfNeeded(terminalRef.current); + }); + }); + }); + } + } + const dataDisposable = terminal.onData((data) => { onData?.(data); }); @@ -548,6 +590,7 @@ const Terminal = forwardRef(({ titleDisposable.dispose(); resizeObserver.disconnect(); intersectionObserver.disconnect(); + fontLoadCancelled = true; resizeDebouncer.dispose(); webglAddonRef.current?.dispose(); terminal.dispose();