Skip to content

fix(terminal): faithfully transfer terminals across windows (v1.2.7)#356

Merged
Anton-Horn merged 6 commits into
mainfrom
fix/detached-terminal-pty-resize
Jun 9, 2026
Merged

fix(terminal): faithfully transfer terminals across windows (v1.2.7)#356
Anton-Horn merged 6 commits into
mainfrom
fix/detached-terminal-pty-resize

Conversation

@Anton-Horn

@Anton-Horn Anton-Horn commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Makes a terminal dragged into its own window — or reopened on the next launch — come back exactly as it was, and folds in a workspace duplicate-root guard.

Terminals

Lost styling (the proper fix). Scrollback was captured with translateToString(), which returns plain text and drops every color/bold. Capture now uses xterm's @xterm/addon-serialize (serializeTerminalState) — escape sequences that restore text, styling, wrapping and cursor verbatim. This is the single capture path for both live cross-window transfer and session/detached-window persistence; replay writes the serialized string verbatim. The old translateToString text path, its wrapped-row coalescing, and excludeCursorRow are gone.

Blank/garbled frame. The WebGL renderer initialized while the new window was still hidden, so its glyph atlas was built against a stale DPR and its buffer never painted. Now we force an atlas rebuild + repaint once the window is shown, and on every visibility transition.

Stale size + half-drawn full-screen programs. The reconnected PTY kept the source window's winsize, so ls formatted for the old width and in-place renderers (Claude Code/Ink, vim, htop) never got the SIGWINCH they need to repaint. finalizeReconnect nudges the winsize (one row short, then the real fitted size 150ms later), guaranteeing a SIGWINCH at the correct size.

Adds @xterm/addon-serialize.

Workspace

Duplicate-root guard. Two tabs pointed at the same folder would share its .cate/ state and clobber each other's autosave (the per-pid project lock can't catch a same-process duplicate). The renderer now redirects to the tab that already has the folder; main backstops it with a DUPLICATE_ROOT check on the resolved path (catches symlink/trailing-slash aliases).

Bumps to 1.2.7 + changelog.

Test

  • Full suite green (1210 passed)
  • Real-xterm round-trip asserts color + bold survive serialize → replay
  • New same-instance duplicate-root guard tests

@Anton-Horn Anton-Horn force-pushed the fix/detached-terminal-pty-resize branch from 2697bd0 to 40b9845 Compare June 9, 2026 13:25
@Anton-Horn Anton-Horn changed the title fix(terminal): resize PTY to the new window on reconnect (v1.2.7) fix(terminal): resync and repaint the PTY on cross-window reconnect (v1.2.7) Jun 9, 2026
@Anton-Horn Anton-Horn force-pushed the fix/detached-terminal-pty-resize branch from 40b9845 to 47ffa84 Compare June 9, 2026 13:46
@Anton-Horn Anton-Horn changed the title fix(terminal): resync and repaint the PTY on cross-window reconnect (v1.2.7) fix(terminal): faithfully transfer terminals across windows (v1.2.7) Jun 9, 2026
A terminal dragged into its own window — or reopened on the next launch —
came back wrong. Root causes and fixes:

- Blank/garbled frame: the WebGL renderer initialized while the new window
  was still hidden, so its glyph atlas was built against a stale DPR and its
  buffer never painted. Force an atlas rebuild + repaint once the window is
  shown, and on every visibility transition.

- Lost styling: scrollback was captured with translateToString(), which
  returns plain text and drops every color/bold. Capture now uses xterm's
  SerializeAddon (serializeTerminalState) — escape sequences that restore
  text, styling, wrapping and cursor verbatim. This is the SINGLE capture
  path for both live cross-window transfer and session/detached-window
  persistence; replay writes the serialized string verbatim. The old
  translateToString text path, its coalescing, and excludeCursorRow are gone.

- Stale size + half-drawn TUIs: the reconnected PTY kept the source window's
  winsize, so commands like `ls` formatted for the old width and in-place
  renderers (Claude Code/Ink, vim, htop) never got the SIGWINCH they need to
  repaint. finalizeReconnect now nudges the winsize (one row short, then the
  real fitted size 150ms later) so the kernel always delivers a SIGWINCH.

Adds @xterm/addon-serialize. Bump to v1.2.7.
@Anton-Horn Anton-Horn force-pushed the fix/detached-terminal-pty-resize branch from 47ffa84 to d1acb7b Compare June 9, 2026 14:07
Two workspace tabs pointed at the same root would share its .cate/
workspace.json + session.json and clobber each other's autosave. The
per-pid project lock can't catch this — two tabs in the same process always
re-acquire it.

- remoteSlice.setWorkspaceRootPath redirects to the workspace that already
  has the folder (focuses it; the empty outgoing tab is discarded on switch)
  instead of duplicating it.
- workspaceManager backstops with a DUPLICATE_ROOT check on the RESOLVED
  path, catching symlink / trailing-slash aliases the renderer's raw string
  compare misses.
@Anton-Horn Anton-Horn force-pushed the fix/detached-terminal-pty-resize branch from c5ed268 to 10643ef Compare June 9, 2026 14:15
@Anton-Horn Anton-Horn merged commit c7ec82e into main Jun 9, 2026
3 checks passed
@Anton-Horn Anton-Horn deleted the fix/detached-terminal-pty-resize branch June 9, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant