Skip to content

perf(terminal): re-enable WebGL rendering by moving xterm to the beta line VS Code ships #871

Description

@Juliusolsson05

Motivation

The xterm WebGL renderer has been switched off since 2026-09-08 (WEBGL_RENDERER_ENABLED = false in src/renderer/src/workspace/terminal/xtermWebglRenderer.ts, commit fcf70d0a, PR #841). The pinned @xterm/addon-webgl@0.19.0 corrupts its texture atlas under provider-TUI workloads (garbled glyphs, mid-word substitutions, stale inverse blocks — #789, re-reported 2026-09-08). The upstream fix (xterm.js #5883, merged 2026-05-21) ships only in @xterm/addon-webgl@0.20.0-beta.219+, whose peer range requires @xterm/xterm ^6.1.0-beta.304. The kill switch's comment said to wait for a stable 0.20.0; as of 2026-09-11 npm still has only 0.19.0 stable.

Cost of staying on the DOM renderer (#768): xterm 6's DOM renderer rebuilds every dirty row's spans on each repaint, and Claude Code repaints the whole viewport inside ?2026 synchronized-output brackets, so nearly every row is dirty on every frame (40–60 rows × 10–40 spans). Every raw terminal surface pays it — agent terminal view mode, plain shells, OpenCode Terminal — multiplied by the number of visible lanes.

What changed the calculus: VS Code — the largest xterm.js consumer — depends on exactly this beta line on its main branch today: "@xterm/xterm": "^6.1.0-beta.304", "@xterm/addon-webgl": "^0.20.0-beta.300". xterm.js publishes betas continuously from master and VS Code ships them, so "don't put the core terminal on a beta" is a much weaker argument than it looked when the switch was added.

Intended behavior

  • Move the renderer-side xterm packages to the beta line VS Code ships, pinned EXACTLY (betas move daily; builds must be reproducible and upgrades deliberate):
    • @xterm/xterm 6.0.0 → 6.1.0-beta.304
    • @xterm/addon-webgl 0.19.0 → 0.20.0-beta.300
    • @xterm/addon-fit 0.11.0 → 0.12.0-beta.301 (its beta requires @xterm/xterm ^6.1.0-beta.304; keeps the add-ons on one release train)
  • WEBGL_RENDERER_ENABLED = true, and delete the invalidateTextureBindings atlas bridge that existed only to paper over 0.19.0 (the switch's own instructions).
  • @xterm/headless (main-process screen parsing, no renderer) is out of scope.

Acceptance criteria

  • The three packages are exact-pinned in package.json and resolved in package-lock.json; npm ci succeeds in CI.
  • WebGL attaches to xterm hosts again, with the existing context-loss → DOM fallback intact.
  • The 0.19.0 atlas bridge and its tests are removed; the renderer module's remaining tests cover attach, disabled, failure and context-loss paths.
  • npm run typecheck and CI quality-gate (incl. packaged-output verification) pass.
  • Manual soak in the running app before merge: heavy Claude Code / Codex terminal output shows no garbled or interleaved glyphs, no stale inverse blocks, and scrolling/resizing behave as before. If corruption appears: copy the garbled text — clean clipboard means renderer.
  • A follow-up note records moving to stable @xterm/xterm 6.1.0 / @xterm/addon-webgl 0.20.0 once released.

Refs #768, #789, #841.

Activity

  1. Juliusolsson05 commented on Sep 11, 2026

    @Juliusolsson05
    OwnerAuthor

    Implemented in #873 (branch perf/reenable-xterm-webgl): exact pins @xterm/xterm 6.1.0-beta.304, @xterm/addon-webgl 0.20.0-beta.300, @xterm/addon-fit 0.12.0-beta.301; WEBGL_RENDERER_ENABLED = true (kept as the one-line rollback); 0.19.0 atlas bridge deleted. Lockfile changes only the three xterm entries (npm's own rewrite also pruned vitest's nested esbuild — not shipped). typecheck + all xterm suites green locally. Merge is gated on a manual soak in the running app — see the PR.

  2. Juliusolsson05 commented on Sep 11, 2026

    @Juliusolsson05
    OwnerAuthor

    Blocked: the pinned core beta (6.1.0-beta.304) duplicates output and write callbacks when a terminal resizes mid-queue (upstream bf7c95b6 'Flush writes on resize', 2026-01-09 — predates the atlas fix, so every WebGL-fixed beta has it; not fixed upstream as of beta.304; sibling open issue xtermjs/xterm.js#6154). Repro and details on #873 (now draft). Also found: context-loss fallback needs a host refit before WebGL can ship on any version. Main is unaffected.

  3. Juliusolsson05 commented on Sep 11, 2026

    @Juliusolsson05
    OwnerAuthor

    Scope change (user-approved 2026-09-11): the beta core's resize() bug (duplicate/dropped writes; upstream xtermjs/xterm.js#6154) is now fixed locally. scripts/patch-xterm.mjs runs at postinstall and removes the flush from resize() in both bundles of the exact-pinned 6.1.0-beta.304 (6.0.0 resize semantics, maintainer-endorsed form), refusing any other version; xtermResizeFlushPatch.test.ts guards it. Also: refit on renderer change (context-loss clipping), Electron pixel-harness invariant fixed, review nits. Details and verification on #873.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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