Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Why Standalone First?

  1. Universal compatibility: Works for all terminal users, not just Zellij
  2. Simpler initial design: Focus on core features, not plugin APIs
  3. Faster iteration: No need to reload Zellij to test changes
  4. Distribution: Single binary via cargo install
  5. 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:

  1. Embedded mode: Plugin runs full harness-core in WASM

    • Pros: Self-contained
    • Cons: Large WASM bundle, no persistence
  2. Daemon mode: Plugin connects to running harness daemon

    • Pros: Shared state with standalone app
    • Cons: Requires daemon to be running
  3. 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:

  1. Target audience: Developers using various terminal setups
  2. Use case: Primary interface, not helper utility
  3. Deployment: Web accessibility is important (WASM path)
  4. 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.