Web Package - rahulpandita/react-term GitHub Wiki

The @next_term/web package provides the browser-side terminal runtime: the WebTerminal orchestrator, Web Worker communication bridges, WebGL2 and Canvas 2D renderers, input handling, accessibility, and the addon system.

WebTerminal

WebTerminal is the main entry point. It owns all subsystems and exposes a unified API to framework layers.

const term = new WebTerminal({
  cols: 80,
  rows: 24,
  fontSize: 14,
  fontFamily: 'monospace',
  fontWeight: 400,
  fontWeightBold: 700,
  theme: myTheme,
  useWorker: true,
  onData: (data) => socket.send(data),
  onResize: (cols, rows) => pty.resize(cols, rows),
  onTitleChange: (title) => (document.title = title),
});

term.attach(containerElement);
term.write(data);
term.loadAddon(new FitAddon());
term.dispose();

WebTerminal maintains two Buffer instances (normal + alternate screen), passes both SABs to WorkerBridge on init, and manages a scrollback viewport and a scrollbar overlay (1.5 s auto-hide).

Write Data Flow

The following sequence shows how data written to WebTerminal.write() reaches the canvas.

sequenceDiagram
  participant App
  participant WT as WebTerminal
  participant WB as WorkerBridge
  participant PW as Parser Worker
  participant SAB as SharedArrayBuffer
  participant RW as Render Worker

  App->>WT: write(data)
  WT->>WB: write(data)
  WB->>WB: check watermark
  WB->>PW: postMessage(chunk)
  PW->>PW: VTParser.parse()
  PW->>SAB: Atomics.store (cells + dirty)
  PW-->>WB: FlushMessage
  WB-->>WT: onFlush()
  WT->>RW: update(cursor, selection)
  RW->>RW: rAF → drawFrame

WorkerBridge

WorkerBridge manages the parser worker lifecycle and implements write-side flow control.

  • High watermark (2 MB pending bytes): incoming write() calls are queued once the parser worker is busy.
  • Low watermark (512 KB): the write queue drains when the worker signals readiness via FlushMessage.
  • SAB mode: the worker writes directly to the SharedArrayBuffer; only a FlushMessage (with an isAlternate flag) is sent back to trigger a render.
  • Non-SAB fallback: cell data and dirty rows are transferred as Transferable objects in the flush message and applied to the main-thread grid via applyFlush.

RenderBridge

RenderBridge manages the OffscreenCanvas render worker. It is used when both SharedArrayBuffer and OffscreenCanvas are available (Full Worker strategy). canUseOffscreenCanvas() performs the feature detection.

Messages sent to the render worker: init, update (cursor + selection), resize, theme, font, dispose. The render worker posts frame (FPS stats) and error messages back.

Canvas2DRenderer

Canvas2DRenderer implements IRenderer using the CanvasRenderingContext2D API. It is the universal fallback and runs on the main thread in Parser Worker strategy.

  • Renders only dirty rows per frame.
  • Wide characters: isSpacerCell() skips the right-half cell; isWide() doubles the render width via cellWidth * 2.
  • Supports fontWeight (normal) and fontWeightBold for fine-grained weight control.

WebGLRenderer

WebGLRenderer is the high-performance renderer using WebGL2 instanced rendering.

  • 2 draw calls per frame: one for background rectangles, one for foreground glyphs.
  • Alpha-only glyph atlas: glyphs are rasterized lazily into an OffscreenCanvas-backed texture, keyed by (codepoint, bold, italic).
  • Wide character support: spacer cells are skipped; the background instance for a wide character spans 2 × cellWidth; the glyph instance is rendered at double width.
  • hexToFloat4: accepts any CSS color expression (#rrggbb, #rgb, rgb(), rgba(), hsl(), oklch(), named colors) via a 1×1 OffscreenCanvas color-parsing trick that works inside workers.
  • createRenderer('auto' | 'webgl' | 'canvas2d'): factory that returns a WebGLRenderer or Canvas2DRenderer.

SharedWebGLContext

SharedWebGLContext enables multiple terminal panes to share a single WebGL2 context, working around Chrome's hard limit of 16 simultaneous contexts.

  • preserveDrawingBuffer: true keeps each pane's canvas content after compositing.
  • Per-terminal dirty-row caches prevent redundant redraws; at 16 panes this allows ~120 fps.
  • Wide character support mirrors the standalone WebGLRenderer: spacer cells skipped, background instances at 2× width.
  • Software renderer detection: if WebGL2 is software-backed, SharedWebGLContext throws to trigger automatic Canvas 2D fallback.

InputHandler

InputHandler translates DOM keyboard and pointer events into VT sequences.

  • Kitty keyboard protocol (modes 1/2/4/8/16): CSI u format, event types, alt/shifted base keys, all-key reporting, associated text.
  • Mouse reporting: X10, Normal, Button, Any event modes.
  • Touch input: touch-to-pointer translation.
  • Paste: bracketed paste wrapping.

AccessibilityManager

AccessibilityManager maintains a parallel DOM that screen readers can traverse. See Accessibility for details.

Addons

Addons are loaded via WebTerminal.loadAddon(addon).

SearchAddon

Provides regex text search across the terminal viewport and scrollback.

import { SearchAddon } from '`@next_term/web`';

const search = new SearchAddon();
term.loadAddon(search);

search.findNext('error', { regex: true, caseSensitive: false });
search.findPrevious('error');
search.clearDecorations();

WebLinksAddon

Detects URLs in terminal output and makes them clickable.

import { WebLinksAddon } from '`@next_term/web`';

term.loadAddon(new WebLinksAddon((event, uri) => {
  window.open(uri, '_blank');
}));

FitAddon

Automatically resizes the terminal columns and rows to fill its container.

import { FitAddon } from '`@next_term/web`';

const fit = new FitAddon();
term.loadAddon(fit);

// Call after the container is mounted or the window is resized
fit.fit();