claw-code/docs/tui/TUI.md

11 KiB

TUI Research & Build Plan for Claw Code

Status: Draft | Date: 2026-06-12 | Branch: feat/tui

Executive Summary

This document synthesizes research on how similar tools build terminal UIs and proposes a phased plan for adding a first-class TUI to claw. The existing CLI already embeds crossterm, rustyline, pulldown-cmark, and syntect — giving us a strong foundation. The TUI should be a new mode (claw tui or claw --tui) rather than a replacement, sharing the ConversationRuntime, Session, and tool surfaces already in crates/runtime.

Prior Art: How Peers Build TUIs

1. OpenAI Codex CLI (codex-rs/tui)

The closest direct prior art — a Rust TUI for an AI coding assistant.

Component Crate / Pattern
Framework ratatui + crossterm
Event loop tokio-async with EventBroker and frame-rate limiter
Layout App (top-level state machine) → ChatWidget (history cells + in-flight streaming cell) → bottom_pane (composer/input)
Markdown pulldown-cmark
Syntax highlight syntect
Clipboard arboard
Testing insta snapshot testing

Key architectural decision: top-level state machine (App) that owns widget dispatch. Chat history is a scrollable list of "cells" (completed turns + one in-progress streaming cell). Input is a separate widget. This cleanly separates streaming concerns from input handling.

2. aichat (sigoden)

A popular all-in-one LLM CLI.

Component Crate / Pattern
REPL reedline (nushell line editor) + crossterm
Prompts inquire for interactive dialogs
Streaming render/ module with MarkdownRender, markdown_stream, raw_stream
Syntax highlight syntect
Color ansi_colours

Key insight: aichat shows that reedline is a stronger modern alternative to rustyline for rich REPLs, but the existing rustyline integration in claw can stay untouched if the TUI is a separate surface. The TUI should be a new surface, not a REPL replacement.

3. crush (liljencrantz)

An advanced Rust shell.

Component Crate / Pattern
REPL rustyline (with file history)
Terminal control termion
Layout unicode-width for proper text measurement

Key insight for claw: crush demonstrates shell/parser separation — the REPL is thin, and the heavy lifting (parsing, execution) lives in shared modules. This mirrors how claw should build its TUI: thin TUI shell, shared runtime core.

4. Other Rust TUI Patterns (from ecosystem research)

  • Pimalaya (himalaya mail client): TUI as an add-on to existing CLI using feature flags; core logic is model-driven, UI is view-only.
  • Vuls (security scanner): Terminal viewer uses vim-like keybindings; scan results shared between JSON/CLI/TUI outputs.
  • halp: Rust CLI tool with optional TUI mode via feature flags or runtime detection.
  • TUIfying guides (Jack Bisceglia, Isakdl): Show layering a TUI atop CLI logic without regressing headless usage — confirming the dual-surface strategy.
Layer Crate Rationale
TUI Framework ratatui De-facto standard; layout engine, widget system, backend abstraction over crossterm/termion. Already the choice of codex-rs.
Backend crossterm (already in use) Cross-platform; claw already depends on it.
Async bridge tokio (already in use) ratatui + crossterm + tokio via crossterm::event::EventStream for async event handling.
Input widget tui-textarea or tui-input Pre-built text input widget with scrolling, wrapping, key handling. tui-textarea is mature and widely used.
Markdown render pulldown-cmark → custom widget (already in use) claw already has render.rs using pulldown-cmark + syntect. Port/adapt to a ratatui::widgets::Widget.
Syntax highlight syntect (already in use) No change needed — wire existing HighlightLines into a ratatui Paragraph widget.
Clipboard arboard (optional) For "copy code block" or "copy response" — nice-to-have for MVP.

Crate not recommended:

  • tui-rs — deprecated, succeeded by ratatui.
  • termioncrossterm is already in the dependency tree; doubling backends adds pain.
  • reedline — great for REPL, but the TUI is a full-screen app; ratatui owns input.

Architecture: Proposed Design

Guiding Principles

  1. Non-breaking: claw prompt, claw --help, and the REPL stay exactly as they are.
  2. Shared core: The TUI consumes ConversationRuntime, Session, ToolExecutor — no duplication.
  3. Alt-screen only: TUI runs in the alternate screen buffer; exiting returns to the shell cleanly.
  4. Feature-flag friendly: Start as always-on dependency, gate behind tui feature if binary bloat becomes an issue.

Module Layout

