import { useEffect, useRef } from "react"; import { Terminal } from "@xterm/xterm"; import { WebglAddon } from "@xterm/addon-webgl"; import { SearchAddon } from "@xterm/addon-search"; import { attachSurface, detachSurface, sendInput, resizeSurface } from "./socketBridge"; import { registerSearch, unregisterSearch } from "./searchRegistry"; const decoder = new TextDecoder(); const encoder = new TextEncoder(); export function TerminalView({ surfaceId }: { surfaceId: string }) { const ref = useRef(null); useEffect(() => { if (!ref.current) return; const term = new Terminal({ fontFamily: "'JetBrains Mono Variable', 'JetBrains Mono', monospace", fontSize: 13, convertEol: false, scrollback: 10000 }); try { term.loadAddon(new WebglAddon()); } catch { // webgl unavailable → fall back to canvas/dom renderer silently } term.open(ref.current); const search = new SearchAddon(); term.loadAddon(search); registerSearch(surfaceId, search); // Input → daemon. const inputDisposable = term.onData((data) => { void sendInput(surfaceId, encoder.encode(data)); }); let disposed = false; // Attach: fresh xterm instance, write snapshot, then stream live output. void attachSurface(surfaceId, (bytes) => { if (!disposed) term.write(decoder.decode(bytes)); }).then((res) => { if (disposed) return; if (res.snapshot) term.write(res.snapshot); if (res.cols && res.rows) { term.resize(res.cols, res.rows); void resizeSurface(surfaceId, res.cols, res.rows); } }); return () => { disposed = true; inputDisposable.dispose(); void detachSurface(surfaceId); unregisterSearch(surfaceId); term.dispose(); }; }, [surfaceId]); return
; }