Skip to main content

Architecture

core.term is decomposed into seven layers. Each layer has a single responsibility and depends only on the layers beneath it, so you can drop in at the exact level of abstraction your program needs.

Design principles​

  1. Reification over magic. Side effects are values (Command, Subscription) interpreted by the runtime — update stays pure.
  2. Use the language. The framework is a consumer of core.async (channels, spawn, timers, cancellation) and core.sync (atomics, futex), not a re-implementation.
  3. Composable, not monolithic. Widgets are plain types with a render method; layouts are pure functions from Rect + children to List<Rect>.
  4. Protocols over inheritance. Widget, StatefulWidget, Styled, Shape are small protocols; composition replaces deep class hierarchies.
  5. Industrial-grade Unicode, always. Grapheme clusters are the unit of cursor navigation and display width everywhere, not UTF-8 bytes.

Layer 0 — Raw I/O​

Module: core.term.raw

FileResponsibility
termios.vrPOSIX termios state save/restore (@ffi("libc"))
wincon.vrWindows Console Host / VT enable (@ffi("kernel32"))
escape.vrEscapeWriter — CSI / OSC / DCS / SS3 helpers
screen.vrAlternate screen enter/leave
cursor.vrCursor shape + visibility
capabilities.vr$TERM/$COLORTERM/$TERM_PROGRAM → TermCapabilities
clipboard.vrOSC 52 set/get

The termios.vr libc declaration is a current implementation dependency; core/term/event/source.vr also declares libc poll. Host interpreter handlers may use libc, but generated AOT terminal code must follow the strict target boundary: direct syscalls on Linux and allowed libSystem entry points on macOS. The table is not evidence that a generated terminal program already conforms.

The raw layer never allocates and never reads terminal input — it only writes bytes. Reading is Layer 1's job.

Layer 1 — Events​

Module: core.term.event

A small pushdown-FSM input parser translates byte streams into structured events (Event.Key, Event.Mouse, Event.Resize, Event.Paste, Event.FocusGained, Event.FocusLost). Two front-ends exist:

  • EventStream — synchronous, blocking or timed poll.
  • AsyncEventStream — implements AsyncIterator; integrates with select! and async for.

Mouse events are parsed in SGR Extended (1006) format by default. The parser also understands X10, Xterm normal, and urxvt encodings.

Layer 2 — Style & color​

Module: core.term.style

Color is a sum of Base16 | Ansi256 | Rgb | Hsl | Lab. The ColorProfile of the target terminal is detected once at startup; adapt_color() lowers a Color to the terminal's best approximation using CIELAB perceptual distance rather than naive RGB-Euclidean distance, which preserves apparent brightness and hue on 16-color shells.

Themes are first-class: a Theme assigns semantic roles (surface, primary, accent, error, success, muted) rather than raw colors, so an application can switch dark↔light at runtime by replacing the theme rather than touching every widget.

Layer 3 — Rendering​

Module: core.term.render

The rendering engine is double-buffered. Each frame:

  1. The application renders widgets into next: Buffer.
  2. flush_diff(prev, next) emits the minimal escape sequences to transition the terminal from prev to next.
  3. Buffers swap; the previous next becomes prev.

The diff algorithm has three fast paths:

  • Row-level skip — a row equal in both buffers emits no bytes.
  • Natural advancement — consecutive cells in the same row need no cursor motion.
  • CUF/CUB collapse — any horizontal cursor jump becomes a single ESC [ n C / ESC [ n D.

The whole frame is wrapped in Mode 2026 synchronized output so the terminal either shows the complete new frame or the complete previous one — never a torn intermediate.

Layer 4 — Layout​

Module: core.term.layout

Three layout engines, each suited to a different problem:

  • Constraint — linear solver for splitting a rectangle by Length, Percentage, Ratio, Min, Max, Fill. Fastest.
  • Flex — full CSS Flexbox Level 1, including multi-line wrap, flex-grow, flex-shrink, and all justify-content / align-items / align-content variants.
  • Grid — CSS Grid Level 1 with fr units, minmax(), and auto tracks.

All three return List<Rect> — the engine of choice is an implementation detail that doesn't leak into the widgets.

Layer 5 — Widgets​

Module: core.term.widget

Every widget implements either Widget (stateless — pure function of (self, Rect, Buffer)) or StatefulWidget (with an associated State type). The library currently ships twenty widgets; see the widget index for the full list.

Layer 6 — App framework​

Module: core.term.app

The Elm Architecture: Model, update(msg) -> Command, view(frame), subscriptions() -> Subscription. The runtime interprets Command and Subscription as values — update is pure, all side effects are reified and spawned on the async runtime with structured cancellation.

See The Elm pattern in Verum for a full treatment.