How I understand PTY and xterm.js

From terminal and shell to pseudoterminal and browser rendering.

When a terminal appears inside a browser, several distinct pieces can look like one thing. The useful mental model is to separate the program, the transport, and the renderer.

This note came from a small study repo: learn-xterm. I built it to learn how a browser terminal is connected, not as a product or library. The repo is useful mainly as a record of the experiment.

The short version

The shell is a program. A terminal is an interface. A PTY is the operating-system mechanism that lets a program behave as if it were connected to a terminal.

xterm.js does not run the shell. It interprets terminal control sequences and renders the result in the browser.

browser/xterm.js ↔ websocket ↔ server ↔ PTY ↔ shell

Why the PTY matters

Programs change their behavior when attached to a terminal. They may add colors, redraw a progress line, or enter an interactive mode. A plain pipe moves bytes, but it does not provide all the terminal semantics these programs expect.

The PTY provides a master and slave pair. The shell runs against the slave side. The server reads and writes the master side, then carries those bytes to the browser.

xterm.js is a stateful renderer

The bytes coming from a shell are not simply text. They may contain escape sequences that mean “move the cursor,” “clear this line,” or “use this color.” The renderer maintains screen state while interpreting that stream.

This distinction clarifies where bugs live:

  • process lifecycle and signals belong near the server and PTY;
  • latency and flow control belong in the transport;
  • cursor movement and display belong in the terminal emulator.

Once those boundaries are visible, a browser terminal stops feeling magical. It becomes a small pipeline with explicit responsibilities.