xterm.js 6, and the canvas renderer retired
Explore this workspace.
Technical background
The terminal renders through WebGL on xterm.js 6.0. The canvas renderer is gone — it was last released in April 2024 and xterm 6 deletes it outright — so terminal.frontend: xterm now means xterm's own DOM renderer, slow but always correct, and the fallback for a pane whose WebGL context could not be recovered.
The problem
Tabby's terminal.frontend: xterm meant @xterm/addon-canvas, last released in April 2024 against @xterm/xterm ^5.0.0 and deleted outright by xterm 6. It is also the renderer behind every stale-glyph report upstream has open: it repaints only the rows it believes are dirty, so anything it draws and then loses track of stays on screen until something forces a full repaint.
How it works
WebGL is what draws a pane. XTermFrontend now means xterm's own DOM renderer — slow but always correct — and is the fallback for the SwiftShader workaround and for a pane whose WebGL context could not be recovered. A saved frontend: xterm is aliased to WebGL rather than migrated: a fork-owned bump of config.version would make upstream's own migrations skip these configs at the next sync.
Details
- Four things break against 6.0, each checked against the shipped sources rather than the changelog:
overviewRulerWidthbecame an object;_core.viewport._refresh()is gone andqueueSync()replaces it;_core.browseris a module namespace whose properties are read-only getters, so it must be spread rather than assigned into; andscrollToBottom()gaineddisableSmoothScroll, without which every pinned write starts a scroll animation. - xterm 6 paints
.xterm-viewportblack and.xterm-scrollable-elementwhite, both on top of Tabby's background — measured, not theorised. Both are overridden. The scrollbar slider needs nothing: xterm derives it from the theme's foreground at 20/40/50% opacity, which already follows a light or dark scheme. - The synchronous row render after a fit stays. It is what closes the blank frame during a window drag, and
xterm.refresh()cannot replace it — that goes through the render debouncer and lands a frame later.
What this does not claim
- The stale-glyph artifacts that prompted this work are not reproduced by the test written to measure them.
tabby-terminal/test/glyphs.cdp.jsfills the scrollback past capacity, scrolls with real wheel events while output arrives, resizes mid-flow and diffs the renderer's own canvases against a forced full repaint — and reports 0 dirty cells on canvas, on WebGL and on xterm 6. Its instrumented repaint counter says why: under that generator a full-viewport repaint follows nearly every buffer scroll, so nothing can go stale. Retiring the canvas renderer is well-founded on its own; it is not measured to be the fix. @xterm/addon-unicode-graphemes@0.4.0cannot be put in this bundle at all, and that is still unexplained. Importing it intotabby-terminal— importing, not using — spins the renderer at 100% CPU during module evaluation, measured at 270s and climbing, and V8's inspector cannot interrupt it. Not the ESM entry, not babel, and not the addon itself, which loads standalone in 11–15 ms. This forced an earlier attempt at the xterm 6 upgrade to be reverted, and it is why emoji width is still broken here.