feat(electrobun): add xterm.js terminal with image addon (Sixel/iTerm2)

- Terminal.svelte component with @xterm/xterm + Canvas + Fit + Image addons
- Catppuccin Mocha terminal theme matching main app
- Sixel, iTerm2 inline image protocol support via xterm-addon-image
- ResizeObserver for responsive terminal sizing
- Demo cargo test output in terminal section below agent messages
This commit is contained in:
Hibryda 2026-03-20 01:40:24 +01:00
parent b79fbf688e
commit f97ea95373
10 changed files with 241 additions and 23 deletions

View file

@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte App</title>
<script type="module" crossorigin src="/assets/index-C2tlpXVI.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-xWFAud6t.css">
<script type="module" crossorigin src="/assets/index-CxMdDiN5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dim5hfvk.css">
</head>
<body>
<div id="app"></div>

View file

@ -5,6 +5,10 @@
"": {
"name": "electrobun-svelte",
"dependencies": {
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-image": "^0.9.0",
"@xterm/xterm": "^6.0.0",
"electrobun": "latest",
},
"devDependencies": {
@ -148,6 +152,14 @@
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"@xterm/addon-canvas": ["@xterm/addon-canvas@0.7.0", "", { "peerDependencies": { "@xterm/xterm": "^5.0.0" } }, "sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw=="],
"@xterm/addon-fit": ["@xterm/addon-fit@0.11.0", "", {}, "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g=="],
"@xterm/addon-image": ["@xterm/addon-image@0.9.0", "", {}, "sha512-oYWA8/QAr5/Emwl1xL7WCoOqeG3IZfpzEz/OVq1j4Oi9934TQmHiyubClikRf0D/jL3JNiNuz/Lsqx0kXQ02BA=="],
"@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],

View file

@ -11,6 +11,10 @@
"build:canary": "vite build && electrobun build --env=canary"
},
"dependencies": {
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-image": "^0.9.0",
"@xterm/xterm": "^6.0.0",
"electrobun": "latest"
},
"devDependencies": {

View file

@ -1,4 +1,6 @@
<script lang="ts">
import Terminal from './Terminal.svelte';
// ── Types ────────────────────────────────────────────────────
type AgentStatus = 'running' | 'idle' | 'stalled';
type MsgRole = 'user' | 'assistant' | 'tool-call' | 'tool-result';
@ -165,16 +167,22 @@
role="tabpanel"
aria-label="Model"
>
{#each project.messages as msg (msg.id)}
<div class="msg">
<span class="msg-role {msg.role.split('-')[0]}">{msg.role}</span>
<div
class="msg-body"
class:tool-call={msg.role === 'tool-call'}
class:tool-result={msg.role === 'tool-result'}
>{msg.content}</div>
</div>
{/each}
<div class="agent-messages">
{#each project.messages as msg (msg.id)}
<div class="msg">
<span class="msg-role {msg.role.split('-')[0]}">{msg.role}</span>
<div
class="msg-body"
class:tool-call={msg.role === 'tool-call'}
class:tool-result={msg.role === 'tool-result'}
>{msg.content}</div>
</div>
{/each}
</div>
<!-- Terminal section -->
<div class="terminal-section">
<Terminal />
</div>
</div>
<!-- Docs tab placeholder -->

View file

@ -0,0 +1,97 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { Terminal } from '@xterm/xterm';
import { CanvasAddon } from '@xterm/addon-canvas';
import { FitAddon } from '@xterm/addon-fit';
import { ImageAddon } from '@xterm/addon-image';
// Catppuccin Mocha terminal theme
const THEME = {
background: '#1e1e2e',
foreground: '#cdd6f4',
cursor: '#f5e0dc',
cursorAccent: '#1e1e2e',
selectionBackground: '#585b7066',
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',
};
let termEl: HTMLDivElement;
let term: Terminal;
let fitAddon: FitAddon;
onMount(() => {
term = new Terminal({
theme: THEME,
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
fontSize: 13,
cursorBlink: true,
allowProposedApi: true,
scrollback: 5000,
});
fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.loadAddon(new CanvasAddon());
// Image addon — enables Sixel, iTerm2, and Kitty inline image protocols
term.loadAddon(new ImageAddon({
enableSizeReports: true,
sixelSupport: true,
sixelScrolling: true,
sixelPaletteLimit: 4096,
showPlaceholder: true,
}));
term.open(termEl);
fitAddon.fit();
// Demo content with ANSI colors
term.writeln('\x1b[1;34m~/code/ai/agent-orchestrator\x1b[0m \x1b[32m$\x1b[0m cargo test --workspace');
term.writeln(' \x1b[1;32mCompiling\x1b[0m agor-core v0.1.0');
term.writeln(' \x1b[1;32mCompiling\x1b[0m agor-gpui v0.1.0');
term.writeln(' \x1b[1;32mRunning\x1b[0m tests/unit.rs');
term.writeln('test result: ok. \x1b[32m47 passed\x1b[0m; 0 failed');
term.writeln('');
term.writeln('\x1b[1;34m~/code/ai/agent-orchestrator\x1b[0m \x1b[32m$\x1b[0m \x1b[5m▊\x1b[0m');
// Resize on window resize
const ro = new ResizeObserver(() => fitAddon.fit());
ro.observe(termEl);
return () => ro.disconnect();
});
onDestroy(() => {
term?.dispose();
});
</script>
<div class="terminal-container" bind:this={termEl}></div>
<style>
.terminal-container {
width: 100%;
height: 100%;
min-height: 10rem;
}
/* xterm.js base styles */
:global(.xterm) {
padding: 0.5rem;
}
</style>

View file

@ -401,3 +401,20 @@ html, body {
color: var(--ctp-text);
font-weight: 500;
}
/* ── Terminal section ─────────────────────────────────────── */
.agent-messages {
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.terminal-section {
height: 12rem;
min-height: 8rem;
border-top: 1px solid var(--ctp-surface0);
flex-shrink: 0;
}

View file

@ -1,4 +1,5 @@
import "./app.css";
import "@xterm/xterm/css/xterm.css";
import App from "./App.svelte";
import { mount } from "svelte";