Guides/Architecture notes

Architecture notes

A look under the hood: how input becomes messages, how the loop drains and renders, and the two renderer strategies.

Event loop

text
stdin bytes


TerminalInputDecoder
    │ (KeyPressMsg, WindowSizeMsg, BackgroundColorMsg, …)

Queue<Msg>

    ▼  drain all pending messages first
for msg in queue:
    model = model.update(msg)
    fire cmd (unawaited — result enqueues next message)

    ▼  render once per batch (FPS-throttled)
renderer.render(model.view())

Key properties: all pending messages are drained before each render, so rapid key presses never block each other. Commands are fire-and-forget; their result arrives as the next message. The FPS cap only throttles screen output, not message processing.

Renderers

RendererStrategyWhen to use
AnsiRenderer (default)Line-level diffMost terminals
CellRendererCell-level diff (per grapheme)Terminals without ?2026 sync

The cell-level diff renderer writes only changed cells, giving zero flicker. Synchronized updates (CSI ?2026) are used automatically where the terminal supports them.

Related