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 backends (BackendRegistry)
   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
project  ← imports session only
agent    ← imports session only (NEVER ui, git, or project)
ui       ← imports session and project only (NEVER agent or git)
mcp      ← imports storage, session, project, sync, paths only
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. Session wraps SessionBackend . BackendRegistry manages multiple backends. ContainerManager and VmManager handle lifecycle.
session/ Plain data types: SessionId , SessionStatus , SessionInfo , SessionConfig , VmState , ContainerState . No logic beyond Display/Default.
project/ Plain data: ProjectId , ProjectConfig , ProjectInfo . Imports session only.
ui/ Pure rendering functions. layout.rs computes panel areas (responsive breakpoints). Widgets: project_list, terminal_view, info_panel, status_bar.
mcp/ MCP server ( thurbox-mcp binary). Exposes CRUD over stdio or Streamable HTTP JSON-RPC.

Session Pipeline #

A SessionBackend trait abstracts session lifecycle. The default is LocalTmuxBackend ( 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 .

Multi-Backend Architecture #

Sessions select their backend at creation time via a BackendRegistry . Each session stores its backend_type in the database.

Backend Transport Use Case
LocalTmuxBackend tmux -L thurbox Default, local sessions
DevcontainerBackend tmux over docker/podman exec Container isolation
QemuVmBackend tmux over SSH Full VM sandbox

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