You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(terminal): one agent-pty-data dispatcher per window and the WebGL renderer for xterm hosts #768
Landed:#783: the keyed per-window dispatcher and WebGL with a DOM fallback (later moved to the beta line in #873).
Remaining:TerminalHostCore was never extracted. Scrollback is still 2000 everywhere; 500 for unfocused panes depends on #760.
Acceptance: The remaining items land, or they are dropped as not worth it.
Original report
Symptom
Each xterm host registers its own ipcRenderer.on('session:agent-pty-data') for the whole channel and filters sid !== sessionId in JS (AgentTerminalLeaf.tsx:259-260, TerminalLeaf, AgentInlineTerminal; preload session.ts:225); the preload's own header (session.ts:39-43) says to subscribe once per event type. N mounted panes → every PTY chunk delivered to N listeners, N−1 discard it; 984k chunks in the two-day run; agent-pty-data is uncoalesced (one webContents.send per chunk, forwarder.ts:108-113).
No @xterm/addon-webgl / addon-canvas anywhere (ls node_modules/@xterm → addon-fit, headless, xterm): xterm 6's DOM renderer rebuilds every dirty row's spans with replaceChildren (DomRenderer.ts:441-460,520-525), and Claude Code repaints the whole viewport inside ?2026 brackets, so nearly every row is dirty on every repaint (40–60 rows × 10–40 spans). The GPU process already exists (8–16% CPU); WebGL adds no process.
@xterm/addon-webgl 0.19.0 (released with xterm 6.0.0) loaded in the shared host lifecycle with context-loss → DOM fallback; extract the shared lifecycle of the three hosts into a non-React TerminalHostCore so this is one change in one place.
Renderer scrollback 2000 → 500 for non-focused panes once the registry exists.
A live Agent Code run with one visibly unusable Claude raw terminal had 57 direct children and about 148% aggregate child CPU.
14 Claude processes accounted for about 38% CPU, while Electron helpers accounted for about 54% CPU. Two renderer processes alone were repeatedly in the 13–18% range.
The current raw-terminal hosts still each subscribe to the global session:agent-pty-data / session:terminal-data channels and discard other session ids in their own callbacks, despite the preload contract saying those event types should be subscribed once per renderer.
The full-screen repaint pattern therefore multiplies callback delivery by mounted terminal count before the owning xterm uses the DOM renderer to rebuild dirty rows.
I am implementing this issue on perf/claude-terminal-throughput: one renderer-side keyed dispatcher per raw PTY channel, plus a shared WebGL-addon lifecycle with context-loss fallback for every xterm host. Regression tests will protect keyed delivery, listener cardinality, teardown, late async addon loading, and fallback.
PR #783 implements keyed per-window PTY dispatch and lazy WebGL with safe DOM fallback across agent, shell and inline hosts. Full CI passed for implementation commit 4557b72; the documentation-only follow-up is rerunning checks. Real Electron context-loss/input smoke passed. This is a contribution, not closure: broader host-core/persistent-registry acceptance criteria remain open. No measured live key-to-echo gain is claimed until a controlled app restart.
Triage (W2, 2026-09-27): closing the remaining items as not worth it, which the audit's acceptance allows. Evidence is from origin/main.
Already shared. Every piece this issue asked to share is now its own module, used by all three xterm hosts (AgentTerminalLeaf, TerminalLeaf, AgentInlineTerminal):
terminal/sessionDataDispatcher (subscribeToAgentPtyData / subscribeToTerminalData): one subscription per window;
TerminalLeaf uses convertEol: true, the shell scrollback default, and the pane toast wiring;
AgentInlineTerminal uses a different font size and no PTY size ownership.
A shared core would either expose all of that as options or move the fragile sizing/attach logic behind a new abstraction. That is a large regression surface for no remaining perf gain: the per-chunk fan-out and the DOM renderer were the costs, and both are fixed.
Scrollback 500 for unfocused panes depends on the per-session registry, so it moves to #760 (noted there).
Claim marked done. Reopen if someone wants the extraction for its own sake.
Activity
Juliusolsson05 commentedon Sep 5, 2026
Implementation evidence from 2026-09-04:
I am implementing this issue on perf/claude-terminal-throughput: one renderer-side keyed dispatcher per raw PTY channel, plus a shared WebGL-addon lifecycle with context-loss fallback for every xterm host. Regression tests will protect keyed delivery, listener cardinality, teardown, late async addon loading, and fallback.
Juliusolsson05 commentedon Sep 5, 2026
PR #783 implements keyed per-window PTY dispatch and lazy WebGL with safe DOM fallback across agent, shell and inline hosts. Full CI passed for implementation commit 4557b72; the documentation-only follow-up is rerunning checks. Real Electron context-loss/input smoke passed. This is a contribution, not closure: broader host-core/persistent-registry acceptance criteria remain open. No measured live key-to-echo gain is claimed until a controlled app restart.
Juliusolsson05 commentedon Sep 27, 2026
Triage (W2, 2026-09-27): closing the remaining items as not worth it, which the audit's acceptance allows. Evidence is from origin/main.
Already shared. Every piece this issue asked to share is now its own module, used by all three xterm hosts (
AgentTerminalLeaf,TerminalLeaf,AgentInlineTerminal):terminal/sessionDataDispatcher(subscribeToAgentPtyData/subscribeToTerminalData): one subscription per window;terminal/xtermWebglRenderer(attachXtermWebglRenderer): WebGL with context-loss → DOM fallback, now on the beta line (perf(terminal): re-enable WebGL rendering on the xterm beta line #873);terminal/terminalWheelBoundary;tile-tree/terminalInputForwarder.TerminalHostCoreis dropped. What is left per host is ~60 lines of construct/fit/observe/dispose, and the hosts genuinely diverge in it:convertEol: true, the shell scrollback default, and the pane toast wiring;A shared core would either expose all of that as options or move the fragile sizing/attach logic behind a new abstraction. That is a large regression surface for no remaining perf gain: the per-chunk fan-out and the DOM renderer were the costs, and both are fixed.
Scrollback 500 for unfocused panes depends on the per-session registry, so it moves to #760 (noted there).
Claim marked done. Reopen if someone wants the extraction for its own sake.