rust/crates/rusty-claude-cli/src/
  main.rs              # existing — add `claw tui` dispatch
  cli.rs               # existing — add `Tui` to CliAction
  tui/
    mod.rs             # public entry: run(env) -> Result
    app.rs             # App: top-level state machine (Screen enum)
    event.rs           # EventBroker: bridges crossterm events → App events
    widgets/
      chat.rs          # ChatWidget: scrollable message list
      input.rs         # ComposerWidget: multi-line input + send
      sidebar.rs       # Optional: session list / tool status
    render.rs          # Markdown-to-ratatui Paragraph rendering (adapts existing render.rs)

State Machine: App

enum Screen {
    Chat {          // primary chat view
        messages: Vec<MessageCell>,
        scroll: ScrollState,
        input: InputState,
        streaming: Option<StreamingCell>,
    },
    SessionPicker,  // list saved sessions (nice-to-have)
    Help,           // keybindings overlay
    Quit,           // shutdown signal
}

Event Flow

┌─────────────┐     ┌─────────────────┐     ┌──────────┐
│ Crossterm   │────▶│ EventBroker     │────▶│ App      │
│ (keyboard)  │     │ (tokio stream)  │     │ (update) │
└─────────────┘     └─────────────────┘     └────┬─────┘
                                                  │
                                 ┌────────────────┘
                                 ▼
                         ┌───────────────┐
                         │ ratatui::Frame│────▶ terminal draw
                         └───────────────┘

Integration with Existing Runtime

The TUI reuses these existing types directly (no wrappers):

Existing Type TUI Usage
ConversationRuntime Drive turns; receive AssistantEvent / TurnProgressReporter
Session / SessionStore Persist and resume chat history
ToolExecutor / execute_tool Execute tool calls from streaming responses
render::TerminalRenderer Adapt to ratatui Paragraph / Text
input::LineEditor Not used; TUI has its own tui-textarea input

The runtime emits events via TurnProgressReporter — the TUI provides its own reporter that writes into a tokio::sync::mpsc channel consumed by the event loop.

Implementation Roadmap

Phase 0: Prep (this PR)

  • Create feat/tui branch ✓
  • Add ratatui, tui-textarea (and optionally crossterm with event-stream feature) to rusty-claude-cli/Cargo.toml
  • Ensure cargo check --workspace passes

Phase 1: Skeleton (MVP)

  1. tui/mod.rsrun_tui() entrypoint: init terminal (alternate screen, raw mode), run event loop, restore on exit.
  2. tui/event.rsEventBroker: crossterm::event::EventStreamAppEvent (Tick, Key, Resize, Backend(AssistantEvent)).
  3. tui/app.rs — Minimal App with one Screen::Chat. State: message list (mock data) + input field.
  4. tui/widgets/chat.rsChatWidget: renders Vec<MessageCell> as List or vertical Layout of Paragraphs.
  5. tui/widgets/input.rsComposerWidget: wraps tui_textarea::TextArea, handles Enter-to-send, Shift+Enter for newline.
  6. NavigationCtrl+C or q to quit; Ctrl+L to clear; arrow keys to scroll.
  7. CLI hook — Add claw tui command that calls run_tui().

Phase 2: Live Conversation

  1. Wire ConversationRuntime::run_turn() into the TUI event loop.
  2. Create a TuiProgressReporter implementing TurnProgressReporter that sends AppEvent::AssistantDelta into the channel.
  3. Render streaming assistant responses in real-time (append to StreamingCell, convert to MessageCell on finish).
  4. Render tool calls (spinner + result) inline in the chat.
  5. Session persistence: auto-save on exit, /resume integration.

Phase 3: Polish

  1. Markdown widget — Port render.rs (pulldown-cmark + syntect) to a ratatui widget with proper soft-wrap, code blocks, and inline formatting.
  2. Sidebar — Show session list or active tool status in a third pane.
  3. Search / filter — Slash command picker, message search.
  4. Copy / clipboardarboard integration for copying code blocks.
  5. Theme — Respect NO_COLOR, dark/light palette, user config.

Phase 4: Hardening

  1. Graceful degradation when terminal is too small.
  2. Mouse support (optional, via crossterm).
  3. cargo test — unit tests for widgets, snapshot tests for render output.
  4. Clippy clean, format check, docs.

Open Questions

  1. Should the TUI replace the REPL? No — keep both. The REPL is lightweight for quick queries; the TUI is immersive for long sessions.
  2. Should we add a tui feature flag? Start without one (simpler). Add if binary size becomes a concern.
  3. How to handle permission prompts in the TUI? Modal overlay (ratatui-popup or custom overlay widget) rather than spawning a subprocess.
  4. Should the TUI support multiple conversation tabs? Nice-to-have; defer to Phase 3+.

Sources