feat(app): UDS bridge (channel/invoke/emit) + xterm.js terminal, M0 e2e works
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { WebglAddon } from "@xterm/addon-webgl";
|
||||
import { attachSurface, detachSurface, sendInput, resizeSurface } from "./socketBridge";
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
export function TerminalView({ surfaceId }: { surfaceId: string }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const term = new Terminal({ fontFamily: "monospace", fontSize: 13, convertEol: false });
|
||||
try {
|
||||
term.loadAddon(new WebglAddon());
|
||||
} catch {
|
||||
// webgl unavailable → fall back to canvas/dom renderer silently
|
||||
}
|
||||
term.open(ref.current);
|
||||
|
||||
// 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);
|
||||
term.dispose();
|
||||
};
|
||||
}, [surfaceId]);
|
||||
|
||||
return <div ref={ref} style={{ width: "100%", height: "100%" }} />;
|
||||
}
|
||||
Reference in New Issue
Block a user