Termio creates the terminal and stream handler

`Termio.init` creates the `terminal.Terminal`, lets the backend initialize terminal-specific state, and builds `StreamHandler` with references to termio, surface, renderer, size, and terminal state.

const handler: StreamHandler = .{
    .termio_mailbox = &self.mailbox,
    .surface_mailbox = opts.surface_mailbox,
    .renderer_state = opts.renderer_state,
    .renderer_wakeup = opts.renderer_wakeup,
    .renderer_mailbox = opts.renderer_mailbox,
    .size = &self.size,
    .terminal = &self.terminal,
};

self.* = .{
    .terminal = term,
    .terminal_stream = .initAlloc(alloc, handler),
};
The exec backend starts the subprocess on IO thread entry

`termio.Thread.threadMain_` calls `io.threadEnter`, which dispatches to the backend. For `Exec`, this starts the subprocess and sets up backend thread data before the IO loop begins processing mailbox wakeups.

try io.threadEnter(self, &cb.data);
defer cb.data.deinit();
defer io.threadExit(&cb.data);

try self.loop.run(.until_done);
Read threads feed raw bytes into Termio.processOutput

The POSIX read thread sets the PTY fd nonblocking, reads repeatedly until it would block, and calls `Termio.processOutput` for each buffer. The Windows path mirrors this using `ReadFile`.

while (true) {
    const n = posix.read(fd, &buf) catch |err| {
        switch (err) {
            error.WouldBlock => break,
            else => unreachable,
        }
    };

    @call(.always_inline, termio.Termio.processOutput, .{ io, buf[0..n] });
}
Termio locks renderer state and feeds the terminal stream

Output processing is protected by `renderer_state.mutex` because it mutates terminal state that the renderer reads. The handler schedules a render before parsing, resets cursor blink as needed, records inspector data in slow mode, and feeds either byte-by-byte or as a slice.

self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();

self.terminal_stream.handler.queueRender() catch unreachable;
self.terminal_stream.nextSlice(buf);
VT parser actions apply directly to terminal state

The stream handler receives parser actions and updates the terminal. The common path is intentionally branch-hinted around print, SGR attributes, cursor motion, line feed, and carriage return.

switch (action) {
    .print => {
        @branchHint(.likely);
        try self.terminal.print(value.cp);
    },
    .cursor_pos => self.terminal.setCursorPos(value.row, value.col),
    .set_attribute => self.terminal.setAttribute(value) catch |err| {
        log.warn("error setting attribute {}: {}", .{ value, err });
    },
    else => {},
}

Read next: Renderer follows how these mutations become a new drawn frame.