import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import { WebglAddon } from "@xterm/addon-webgl"; import { LigaturesAddon } from "@xterm/addon-ligatures"; import "@xterm/xterm/css/xterm.css"; import "@fontsource/jetbrains-mono/400.css"; import "@fontsource/jetbrains-mono/500.css"; import "@fontsource/jetbrains-mono/700.css"; import { invoke } from "@tauri-apps/api/core"; import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; // Debug-Instrumentierung: Fehler auf der Seite anzeigen und ins Dev-Log spiegeln. function showError(msg: string) { const el = document.createElement("pre"); el.style.cssText = "color:#f38ba8;padding:16px;font:12px Menlo,monospace;white-space:pre-wrap"; el.textContent = msg; document.body.appendChild(el); invoke("term_log", { msg }); } window.addEventListener("error", (e) => showError(`error: ${e.message}\n${e.error?.stack ?? ""}`), ); window.addEventListener("unhandledrejection", (e) => showError(`rejection: ${e.reason}\n${e.reason?.stack ?? ""}`), ); const project = new URLSearchParams(location.search).get("project")!; const win = getCurrentWebviewWindow(); document.getElementById("project-name")!.textContent = project; // Catppuccin Mocha const theme = { background: "#1e1e2e", foreground: "#cdd6f4", cursor: "#f5e0dc", cursorAccent: "#1e1e2e", selectionBackground: "#585b7080", black: "#45475a", red: "#f38ba8", green: "#a6e3a1", yellow: "#f9e2af", blue: "#89b4fa", magenta: "#f5c2e7", cyan: "#94e2d5", white: "#bac2de", brightBlack: "#585b70", brightRed: "#f38ba8", brightGreen: "#a6e3a1", brightYellow: "#f9e2af", brightBlue: "#89b4fa", brightMagenta: "#f5c2e7", brightCyan: "#94e2d5", brightWhite: "#a6adc8", }; const term = new Terminal({ // Ligatur-Addon nutzt die Character-Joiner-API (Proposed API) allowProposedApi: true, fontFamily: '"JetBrains Mono", Menlo, monospace', fontSize: 13, fontWeightBold: "600", lineHeight: 1.25, letterSpacing: 0, cursorBlink: true, cursorStyle: "bar", scrollback: 10000, theme, }); const fit = new FitAddon(); term.loadAddon(fit); // Shift+Enter als CSI-u-Sequenz senden (Zeilenumbruch in Claude Code, // auch bei nicht-leerer Zeile). Neben keydown muss auch keypress unter- // drückt werden, sonst sendet xterm.js zusätzlich \r und submittet damit. term.attachCustomKeyEventHandler((e) => { if (e.key === "Enter" && e.shiftKey) { if (e.type === "keydown") invoke("term_write", { data: "\x1b[13;2u" }); return false; } return true; }); // Font muss geladen sein, bevor WebGL den Glyphen-Atlas aufbaut. await document.fonts.load('13px "JetBrains Mono"'); term.open(document.getElementById("term")!); // Reihenfolge laut Addon-Doku: Ligaturen vor WebGL aktivieren. term.loadAddon(new LigaturesAddon()); term.loadAddon(new WebglAddon()); fit.fit(); function b64ToBytes(b64: string): Uint8Array { const bin = atob(b64); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return bytes; } await win.listen("pty-output", (e) => term.write(b64ToBytes(e.payload))); await win.listen("pty-exit", () => term.write("\r\n\x1b[90m[Prozess beendet]\x1b[0m\r\n")); term.onData((data) => invoke("term_write", { data })); term.onResize(({ rows, cols }) => invoke("term_resize", { rows, cols })); window.addEventListener("resize", () => fit.fit()); await invoke("term_start", { project, rows: term.rows, cols: term.cols }); term.focus(); // Pool-Chip im Titelbalken interface Project { name: string; pool: string | null; } const projects = await invoke("list_projects"); const pool = projects.find((p) => p.name === project)?.pool; if (pool) document.getElementById("pool")!.textContent = pool;