The Zellij Question
What is Zellij?
Zellij is a modern terminal workspace (multiplexer) with built-in WASM plugin support. Unlike tmux or screen, Zellij treats plugins as first-class citizens alongside terminal panes.
┌──────────────────────────────────────────────────────┐
│ Zellij Session │
├──────────────────────────────────────────────────────┤
│ Tabs: [Main] [Code] [Logs] [Plugins] │
├──────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ Terminal │ │ Terminal │ │ WASM Plugin │ │
│ │ (nvim) │ │ (shell) │ │ ┌────────┐ │ │
│ │ │ │ │ │ │ Custom │ │ │
│ │ │ │ │ │ │ UI │ │ │
│ └─────────────┘ └─────────────┘ │ └────────┘ │ │
│ │ ↑ │ │
│ │ Zellij │ │
│ │ Plugin API│ │
│ └────────────┘ │
└──────────────────────────────────────────────────────┘
Zellij’s WASM Architecture
Zellij’s plugin system is sophisticated:
#![allow(unused)]
fn main() {
// Zellij plugin runs in WASM runtime with specific APIs
#[derive(Default)]
struct State {
users: Vec<User>,
}
register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self, configuration: BTreeMap<String, String>) {
// Plugin loaded by Zellij
request_permission(&[PermissionType::ReadApplicationState]);
}
fn update(&mut self, event: Event) -> bool {
// Handle Zellij events
match event {
Event::Key(key) => { /* handle keypress */ }
Event::TabUpdate(tabs) => { /* tabs changed */ }
_ => {}
}
true // should render
}
fn render(&mut self, rows: usize, cols: usize) {
// Render to the pane using print! macros
println!("Line to display in pane");
}
}
}
Key characteristics:
- Plugins are WASM modules loaded by Zellij
- Can render UI within a pane using text/ANSI
- Access to Zellij state (tabs, panes, sessions)
- Can control Zellij (open panes, run commands, etc.)
- Written in Rust (or any WASM language)
The Integration Question
Should robit be:
A. A standalone TUI application (current plan) B. A Zellij plugin C. Both
Option A: Standalone TUI
# User experience
$ robit
# Full-screen TUI starts
# User interacts with LLM
# Quit returns to shell
Pros:
- Works in any terminal (no Zellij dependency)
- Full control over terminal (alternative screen buffer)
- Can spawn subprocesses freely
- Simpler distribution (single binary)
- Can still detect Zellij and integrate
Cons:
- Another context switch (exit editor → run harness → return)
- Not integrated with existing Zellij workflow
Option B: Zellij Plugin
# In Zellij session
# Open plugin pane
$ zellij action new-pane --plugin harness
# Or bind to key: Ctrl+g → open harness
Pros:
- Lives within existing Zellij workflow
- Can interact with terminal panes (read output, send input)
- Great for “analyze this error” or “explain this code” workflows
- No context switching
- Zellij handles pane management
Cons:
- Only works for Zellij users (excludes tmux/screen users)
- Plugin API limitations (pane-sized, no full terminal control)
- Can’t easily run standalone
- Web deployment would be separate codebase
Option C: Hybrid Approach
┌─────────────────────────────────────────────────────────┐
│ Standalone App │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ robit TUI │ │
│ │ Full terminal control, works everywhere │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌───────────────┐ ┌──────────────────┐
│ WASM Web App │ │ Zellij Plugin│ │ CLI Interface │
│ (ratzilla) │ │ (optional) │ │ (commands) │
│ │ │ │ │ │
│ Browser-based │ │ Pane-based │ │ Scripts/pipes │
│ access │ │ access │ │ │
└─────────────────┘ └───────────────┘ └──────────────────┘
Pros:
- Maximum flexibility
- Users choose their interface
- Code reuse across implementations
Cons:
- More complexity
- Multiple build targets
- Testing matrix expands
Recommended Approach: Standalone First, Plugin Later
Why Standalone First?
- Universal compatibility: Works for all terminal users, not just Zellij
- Simpler initial design: Focus on core features, not plugin APIs
- Faster iteration: No need to reload Zellij to test changes
- Distribution: Single binary via cargo install
- Web deployment: WASM path is clearer for standalone apps
Future Zellij Integration
After standalone is mature, consider a Zellij plugin wrapper:
#![allow(unused)]
fn main() {
// crates/harness-zellij-plugin/src/main.rs
// Wrapper that embeds harness-core
// Provides Zellij-specific UI (pane-sized)
// Communicates with standalone daemon or runs standalone
use harness_core::{App, Config};
use zellij_tile::prelude::*;
struct HarnessPlugin {
app: App,
}
impl ZellijPlugin for HarnessPlugin {
fn render(&mut self, rows: usize, cols: usize) {
// Adapt TUI rendering to pane size
let view = self.app.render_compact(rows, cols);
for line in view.lines {
println!("{}", line);
}
}
}
}
Integration options:
-
Embedded mode: Plugin runs full harness-core in WASM
- Pros: Self-contained
- Cons: Large WASM bundle, no persistence
-
Daemon mode: Plugin connects to running harness daemon
- Pros: Shared state with standalone app
- Cons: Requires daemon to be running
-
Hybrid: Lightweight plugin that launches standalone in new pane
- Pros: Full features, minimal code
- Cons: Context switch (but within Zellij)
When Zellij Integration Makes Sense
Consider prioritizing Zellij if:
- Majority of users are already Zellij users
- Deep integration needed (manipulate panes, read terminal output)
- Enterprise deployment where Zellij is standardized
- Plugin ecosystem is a core value proposition
For robit, standalone-first is better because:
- Target audience: Developers using various terminal setups
- Use case: Primary interface, not helper utility
- Deployment: Web accessibility is important (WASM path)
- Complexity: Full TUI needs full terminal control
Detecting and Cooperating with Zellij
Even as a standalone app, we can integrate with Zellij:
#![allow(unused)]
fn main() {
// Detect if running inside Zellij
fn in_zellij() -> bool {
std::env::var("ZELLIJ").is_ok()
}
// Zellij-specific behaviors
if in_zellij() {
// Open new pane for tool output
// Share session state via Zellij's environment
// Use Zellij's key bindings awareness
}
}
Useful integrations:
- Open LLM conversation in new Zellij pane
- Send code from editor pane to harness
- Copy harness output to clipboard pane
- Use Zellij’s session persistence
Implementation Phases
Phase 1: Standalone (Months 1-3)
- Full TUI with ratatui
- Native terminal experience
- WASM web deployment
- Core features (conversations, tools, LLM)
Phase 2: Zellij Awareness (Month 4)
- Detect Zellij environment
- Basic integration (pane awareness)
- Documentation for Zellij users
Phase 3: Zellij Plugin (Month 6+)
- Optional plugin crate
- Embedded or daemon mode
- Publish to Zellij plugin registry
Conclusion
Don’t build for Zellij exclusively, but design with Zellij in mind:
- Keep core logic separate from UI
- Make UI adaptable to different container sizes
- Support environment detection
- Document integration possibilities
The standalone-first approach gives us:
- Broader reach (all terminal users)
- Clearer WASM deployment path
- Simpler initial implementation
- Option to add Zellij later without rewrite
Zellij is an excellent terminal workspace, but robit should be an excellent LLM tool first—regardless of which multiplexer (if any) the user prefers.