"use client"; import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import "@xterm/xterm/css/xterm.css"; export interface XtermViewHandle { write: (data: string) => void; } const theme = { background: "#101215", foreground: "#edeef0", cursor: "#e9a857", selectionBackground: "#3f3f46", black: "#18181b", red: "#f87171", green: "#4ade80", yellow: "#facc15", blue: "#60a5fa", magenta: "#c084fc", cyan: "#22d3ee", white: "#e4e4e7", brightBlack: "#3f3f46", brightRed: "#fca5a5", brightGreen: "#86efac", brightYellow: "#fde047", brightBlue: "#93c5fd", brightMagenta: "#d8b4fe", brightCyan: "#67e8f9", brightWhite: "#f4f4f5", }; interface XtermViewProps { /** Written into the terminal as soon as it's created. Only read once, on mount. */ initialData?: string; } /** A read-only xterm.js instance: interprets ANSI colors/control codes from streamed script * output, unlike a plain
 which would just show raw escape characters.
 *
 * `initialData` is written inside the same effect that creates the Terminal - not via a
 * ref call from the parent after the fact - specifically because React's Strict Mode (dev
 * only) mounts, cleans up, and re-mounts every effect once: a fresh Terminal is created on
 * each real mount, so a "write once" flag in the parent would write to the first (disposed)
 * instance and silently skip the surviving one, leaving the terminal blank until the next
 * live write arrives.
 */
export const XtermView = forwardRef(
  function XtermView({ initialData }, ref) {
    const containerRef = useRef(null);
    const termRef = useRef(null);

    useEffect(() => {
      if (!containerRef.current) return;

      const term = new Terminal({
        convertEol: true,
        disableStdin: true,
        cursorBlink: false,
        fontSize: 12,
        lineHeight: 1.2,
        fontFamily: "ui-monospace, Menlo, monospace",
        theme,
      });
      const fitAddon = new FitAddon();
      term.loadAddon(fitAddon);
      term.open(containerRef.current);
      fitAddon.fit();
      if (initialData) term.write(initialData);
      termRef.current = term;

      const resizeObserver = new ResizeObserver(() => {
        try {
          fitAddon.fit();
        } catch {
          // container has zero size (e.g. mid-layout-shift) - next resize will retry
        }
      });
      resizeObserver.observe(containerRef.current);

      return () => {
        resizeObserver.disconnect();
        term.dispose();
        termRef.current = null;
      };
      // eslint-disable-next-line react-hooks/exhaustive-deps -- initialData is intentionally only used on mount
    }, []);

    useImperativeHandle(ref, () => ({
      write: (data: string) => termRef.current?.write(data),
    }));

    return (
      
); }, );