fix: instant terminal switching + flicker-free rendering - #24
Conversation
Replace destroy-and-recreate pattern with persistent session views.
All terminal instances stay alive in memory; switching sessions just
toggles container visibility. Eliminates violent redraw/flashing.
- SessionViewManager: renders all live sessions, toggles display
- SessionPane: extensible view container per session (terminal now,
VNC/graphics views in the future)
- useTerminal: takes explicit sessionId param for multi-instance use
- Remove TerminalView (replaced by SessionPane)
- Remove key={sessionId} from App.tsx (was forcing remounts)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix early-return bug that unmounted all terminals when no session selected - Remove dead mountedRef code - Wrap SessionPane in React.memo to prevent re-renders on 5s poll - Guard FitAddon.fit() against zero dimensions in hidden containers - Lazy-load WebGL addon on first visibility (prevents context exhaustion) - Upgrade xterm.js 5→6 for native DEC 2026 synchronized output support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| // Lazily load WebGL only when the terminal becomes visible | ||
| if (!webglLoaded) { | ||
| try { | ||
| terminal.loadAddon(new WebglAddon()); |
There was a problem hiding this comment.
🟢 Suggestion
Problem: webglLoaded is a closure-local flag, so once a hidden terminal becomes visible and loads WebGL, that context lives for the lifetime of the terminal instance. With many sessions open and the user having visited each at least once, you can accumulate N WebGL contexts.
Why it matters: Browsers cap WebGL contexts at ~16 (Chrome) or ~8 (Safari). Exceeding this silently falls back to software rendering — which is benign but invisible to the user, and the console.warn here only fires during the initial load attempt, not on the silent eviction.
Suggested fix: Track active WebGL addons and dispose() on hidden terminals, keeping only the visible one GPU-accelerated:
// In useTerminal, after loading WebGL:
const webglAddonRef = useRef<WebglAddon | null>(null);
// When becoming visible (ResizeObserver, offsetWidth > 0):
if (!webglAddonRef.current) {
try {
const addon = new WebglAddon();
terminal.loadAddon(addon);
webglAddonRef.current = addon;
} catch (err) { /* fall through to canvas */ }
}
// When becoming hidden (visibility toggle on SessionPane):
// webglAddonRef.current?.dispose(); webglAddonRef.current = null;Not blocking for the typical 2–5 session use case, but worth addressing before supporting double-digit session counts.
There was a problem hiding this comment.
Good call — implemented. WebGL addon is now disposed when a terminal becomes hidden (ResizeObserver detects 0×0) and reloaded when it becomes visible again. Only the active terminal holds a GPU context at any time.
nox-0x
left a comment
There was a problem hiding this comment.
Clean implementation of the VSCode-style keep-alive pattern — the architecture (SessionViewManager → SessionPane → useTerminal) is well-structured and the guards (zero-dim fit skip, lazy WebGL, isActiveRef for multi-instance status) are all correct. Left one suggestion about WebGL context accumulation across many sessions (not blocking). xterm v6 synchronized output support is a nice bonus that directly addresses the per-update flicker.
Only the visible terminal holds a GPU context — WebGL addon is disposed when a terminal becomes hidden and reloaded when it becomes visible. Resolves merge conflicts with main. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nox-0x
left a comment
There was a problem hiding this comment.
Previous suggestion (WebGL context accumulation) is fully addressed — disposing on hidden + reloading on visible is the right pattern. One minor nit: sessionId !== null is stricter than the old !sessionId guard; if the store ever initialises sessionId as undefined the placeholder would not render, but in practice this is fine if the type is string | null. Architecture is clean, the ResizeObserver-driven WebGL lifecycle is correct, and the xterm v6 synchronized output support is a solid bonus. Ship it. ✅
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Architecture
display: flexReact.memoon SessionPane prevents re-renders from 5s session pollTest plan
🤖 Generated with Claude Code