#11667·tabby

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)

Author: hetiejunCreated Sep 9, 2026Updated Sep 9, 2026

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/xterm resolved to 5.4.0 in tabby-terminal (yarn.lock), node-pty 1.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

  1. Local shell, small viewport: mode con reports Lines: 36 (== viewport rows).
  2. Run the TUI; type / then 3+ filter letters (oooo), then Backspace a few times — each keystroke repaints the popup above the input line.
  3. 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) and xterm (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)

  1. node-pty's pty console is sized exactly to the viewportmode con reports 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.
  2. 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).
  3. 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.