Libraries
ExTC
activeA terminal emulator written in Elixir, rendered in the browser by a Phoenix LiveView component.
What it is
ExTC is a terminal emulator written in Elixir, plus a Phoenix LiveView component that renders it. The emulator runs on the server; the browser receives server-rendered HTML, and LiveView's diff engine does the drawing. There is no npm package, no bundler step, no canvas and no WebGL.
Why it exists
Browser terminals today run the emulator in the browser, usually by way of xterm.js. That is a reasonable design, and it comes with costs that follow from where the emulator lives. Moving the emulator to the server changes several of them from features into consequences:
- Reloading the page leaves the terminal where it was; sessions outlive their viewers.
- Two people watching is just attaching twice, each viewer with its own role.
- A program writing very fast is throttled rather than having output discarded.
- Searching old output covers the whole history held on the server, not only what is in browser memory.
- Shell integration produces structured command blocks with text, exit status and duration.
- Testing a TUI gets a headless assertion API,
ExTC.Test, such asassert_screen(t, ~r/Main Menu/).
The counterweight is latency: every keystroke makes a round trip. The specification covers a predictive-echo approach of the kind mosh uses, but it is not built yet.
Example
Start a session and render it:
{:ok, _} = ExTC.start_session("console", backend: {ExTC.Backend.Pty, cmd: "/bin/bash"})
<ExTC.LiveTerminal.terminal id="console" session="console" role={:read_write} />
Then register ExTC's colocated hooks in app.js beside the application's own. That is the whole integration. mix extc.demo opens a terminal in a browser with no asset build.
How it works
ExTC.Vt is a pure functional core: a struct and feed(vt, bytes) -> {vt, effects}, with no processes, I/O or dependencies. That choice is why a snapshot is a single term_to_binary, why replay is a fold, and why the parser can be model-checked and differentially tested without scaffolding.
Around it sits a thin OTP shell (an emulator process, a backend process, a scrollback table) and a LiveView component that streams rows keyed by line id. A scroll is one insert and a prune rather than a repaint, and the cursor is an overlay, so moving it changes no row.
Two claims in the specification, one about the parser and one about credit-based flow control, quantify over every reachable state, so they are model-checked with ExTLA rather than only tested. The flow-control model refuted one candidate design before any of it was written.
What works today
- A VT500 parser with the CSI/ESC/OSC/DCS catalog, truecolour SGR, DEC and ANSI modes, the alternate screen, ISO-2022 charsets, wide and combining characters, and scrollback with reflow.
- Backends for a real pty (via a small Zig port program), SSH over OTP's own
:sshwith no native code, a plainPortwith signal-based throttling, and an echo double. - Server-side key encoding including the Kitty protocol, mouse protocols, and themes as CSS custom properties.
- Selection anchored to line ids, search across history, snapshots, reattachment, and exporters to text, ANSI, HTML and SVG.
Not yet: OSC 8 links, recording and replay, images, automatic shell-integration injection, and the conformance suites.
Status
ExTC is under active development and has not been published to Hex. The emulator, sessions, backends and LiveView component work and are covered by the test suite, but the public API is pre-release. MIT licensed.