Text rows overlap during rapid TUI redraws in short local-shell viewports — pinned @xterm/xterm 5.4.0 implicated (Orca clean on 6.1.x)
Summary
Text rows visually overlap (stale glyph remnants under newly painted rows) during rapid incremental redraws in local shell sessions on Windows — e.g. a TUI that full-screen repaints and then redraws an above-prompt popup on every keystroke. The problem tracks the viewport row count: short viewports (large font / small window) reproduce it, tall viewports do not. Evidence points to the pinned @xterm/xterm 5.4.0 core: the same Electron + node-pty + xterm stack with xterm 6.1.x (Orca) is unaffected at the same small buffer size.
Environment
- Windows 10 Pro 64-bit (19045), CMD + Clink shell profile (local shell)
- Tabby 1.0.235 (current);
@xterm/xtermresolved to 5.4.0 intabby-terminal(yarn.lock), node-pty1.2.0-beta.8 - Reproducer app: the oh-my-pi "omp" interactive TUI (
/command popup), but the mechanism is generic (full-frame repaint taller than the pty viewport, followed by incremental row-diff redraws)
Repro
- Local shell, small viewport:
mode conreports Lines: 36 (== viewport rows). - Run the TUI; type
/then 3+ filter letters (oooo), then Backspace a few times — each keystroke repaints the popup above the input line. - Rows overlap / collide (stale fragments from the previous frame remain mixed with the new row content). Enlarging the viewport so rows ≥ ~65 (smaller font) makes it disappear entirely.
What was ruled out
- ConPTY toggle: reproduces with ConPTY on and off (node-pty conpty and winpty backends both).
- Renderer: reproduces with the
xterm (WebGL)andxterm (canvas)frontends, and with Electron "Disable GPU acceleration". - Synchronized output (DEC 2026): unrelated — markers are ignored by xterm 5.4 either way; toggling sync output changes nothing.
Clean references for the same TUI at identical interaction: Windows Terminal, Alacritty, plain CMD (conhost), and Orca.
Root-cause analysis (strong hypothesis)
- node-pty's pty console is sized exactly to the viewport —
mode conreports Lines == viewport (36). There is no scrollback reserve in the console buffer, unlike Windows Terminal/OpenConsole-style ConPTY hosts and conhost, which keep a ~9001-line buffer. - The TUI's full-frame repaints are frequently taller than 36 rows, so the console scrolls and discards the top rows mid-paint (we captured the pty write log; replaying it through a 36-row ConPTY reaches the terminal with only ~1/3 of the bytes — the rest is lost to buffer discard).
- The application keeps its own row bookkeeping and assumes scrolled-out rows are retained. After a discard the app's tracked baseline drifts from the terminal's actual rows, so subsequent small incremental frames erase/rewrite at shifted positions → residual + new glyphs overlap until the next full repaint (which itself discards again).
This points at the interaction between the scrollback-less ConPTY console and the terminal core. Orca uses the same stack shape (Electron + node-pty + xterm) with @xterm/xterm 6.1.0-beta.303 and a likewise 36-row buffer — and is clean. A local headless replay of the captured stream through a real 36-row ConPTY into @xterm/headless 5.4.0 vs 6.1.0-beta showed the models usually agree but occasionally diverge transiently by 17–35 rows on chunked streams (5.4.0 exhibits the artifact-prone states). Related upstream context: microsoft/terminal#15976 "ConPTY buffer gets out-of-sync".
Suggested fix
Upgrade the bundled terminal core: @xterm/xterm 5.4.0 → 6.x in tabby-terminal (with addon versions bumped in lockstep — Orca pairs xterm 6.1 with addon-webgl 0.20 / addon-fit 0.12 etc.). This is very likely to resolve the issue; a controlled build test with the upgraded xterm is the confirmation step.
Available debug material
Raw pty write logs captured from the failing session (PI_TUI_WRITE_LOG) and the headless replay harness are available on request.
Source: Eugeny/tabby