Skip to content

bug(terminal): terminal buffer clears on any split tree restructure (pane add/remove/drag) #49

Description

@schroldgames

Environment

  • wmux: v0.8.7 (confirmed on fresh install)
  • Reproducible on any shell (bash, zsh, PowerShell — does not require tmux)

Reproduction steps

  1. Open wmux with a single terminal pane. Type some commands so there is visible text in the buffer.
  2. Click any link in the terminal (or use the + caret to open a Browser tab). A second pane appears.
  3. The original terminal pane is now completely blank. All scrollback history is gone.

Also reproducible by:

  • Dragging a terminal surface from one pane group to another
  • Closing a pane (the surviving pane's buffer clears)
  • Any operation that restructures the split tree depth

Expected behavior

The terminal should retain its scrollback buffer and visible content across split tree changes.

Root cause

SplitContainer is a recursive component that renders differently based on whether its node is a leaf or a branch:

Single pane (leaf node):

SplitContainer(root=leaf) → <div.split-child> → <PaneWrapper key="paneA">

After adding a second pane (branch node):

SplitContainer(root=split) → <div.split-container>
  → <div.split-child> → <SplitContainer(leaf)> → <div.split-child> → <PaneWrapper key="paneA">

PaneWrapper moves from 3 levels deep to 6 levels deep with a different parent component. React's reconciliation algorithm matches children by key within the same parent — since the parent changed, the key="paneA" prop does not prevent an unmount + remount. A fresh xterm.Terminal instance is created with an empty buffer. The PTY process keeps running (the keep-alive design works correctly), but xterm has no memory of what it previously displayed.

This affects all terminal types:

  • Plain shells (bash/zsh/PowerShell): Buffer is gone. Terminal appears blank until the user presses Enter to get a new prompt. Scrollback history is permanently lost for that session.
  • TUIs (tmux, vim, etc.): pty.resize() sends SIGWINCH after remount, which triggers a full screen redraw. Content comes back after ~100–200ms. Scrollback is still lost, but the active screen is restored.

Why it wasn't obvious before

For tmux users, the SIGWINCH redraw makes the terminal appear to "survive" the remount. The issue is most visible with plain shells, and it only manifests when actually restructuring the split tree (most common operations — like switching tabs or workspaces — do not trigger a remount).

Suggested fix

Short-term mitigation: After reattaching to an existing PTY with a plain shell (detected via absence of SGR mouse tracking escapes), write \x0c (Ctrl+L) to the PTY to trigger a prompt redraw. This won't recover scrollback but prevents the terminal from appearing completely dead.

Proper fix (Option A): Install @xterm/addon-serialize and snapshot the terminal buffer state before PaneWrapper unmounts, restoring it into the new xterm.Terminal instance on remount. xterm's SerializeAddon supports serializing both the visible rows and scrollback.

Proper fix (Option B): Restructure SplitContainer so that PaneWrapper always renders at a consistent depth in the React tree, regardless of whether the node is a leaf or branch. This would prevent React from unmounting the component at all during tree restructuring. Requires a more significant refactor of the split pane renderer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions