TERMIUM / DOCUMENTATION

Architecture

This page describes the current implementation. For planned changes, see installation and browser UI.

Browser and client

The TypeScript server controls a headless Chromium browser through Puppeteer. The Go client sends navigation, input, and viewport requests over gRPC and renders paced screenshots in the terminal.

flowchart LR
    User[Keyboard and mouse] --> Client[Go terminal client]
    Client -->|Control requests| Server[TypeScript gRPC server]
    Server -->|Puppeteer| Browser[Chromium]
    Browser -->|Screenshots| Server
    Server -->|Paced screenshots| Client
    Client --> Terminal[Terminal display]

The protocol lives in proto/bc.proto; Go and TypeScript bindings are generated during the build. Dialog events use a bidirectional stream.

Home page and website

site/index.html is the public product website template; site/welcome/index.html is the separate browser home page. The public website never links to the browser home page. scripts/build-extensions.mjs copies its HTML, CSS, and vector mark into the extension bundle and adds only the trusted local Vimium bootstrap. The default bootstrap checks https://termium.dev/welcome/ for a Termium marker before navigating to that ordinary web origin. A 1.5-second timeout, non-Termium response, or trusted input keeps the local page. Remote HTML is never injected into the extension origin.

Custom home pages open directly. The client persists the setting atomically, supports session/environment overrides, and passes it to its managed server. BrowserSession and Vimium use the same destination for native and extension tab commands. The Home command reuses the current tab. A positional URL overrides only the initial navigation.

Renderers

The client uses CaptureScreenshot to bound outstanding capture requests. A single worker decodes and prepares immutable frames, with one pending raw frame and one pending prepared frame; newer work replaces pending work. Local chrome damage is independent of image damage. Unchanged frames reuse the prepared image, while overlay closure and document/viewport changes explicitly invalidate its displayed placement. Capture pauses when local overlays cover the page.

The legacy screenshot stream remains available and stops capture after write(false) until the stream drains. Both server viewports and client frame dimensions are limited to 16,384 pixels per side and 16 megapixels total; screenshots are limited to 32 MiB, with a matching gRPC receive limit. Committed page content remains visible while subresources are loading. Failed unary captures can recover without restarting the client. Terminal output stays in the UI owner; encoding workers never write terminal escape sequences. Physical terminal writes can still block, so their measured duration also limits capture pacing.

Sixel bands are six pixels high. Caching at that granularity follows the image format, but effectiveness depends on page changes, palette selection, and viewport size. Earlier README timing figures were not a cross-platform benchmark and should not be used as release guarantees.

OPTIMUS.md contains historical performance investigations, including items that may already be implemented. Confirm its suggestions against the current code before treating them as active tasks.

Process and transport lifecycle

Normal clients create a private per-session socket and launch their own server. Release builds resolve the private Node and Chromium runtimes beside the installed executable; development builds use the contributor toolchain. An explicit TCP endpoint can connect to a separately managed server. It watches stdout for TERMIUM_READY. That sentinel means the gRPC listener is available; browser startup happens later when requested.

Captures have an independent CDP session that is detached on navigation or after a bounded timeout. The shared capture/resize queue waits for the command to reject before continuing; it does not abandon a live command in a timer race. Input and history use a different session.

The server applies desktop viewport metrics directly to the active Chromium target. It serializes capture and resize, and reapplies the viewport after target replacement. This avoids Puppeteer's additional touch-emulation operations, which hung after modal dialogs in native macOS tests.

Each normal client gets a private directory and Unix socket. A manually started server still defaults to /tmp/termium.sock, with optional --socket or TCP. Each server maintains Chromium tabs, one active selection, and one dialog stream; clients explicitly sharing a TCP server still share that state.

Shutdown attempts to close the browser, server, connection, and terminal screen. Terminal probes have bounded deadlines. The event loop alone draws or finalizes the screen; workers post events and publish immutable frames into a latest-frame slot. A single client worker orders navigation, keys, mouse transitions, and resize requests. Browser input carries a tab identity and session/document generation so queued input cannot act on a replacement page. Installed sessions hold shared version locks; the installer requires an exclusive lock before updating.

Code map

Location Responsibility
client/main.go Application lifecycle, terminal events, viewport, and rendering coordination
client/config.go Flags and renderer selection
client/server_launcher.go Server discovery and child process lifecycle
client/keyboard.go, client/navigation_ui.go, client/text_editor.go Keyboard routing, navigation bar, menu, and Unicode editing
client/mouse.go, client/input_dispatcher.go Mouse capture, keyboard pointer, and ordered input
client/ui_layout.go, client/framebuffer.go Shared viewport geometry and immutable frame handoff
client/frame_pipeline.go Bounded preparation, deduplication, Sixel encoding, and capture pacing
client/kitty_renderer.go Kitty graphics encoding
client/sixel_bands.go, client/sixel_band_encoder.go Sixel band processing and caching
client/dialog.go, client/dialog_stream.go Browser and local dialogs
server/src/server.ts, server/src/browser-controls.ts Puppeteer lifecycle, history/loading state, ordered input, and gRPC handlers
server/src/browser-session.ts, server/src/vimium.ts Real tab identity/selection, per-tab controllers, bundled extension loading and readiness
client/tabs.go, third_party/vimium Tab strip/picker and pinned upstream keyboard navigation
proto/bc.proto Client/server protocol

Documentation home

This guide describes the current development build. Edit on GitHub ↗