Architecture

The interface is a Lua plugin kernel #

thurbox boots a Rust kernel that renders whatever Lua plugins it finds under ui/. It owns no pane of its own: the session list, the agent terminal and the search strip are files you can edit, move, turn off or replace. See the interface for what that means in practice.

one frame
resolve layout → call each plugin with its rect → table → nodes → paint

Five constraints keep it small, and each one is load-bearing:

  • Four node kindstext, box, input, surface. Everything else (lists, gauges, panels) composes in Lua.
  • Layout resolves before render, so a plugin is told its own rect and can wrap, truncate and window against it.
  • Reads are snapshots, writes are commands. Lua never blocks or awaits, so no plugin — including one nobody has written yet — can freeze the interface on a slow git fetch or an unreachable host.
  • Capabilities are absent, not blocked. No filesystem, no network: io, os, debug and the loaders are withheld rather than stubbed, and a lint enforces it.
  • Anything touching the world runs on a worker — terminal attach, commands, diffs, metrics, git stats, repository reads, update checks, and programs a plugin asked for.

System chrome is not a pane. Help, settings and the theme picker are kernel-owned: they overlay the arrangement, capture input, and stay out of the focus ring. Plugins contribute data to them — declare a key and it appears in help; declare a setting and the settings modal grows a row.

The event loop #

One loop, one source of truth. It is demand-driven: it paints when something changed, not on every iteration, and slows its input poll once nothing has happened for a while.

main.rs
tokio::main → load config + settings
  → heal extensions → arm the tmux heartbeat
  → resolve ui/ → build the Lua host
  → open SQLite → init terminal
  → loop {
    resolve layout → call each plugin with its rect → paint
    → poll workers (terminals, commands, diffs, metrics, repos, runs)
    → dispatch events to subscribed plugins
    → drain Lua's command queue
    → dispatch keys through the registry
  }
  → restore terminal (sessions keep running in tmux)

Module Structure #

Dependencies flow strictly one-directionally. Enforced by tests/architecture_rules.rs .

dependency rules
session  ← pure data types, no project-local imports
agent    ← imports session (+ path/shell utils; NEVER git)
kernel   ← session + storage + sync + paths + session_ops + git
main     ← the coordinator: the loop, the workers, the chrome

Module responsibilities

Module Responsibility
kernel/ The interface: the four node primitives, layout, the Lua host and its capability grants, the key registry, the snapshot (reads) and command bus (writes), live terminal surfaces, and the worker-backed stores (diffs, metrics, repos, runs, updates).
coordinator/ main's own body: the loop and its workers, then commands, publish, draw, input, mouse, focus, events and the interface plumbing.
agent/ Side-effect layer. AgentProvider trait abstracts CLI command construction; GenericProvider launches any agent from its AgentDef . agent_config::load_or_seed reads agents.toml . Session wraps SessionBackend , backed by TmuxBackend over a TmuxTransport (local tmux -L thurbox , or ssh <dest> tmux for ssh:<host> backends).
session/ Plain data types: SessionId , SessionStatus , SessionInfo , SessionConfig , AgentDef / AgentRegistry (declarative agent definitions). No logic beyond pure helpers.
ui/ (Lua) Not Rust: layout.lua is the arrangement, lib/ holds widgets and theme roles, and plugins/ holds the panes.
cli/ Headless automation ( thurbox-cli binary). Session, automation, and editor subcommands sharing the TUI's SQLite database.

Session Pipeline #

A SessionBackend trait abstracts session lifecycle. The default is TmuxBackend over a local TmuxTransport ( tmux -L thurbox ). vt100::Parser interprets escape sequences, tui_term::PseudoTerminal renders into ratatui.

Backend trait methods

  • check_available — verify backend prerequisites
  • ensure_ready — initialize backend resources
  • spawn() — returns (backend_id, output_reader, input_writer)
  • adopt() — reconnect to existing session, returns initial screen content
  • discover() — list existing sessions for restore-on-startup
  • resize() — update terminal dimensions
  • detach() — stop streaming without killing session
  • kill() — permanently destroy session

tmux details

Control mode ( -C ) supports multiple concurrent client connections. Output arrives as %output notifications (octal-encoded), input is sent via send-keys -H (hex-encoded). Configuration on init includes remain-on-exit on , extended-keys on , and flow control via pause-after .

Session Backend #

Session lifecycle (spawn, adopt, resize, kill, detach, discover) is abstracted behind a SessionBackend trait. The implementation is TmuxBackend , which runs every session in a dedicated tmux -L thurbox server. The trait boundary keeps the app layer transport-agnostic.

TmuxBackend is itself transport-neutral via a TmuxTransport : sessions can run on a remote host over SSH while the TUI runs locally. The local backend launches tmux -L thurbox ; a remote one launches ssh <dest> tmux -L thurbox — the tmux control-mode protocol is byte-identical over either transport. Each host declared in hosts.toml registers a backend named ssh:<host> .

Async Runtime #

The app runs on tokio's multi-threaded runtime. PTY read loops run inside spawn_blocking (blocking I/O in a threadpool), while PTY write and event handling run in tokio::spawn (async).

PTY reads are blocking by nature. Putting them in spawn_blocking prevents stalling the async executor and freezing the UI.

Persistence #

All state is stored in SQLite at ~/.local/share/thurbox/thurbox.db . Multiple instances synchronize via PRAGMA data_version polling (250ms). SQLite WAL mode handles concurrent access.

Multi-instance sync

  • Database : Atomic transactions, no race conditions
  • Session I/O : Each instance independently connects to tmux control mode. Tmux broadcasts output to all clients.
  • Input : Commands serialized by tmux (same as tmux attach with multiple clients)

Core Principles #

Non-negotiable rules that define what Thurbox must always be. Each has an automated enforcement mechanism.

# Principle Enforcement
1 Crash-free operation Clippy + code review
2 Module isolation tests/architecture_rules.rs
3 Zero-warning policy clippy -D warnings + RUSTDOCFLAGS="-D warnings"
4 Permissive licenses only cargo-deny check bans licenses
5 Zero known vulnerabilities cargo-deny check advisories
6 Conventional commits cocogitto ( cog verify )
7 One rendering pattern Architecture tests + code review
8 Backend-first sessions Code review
9 Logging never touches stdout Code review
10 Test-driven development cargo-nextest
11 Deterministic CI Scripts over LLMs
12 Tag-based versioning build.rs + release workflow

Key technical details

  • MSRV: 1.75, Edition 2021
  • Async runtime: tokio (multi-threaded)
  • Terminal state: vt100::Parser + tui_term::PseudoTerminal
  • Logging: file-based at ~/.local/share/thurbox/thurbox.log
  • Panic hook restores terminal before printing
  • Requires tmux >= 3.2