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.
resolve layout → call each plugin with its rect → table → nodes → paint
Five constraints keep it small, and each one is load-bearing:
-
Four node kinds —
text,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,debugand 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.
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
.
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 attachwith 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