Live terminals
Nothing yet — run a test on the left, or open an example.
// The explorer frontend — a Vite app. Terminals are real xterm.js instances // with the full addon set (Unicode 11 widths so emoji align exactly like // Sigil lays them out, clickable links, OSC 52 clipboard, sixel images, and // WebGL + fit on the dedicated terminal view). import { ClipboardAddon } from "@xterm/addon-clipboard"; import { FitAddon } from "@xterm/addon-fit"; import { ImageAddon } from "@xterm/addon-image"; import { Unicode11Addon } from "@xterm/addon-unicode11"; import { WebLinksAddon } from "@xterm/addon-web-links"; import { WebglAddon } from "@xterm/addon-webgl"; import { Terminal } from "@xterm/xterm"; import "@xterm/xterm/css/xterm.css"; import "./style.css"; type Config = { mode: "explorer" | "terminal"; title: string; columns: number; rows: number; tests: boolean; entries: Array<{ id: string; title: string; group: string }>; }; type SerializedTask = { id: string; name: string; type: "suite" | "test"; state: string | undefined; duration: number | undefined; errors: string[]; tasks: SerializedTask[]; }; const root = document.getElementById("root")!; const createTerminal = ( columns: number, rows: number, { webgl = false }: { webgl?: boolean } = {}, ): Terminal => { const terminal = new Terminal({ cols: columns, rows, fontFamily: "monospace", allowProposedApi: true, linkHandler: { activate: (_event, uri) => { window.open(uri, "_blank"); }, }, }); terminal.loadAddon(new Unicode11Addon()); terminal.unicode.activeVersion = "11"; terminal.loadAddon(new WebLinksAddon((_event, uri) => window.open(uri, "_blank"))); terminal.loadAddon(new ClipboardAddon()); terminal.loadAddon(new ImageAddon()); if (webgl) { try { terminal.loadAddon(new WebglAddon()); } catch { // WebGL unavailable — the DOM renderer takes over. } } return terminal; }; const terminalText = (terminal: Terminal): string => { const buffer = terminal.buffer.active; const lines: string[] = []; for (let y = 0; y < terminal.rows; y++) { lines.push( buffer .getLine(buffer.viewportY + y) ?.translateToString(true) .trimEnd() ?? "", ); } return lines.join("\n").replace(/\n+$/, ""); }; // ── Terminal view: one app, full window, fit + resize ─────────────────────── const terminalView = (config: Config, appId: string): void => { const entry = config.entries.find((candidate) => candidate.id === appId); const title = entry ? `${config.title} — ${entry.title}` : config.title; document.title = title; root.innerHTML = `
`; root.querySelector("header span")!.textContent = title; const terminal = createTerminal(config.columns, config.rows, { webgl: true }); const fit = new FitAddon(); terminal.loadAddon(fit); terminal.open(root.querySelectorNothing yet — run a test on the left, or open an example.