claw-code/docs/tui/TUI.md

197 lines
11 KiB
Markdown

# 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.
## Crate Ecosystem: Recommended Stack
| 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`.
- `termion``crossterm` 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
```text
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`
```rust
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.rs`** — `run_tui()` entrypoint: init terminal (alternate screen, raw mode), run event loop, restore on exit.
2. **`tui/event.rs`** — `EventBroker`: `crossterm::event::EventStream``AppEvent` (`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.rs`** — `ChatWidget`: renders `Vec<MessageCell>` as `List` or vertical `Layout` of `Paragraph`s.
5. **`tui/widgets/input.rs`** — `ComposerWidget`: wraps `tui_textarea::TextArea`, handles Enter-to-send, Shift+Enter for newline.
6. **Navigation**`Ctrl+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 / clipboard**`arboard` 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
- [OpenAI Codex CLI (codex-rs/tui)](https://github.com/openai/codex/tree/main/codex-rs/tui) — primary prior art
- [sigoden/aichat](https://github.com/sigoden/aichat) — REPL + streaming patterns
- [liljencrantz/crush](https://github.com/liljencrantz/crush) — shell/parser separation
- [Ratatui](https://github.com/ratatui/ratatui) — TUI framework
- [Pimalaya blog: Designing a TUI](https://pimalaya.org/blog/designing-a-tui-to-preview-markdown-files/) — architectural separation
- [Jack Bisceglia: TUIfying Rust CLIs](https://dev.jackbisceglia.com/posts/tuify/) — layering TUI on CLI
- [Isakdl: How I TUI-fy my Rust CLIs](https://www.isakdl.com/TUIfy) — practical guide
- [halp](https://halp.cli.rs/) — optional TUI mode
- [Vuls](https://vuls.io/en/) — terminal viewer patterns