diff --git a/src/renderer/src/components/terminal-pane/pty-connection/pty-input-forward.ts b/src/renderer/src/components/terminal-pane/pty-connection/pty-input-forward.ts index 8d91e498dea..6ff7c244443 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection/pty-input-forward.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection/pty-input-forward.ts @@ -23,6 +23,7 @@ import { TERMINAL_FOCUS_IN_SEQUENCE, TERMINAL_FOCUS_OUT_SEQUENCE } from './foreg import { isRemoteRuntimePtyId } from './paired-parked-terminal-restore' import { isCodexPaneStale } from './codex-pane-stale' import { installTerminalSelectionFitGuard } from '../terminal-selection-fit-guard' +import { initializePaneGeometry, readPaneSize } from './read-pane-size' import type { ConnectPanePtySession } from './connect-pane-pty-session' @@ -308,12 +309,9 @@ export function installPtyInputForward(session: ConnectPanePtySession): void { if (reattachCols > 0 && reattachRows > 0) { session.transport.resize(reattachCols, reattachRows) } - // Why: POSIX only sends SIGWINCH on an actual dimension change; signal explicitly so restored TUIs repaint at the correct cursor after replay. if (!isRemoteRuntimePtyId(reattachPtyId)) { window.api.pty.signal(reattachPtyId, 'SIGWINCH') } - // Why here: a deferred reveal resolves the fit handle as incomplete, so an awaited - // reassertion at the call site would never run for that path. if (session.deps.isVisibleRef.current) { session.ptySizeReassertion.request({ fit: false }) } @@ -341,8 +339,6 @@ export function installPtyInputForward(session: ConnectPanePtySession): void { ) } session.scheduleForegroundGridDriftCheck = (force = false): void => { - // Why: mobile-owned PTYs intentionally keep a non-desktop grid; drift - // healing would refit xterm even if resize forwarding is later suppressed. if ( session.disposed || !session.deps.isVisibleRef.current || @@ -371,27 +367,12 @@ export function installPtyInputForward(session: ConnectPanePtySession): void { ) { return } - // Why: xterm cell metrics can settle after the DOM box stops resizing, so - // ResizeObserver never fires even though FitAddon now proposes more cols. requestStablePaneFit(session.pane as ManagedPaneInternal, () => session.ptySizeReassertion.request({ fit: false }) ) }) } - // Why: observe the outer pane as the layout signal for both desktop drift - // healing and mobile take-back. Normal desktop panes compare xterm against - // the PTY's applied size; mobile-fit panes only report desktop geometry so - // the parked phone-sized PTY is not resized. See docs/mobile-fit-hold.md. - session.pendingGeometryReportRaf = null - session.lastObservedDesktopGrid = null - session.readPaneSize = (): { width: number; height: number } | null => { - if (typeof session.pane.container.getBoundingClientRect !== 'function') { - return null - } - const rect = session.pane.container.getBoundingClientRect() - return { width: rect.width, height: rect.height } - } - session.lastObservedPaneSize = session.readPaneSize() - session.pendingPaneGeometryChanged = false + session.readPaneSize = () => readPaneSize(session) + initializePaneGeometry(session) } diff --git a/src/renderer/src/components/terminal-pane/pty-connection/read-pane-size.ts b/src/renderer/src/components/terminal-pane/pty-connection/read-pane-size.ts new file mode 100644 index 00000000000..bf316160926 --- /dev/null +++ b/src/renderer/src/components/terminal-pane/pty-connection/read-pane-size.ts @@ -0,0 +1,18 @@ +import type { ConnectPanePtySession } from './connect-pane-pty-session' + +export function readPaneSize( + session: ConnectPanePtySession +): { width: number; height: number } | null { + if (typeof session.pane.container.getBoundingClientRect !== 'function') { + return null + } + const rect = session.pane.container.getBoundingClientRect() + return { width: rect.width, height: rect.height } +} + +export function initializePaneGeometry(session: ConnectPanePtySession): void { + session.pendingGeometryReportRaf = null + session.lastObservedDesktopGrid = null + session.lastObservedPaneSize = readPaneSize(session) + session.pendingPaneGeometryChanged = false +} diff --git a/src/renderer/src/components/terminal-pane/terminal-selection-fit-guard.ts b/src/renderer/src/components/terminal-pane/terminal-selection-fit-guard.ts index 0130aac82c6..f9f6befc6bb 100644 --- a/src/renderer/src/components/terminal-pane/terminal-selection-fit-guard.ts +++ b/src/renderer/src/components/terminal-pane/terminal-selection-fit-guard.ts @@ -37,17 +37,23 @@ export function installTerminalSelectionFitGuard( active = false } - terminalElement?.addEventListener('mousedown', handleMouseDown, true) - ownerDocument?.addEventListener('mouseup', handleMouseUp, true) - ownerWindow?.addEventListener('blur', clear) + if (terminalElement && typeof terminalElement.addEventListener === 'function') { + terminalElement.addEventListener('mousedown', handleMouseDown, true) + } + if (ownerDocument && typeof ownerDocument.addEventListener === 'function') { + ownerDocument.addEventListener('mouseup', handleMouseUp, true) + } + if (ownerWindow && typeof ownerWindow.addEventListener === 'function') { + ownerWindow.addEventListener('blur', clear) + } return { isActive: () => active, dispose: () => { clear() - terminalElement?.removeEventListener('mousedown', handleMouseDown, true) - ownerDocument?.removeEventListener('mouseup', handleMouseUp, true) - ownerWindow?.removeEventListener('blur', clear) + terminalElement?.removeEventListener?.('mousedown', handleMouseDown, true) + ownerDocument?.removeEventListener?.('mouseup', handleMouseUp, true) + ownerWindow?.removeEventListener?.('blur', clear) } } }