Repository navigation
Description
What happens
When the split tree changes shape, React remounts every pane whose position in the tree changed, and useTerminal disposes that pane's xterm instance and builds a new one. The PTY and the program in it keep running. Each piece of terminal state then has to be carried across by hand:
- the buffer, via the SerializeAddon snapshot (bug(terminal): terminal buffer clears on any split tree restructure (pane add/remove/drag) #49)
- mouse modes, via
surfaceMouseModesandmouseModeReplaySequence(bug(terminal): SGR mouse encoding is lost after terminal remount #164) - the cursor row ConPTY expects, via
ReplayHoldand the ConPTY anchor (fix(terminal): keep xterm and ConPTY agreeing about the cursor row #216, fix(terminal): anchor a reflow the way ConPTY does, or the prompt strands #256) - the alternate screen (Pane loses alternate-screen state, so exiting a fullscreen TUI draws the shell prompt over the old frame #345)
Some state still does not survive. Prompt markers are lost, which CLAUDE.md already documents ("Markers do not survive a split-tree restructure"). SerializeAddon also does not carry cursor visibility, cursor style or the scroll region.
Each of these was found the same way: a user hit a broken pane after a layout change, and a new piece of state was added to the replay. The list will keep growing as long as a remount means a new terminal.
What triggers a remount (from reading the code):
SplitContainer.tsxkeys each branch child bygetTreeKey(subtree), so splitting a pane, closing its sibling or moving a tab between panes remounts the panes under the changed branch.App.tsxrendersfindLeaf(ws.splitTree, zoomedPaneId)as the root while a pane is zoomed, so zooming and unzooming remount panes.wmux browser openin a workspace with no browser pane splits the first pane. For an agent running in a one-pane workspace, that pane is its own.
Suggestion
Keep one xterm instance per surface for the surface's whole life, in a module-level registry keyed by surface id. A remount then moves the existing terminal's DOM element into the new container (container.appendChild(terminal.element) followed by a fit) instead of disposing it. Nothing has to be serialized or replayed, and every mode, marker and decoration survives because the emulator never changes.
Things this would need to handle:
- disposing the terminal only on a real surface close (
handleCloseSurface), not on unmount - re-attaching or recreating the WebGL renderer when the element moves, if its context does not survive the move
- the
ReplayHoldand ConPTY-anchor logic becoming unnecessary for remounts, though a resize after the move still needs the existing ConPTY handling
This is a larger change than a bug fix, so I'm opening it as a proposal. I haven't prototyped it.
wmux version
2.39.0
Anything else
Related: #49, #164, #216, #256, #345. Browser panes have the same remount problem from the other side (#315).
Activity
amirlehmam commentedon Oct 24, 2026
Shipped in v2.40.0. Thanks for the detailed report!
fix(terminal): replay remount state only into the PTY it was taken fr…
fix(session): Load session ends the shells of the panes it drops (#361)
feat(terminal): keep each surface's xterm alive across split-tree rem…