Architecture

The Elm Architecture (TEA) #

All state lives in a single App model. Events become messages, update() applies them, view() renders the result.

event loop
Event → Message → update(model, msg) → view(model) → Frame

TEA makes state transitions explicit and testable. Every input has a traceable path from event to screen change. There's no hidden state scattered across components.

Event loop

main.rs
tokio::main → init backend (TmuxBackend)
  → open SQLite DB
  → init terminal
  → spawn/restore sessions
  → loop {
    draw frame → poll crossterm events (10ms)
    → convert to AppMessage
    → app.update()
    → app.tick()
  }
  → app.shutdown() (detach sessions)
  → restore terminal

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 only (NEVER ui or git)
ui       ← imports session only (NEVER agent or git)
app      ← coordinator, imports all modules

Module responsibilities

Module Responsibility
app/ Model ( App struct), Update ( AppMessage + handlers), View. Owns all state, coordinates side effects.
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/ Pure rendering functions. layout.rs computes panel areas (responsive breakpoints). Widgets: project_list, terminal_view, info_panel, status_bar.
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 TEA as single 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