Revised Tech Stack: TUI + WASM Focus
Overview
Given the TUI-first, dual-target architecture (native terminal + WASM web), the tech stack shifts to support:
- Ratatui ecosystem for terminal UI
- WASM compatibility for web deployment
- Shared core between targets
- Async runtime that works in both environments
Core Framework
| Component | Choice | Rationale |
|---|---|---|
| TUI Framework | Ratatui | Mature, fast, WASM-compatible via ratzilla |
| Terminal Backend | crossterm | Cross-platform, async-ready |
| Web Backend | ratzilla | DOM-based rendering for WASM |
| Async Runtime | tokio (native) / wasm-bindgen-futures (web) | Best in class for each target |
| Error Handling | thiserror + anyhow | Ergonomic, works everywhere |
Target-Specific Stacks
Native Terminal Stack
┌──────────────────────────────────────┐
│ Native Application │
├──────────────────────────────────────┤
│ robit-tui │
│ • ratatui widgets │
│ • crossterm backend │
│ • tokio runtime │
├──────────────────────────────────────┤
│ robit-core (shared) │
│ • Business logic │
│ • LLM abstractions │
│ • Tool system │
├──────────────────────────────────────┤
│ Platform APIs │
│ • reqwest (HTTP) │
│ • sqlx (database) │
│ • directories (config) │
└──────────────────────────────────────┘
Key crates:
ratatui = "0.29"- TUI frameworkcrossterm = "0.28"- Terminal backendtokio = { version = "1", features = ["full"] }- Async runtimereqwest = { version = "0.12", features = ["json", "stream"] }- HTTP clientsqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }- Databasedirectories = "5"- Config/cache directories
WebAssembly Stack
┌──────────────────────────────────────┐
│ Web Application │
├──────────────────────────────────────┤
│ robit-web │
│ • ratzilla backend │
│ • ratatui widgets (same as native) │
│ • wasm-bindgen-futures │
├──────────────────────────────────────┤
│ robit-core (shared) │
│ • Same business logic │
│ • Conditional compilation for I/O │
├──────────────────────────────────────┤
│ Web APIs │
│ • web-sys (browser APIs) │
│ • gloo (ergonomic wrappers) │
│ • LocalStorage/IndexedDB │
└──────────────────────────────────────┘
Key crates:
ratzilla = "0.3"- DOM-based ratatui backendwasm-bindgen = "0.2"- Rust/JS interopwasm-bindgen-futures = "0.4"- Async in WASMweb-sys = "0.3"- Browser API bindingsgloo = "0.11"- Ergonomic web APIsjs-sys = "0.3"- JavaScript bindings
Shared Core Stack
Components that work in both environments via conditional compilation:
Serialization
serde = { version = "1.0", features = ["derive"] }- Serializationserde_json = "1.0"- JSON handlingtoml = "0.8"- Config files
Async Utilities
futures = "0.3"- Async utilitiesasync-trait = "0.1"- Async traitspin-project = "1"- Pin projections
LLM Integration
async-openai = "0.26"- OpenAI client (optional, native only)reqwestfor custom HTTP implementations- Custom trait system for multi-provider support
Text Processing
pulldown-cmark = "0.12"- Markdown parsingsyntect = "5"- Syntax highlighting (native)tree-sitter = "0.24"- Language parsing (optional)
Validation
validator = { version = "0.19", features = ["derive"] }- Input validationregex = "1"- Pattern matching
Workspace Structure
robit/
├── Cargo.toml # Workspace root
├── book.toml # Documentation
├── docs/ # mdbook documentation
├── crates/
│ ├── robit-core/ # Shared business logic
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── conversation.rs
│ │ ├── llm/
│ │ │ ├── mod.rs
│ │ │ ├── provider.rs
│ │ │ └── openai.rs
│ │ ├── tools/
│ │ │ ├── mod.rs
│ │ │ └── registry.rs
│ │ └── config.rs
│ │
│ ├── robit-tui/ # Native terminal app
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── main.rs
│ │ ├── app.rs
│ │ ├── ui/
│ │ │ ├── mod.rs
│ │ │ ├── conversation_view.rs
│ │ │ ├── input.rs
│ │ │ └── sidebar.rs
│ │ ├── event.rs
│ │ └── widgets/
│ │
│ ├── robit-web/ # WASM web app
│ │ ├── Cargo.toml
│ │ ├── index.html
│ │ └── src/
│ │ ├── main.rs
│ │ ├── app.rs
│ │ └── storage.rs
│ │
│ └── robit-cli/ # Command-line interface
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
│
├── examples/ # Usage examples
├── scripts/ # Build scripts
└── tests/ # Integration tests
Build Configuration
Native Build
# crates/robit-tui/Cargo.toml
[package]
name = "robit-tui"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "robit"
path = "src/main.rs"
[dependencies]
robit-core = { path = "../robit-core" }
# TUI
crossterm = "0.28"
ratatui = "0.29"
# Async
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
# HTTP
reqwest = { version = "0.12", features = ["json", "stream", "rustls-tls"] }
# Database
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
# Config
directories = "5"
config = "0.14"
# UI
syntect = "5" # Syntax highlighting
unicode-width = "0.1"
textwrap = "0.16"
# CLI
clap = { version = "4.5", features = ["derive"] }
WASM Build
# crates/robit-web/Cargo.toml
[package]
name = "robit-web"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
robit-core = { path = "../robit-core" }
# WebAssembly
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
web-sys = "0.3"
gloo = "0.11"
console_error_panic_hook = "0.1"
# TUI (same widgets as native)
ratzilla = "0.3"
ratatui = "0.29" # Shared version with native
# Async (browser-compatible)
wasm-bindgen-futures = "0.4"
futures = "0.3"
# Storage
gloo-storage = "0.3"
indexed_db_futures = "0.4" # Optional IndexedDB wrapper
[dependencies.web-sys]
version = "0.3"
features = [
"console",
"Window",
"Document",
"Element",
"HtmlElement",
"Storage",
"IdbDatabase", # IndexedDB
]
Conditional Compilation
The shared core uses cfg attributes for platform-specific code:
#![allow(unused)]
fn main() {
// crates/robit-core/src/storage.rs
#[cfg(not(target_arch = "wasm32"))]
mod native {
use sqlx::SqlitePool;
pub struct DatabaseStorage {
pool: SqlitePool,
}
impl Storage for DatabaseStorage {
// SQLite implementation
}
}
#[cfg(target_arch = "wasm32")]
mod web {
use gloo_storage::{LocalStorage, Storage};
pub struct WebStorage;
impl Storage for WebStorage {
// LocalStorage/IndexedDB implementation
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use native::DatabaseStorage as StorageImpl;
#[cfg(target_arch = "wasm32")]
pub use web::WebStorage as StorageImpl;
}
HTTP Client Abstraction
Different HTTP clients for each target:
#![allow(unused)]
fn main() {
// crates/robit-core/src/http.rs
#[cfg(not(target_arch = "wasm32"))]
pub type HttpClient = reqwest::Client;
#[cfg(target_arch = "wasm32"))]
pub type HttpClient = reqwest_wasm::Client; // Or custom fetch-based client
// Unified interface
#[async_trait]
pub trait HttpClientExt {
async fn get(&self, url: &str) -> Result<Response>;
async fn post(&self, url: &str, body: Value) -> Result<Response>;
}
}
Development Tools
Native Development
# Run TUI app
cargo run -p robit-tui
# With logging
RUST_LOG=debug cargo run -p robit-tui
# Release build
cargo build -p robit-tui --release
Web Development
# Install trunk
cargo install trunk
# Add WASM target
rustup target add wasm32-unknown-unknown
# Serve with hot reload
cd crates/robit-web && trunk serve
# Build for production
trunk build --release
Cross-compilation Testing
# Test native
cargo test --workspace --exclude robit-web
# Check WASM compiles
cargo check -p robit-web --target wasm32-unknown-unknown
# Run WASM tests with wasm-pack
wasm-pack test --headless --firefox
Comparison: Before vs After
Original (API-first)
Axum (HTTP) → Core → Database
↓
Sandboxed Executor
Revised (TUI + WASM)
┌─────────────────────────────────────────┐
│ Terminal (ratatui + crossterm) │
│ Web (ratzilla + wasm-bindgen) │
└─────────────────┬───────────────────────┘
│
┌────────▼────────┐
│ Shared Core │
│ • Conversations│
│ • LLM providers│
│ • Tools │
└────────┬────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌───▼───┐ ┌──────▼──────┐ ┌───▼───┐
│SQLite │ │HTTP Client │ │Config │
│(native)│ │(reqwest) │ │(dirs) │
└───────┘ └─────────────┘ └───────┘
┌─── Web Alternative ───┐
│LocalStorage/IndexedDB │
│fetch API │
└───────────────────────┘
Migration Path
If we later want to add the API/server approach:
- Extract server crate:
harness-serverwith Axum - Add client modes: TUI can connect to local or remote server
- Web app options: WASM (standalone) or web client (connects to server)
This keeps options open without over-engineering from the start.
Summary
The revised stack prioritizes:
- Developer experience: Ratatui provides excellent TUI ergonomics
- Code sharing: Single core works across targets
- Deployment flexibility: Native binary or static web hosting
- Performance: Native speed in terminal, near-native in browser
The WASM approach via ratzilla is particularly elegant because it renders the same ratatui widgets to DOM elements, giving us a true terminal aesthetic in the browser without maintaining separate UI code.