Runtime Internals
View SourceTermUI.Runtime is the integrated TermUI 1.0 application loop. It is a single
GenServer with a single Elm root.
Initialization
start_link/1 preserves IEx detection, removes the optional GenServer name,
and starts the process. init/1 then:
- Merges
TermUI.Configvalues with runtime options; runtime options win. - Selects and initializes Raw, TTY, or an explicit custom backend.
- Starts
TermUI.Command.Executor. - Calls
root.init(opts)with the merged option list after the GenServer-only:nameoption has been removed. - Selects the input path and registers resize handling where available.
- Creates bounded event/message queues and the
:rootcomponent entry. - Executes startup commands and schedules the first render.
skip_terminal: true is intended for tests. It skips backend/input/buffer
initialization while leaving the Elm loop usable through public event APIs.
State
The fields are defined by TermUI.Runtime.State. Important ones include:
root_moduleandroot_stateevent_queueandmessage_queuecomponents, currently%{root: %{module: ..., state: ...}}dirtyandrender_interval- backend, backend state, capabilities, dimensions, and buffer manager
- input handler/reader processes
- command executor and pending command metadata
- shutdown and logger-restoration state
The map is not the TermUI.ComponentRegistry; the two APIs are independent.
Event and message flow
Events enter through send_event/2 or an input reader. The bounded event queue
processes one event per pass to avoid starving the GenServer mailbox. Key,
mouse, paste, resize, focus, and tick events all reach the root because it is
the only registered runtime component.
event_to_msg/2 may return {:msg, message}, :ignore, or :propagate.
There is no parent in the 1.0 runtime, so :propagate is currently ignored.
Messages are flushed from TermUI.MessageQueue, passed to root.update/2,
normalized by TermUI.Elm.normalize_update_result/2, and written back to both
root_state and the :root entry. A changed state marks the runtime dirty.
send_message(runtime, :root, message) bypasses event conversion; other IDs
are ignored because they are not registered.
If the root exports handle_info/2, otherwise-unhandled process messages are
forwarded to it. It may return a new state or {new_state, commands}.
Commands
The runtime accepts TermUI.Command structs. It also retains compatibility
with :quit, {:timer, milliseconds, message}, and
{:send, pid, message}. Prefer the struct constructors in new code.
TermUI.Command.Executor implements timers, intervals, asynchronous file
reads, delayed component messages, and timeouts. Results are enqueued as root
messages. Command.quit/1 takes precedence over other commands in the batch.
Rendering
A timer fires every render_interval milliseconds (16 by default). If dirty
is false, no view or output work occurs. Otherwise the runtime calls
root.view/1, rasterizes it, writes cells through the backend, flushes, and
clears the dirty flag.
force_render/1 marks the state dirty and immediately invokes the same render
path. It does not use TermUI.Renderer.FramerateLimiter.
Error boundaries
Exceptions from root event_to_msg/2, update/2, and view/1 are logged and
the runtime keeps its previous usable state. The command executor isolates
asynchronous task failures and reports them as result messages.
Shutdown
Shutdown stops accepting command results, cancels root commands, stops input,
shuts down the backend and buffer manager, removes resize callbacks, restores
terminal and logger state, and exits normally. The runtime does not call a
root terminate/2 callback; applications needing root cleanup should arrange
it outside that callback contract.
Next: Rendering Pipeline.