Skip to content

bug: two windows on the same chat — terminal size state desyncs across primary flips (stale lastPushed dedupe, dual-primary push race, wrong-cols replay) #313

Description

@yicheng47

Description

With two windows showing the same chat tab, the terminal's size state can desync when primary ownership flips between windows. Three mechanisms, from code inspection of the impl 0018/0020 path:

  1. Wrong-cols replay on every flip (by design, with a real cost). A secondary window mounts nothing (ChatPaneGroup.tsx renderTerminalPane: secondaryFor(id)?.secondary → null), so a secondary→primary flip is a fresh xterm mount that replays ring bytes recorded at the other window's cols into this window's grid. The live region heals (attach dance → cols push → fix(session): terminal history loss on tab return — ring purge cols-gate + measurable WebGL attach #308 purge → SIGWINCH repaint), but the re-wrapped replay stays as garbled scrollback, accumulating with every flip between differently-sized windows.

  2. Size state is frontend-local and never reconciled. Each RunnerTerminal instance dedupes pushes against its own lastPushedColsRef/lastPushedRowsRef (RunnerTerminal.tsx:206), and session_resize is push-only — there is no API to read the PTY's actual size (api.ts:309, commands/session.rs:109). When the other window resizes the PTY, this window's refs go stale: refresh-push-skip (RunnerTerminal.tsx:362-371) can then skip a push the PTY actually needs. Same stale-size class as bug: resuming an old claude-code terminal shows a black screen with cursor at top until a manual window resize #312.

  3. Dual-primary push race during flips. Primary is derived from the debounced (80ms), broadcast focus map (windowFocus.ts), so there's an interval during a flip where both windows are mounted and both push conflicting sizes. Last write wins — and the loser can be the window that stays primary, leaving the PTY at the other window's size while nothing on the primary re-pushes (its rect didn't change, so no ResizeObserver event). The agent then repaints at the wrong width into the primary's grid until a manual window resize.

Expected behavior

The primary window's grid and the PTY size converge without a manual resize, and flips between differently-sized windows don't accumulate garbled scrollback.

Suggested direction

An authoritative size read-back — either a session_pty_size query or the current size echoed on resize acks / session events — lets the activation path reconcile against the PTY's real size instead of frontend-local lastPushed refs. That single primitive addresses (2), (3), and the refresh-push-skip half of #312.

Relevant code

  • src/components/ChatPaneGroup.tsxrenderTerminalPane secondary gate (unmount/remount on flip).
  • src/components/RunnerTerminal.tsx:206,362-371lastPushed*Ref dedupe; :1001-1091 replay drain (wrong-cols rewrap).
  • src/lib/windowFocus.ts — debounced focus map → isSecondaryFor (dual-primary interval).
  • src-tauri/src/commands/session.rs:109session_resize (push-only; no size query).

Environment

  • OS: macOS
  • Version: 0.3.17

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Annoying — fix when convenientbugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions