TUI 52-Week Delivery Sprint Plan — Extreme Detail
Repository: ultraworkers/claw-code
Branch: feat/tui → main
Status: Draft for upstream PR review
Date: 2026-06-12
Authors: TheArchitectit, Claude
1. Executive Summary & Prior Failure Analysis
1.1 The Monolith Is Bigger Than We Thought
The rusty-claude-cli/src/main.rs file is 11,282 lines (as of 2026-06-12), not the 3,159 lines documented in the March 2026 plan. It contains:
LiveCli struct and its impl block (~4,500 lines)
- All slash command handlers (~2,800 lines)
- All
format_* report functions (~1,200 lines)
- Session management (create, resume, list, switch, persist) (~1,100 lines)
- Streaming output handling (~800 lines)
- Tool call display and permission prompting (~600 lines)
- Arg parsing and CLI dispatch (~282 lines)
This means every prior TUI attempt was trying to build on quicksand. The monolith grew while we were planning.
1.2 Post-Mortem of Every Prior Attempt
| Branch |
Period |
What Happened |
Root Cause |
feat/ui-hardening |
Earliest |
Patched REPL reliability inside monolith. Produced commit a13b1c2 but was absorbed into fix/ui-parity. |
Hardening a monolith is a treadmill — every fix adds more code to the same file. |
fix/ui-parity |
Apr 2026 |
Scoped DOWN from real TUI. Commit message: "Rejected: full multi-pane typeahead overlay — too large for this UI-only parity slice." Ported slash command completion and hints. |
Correctly identified extraction was needed first, but extraction was deferred and never done. |
feat/uiux-redesign |
Mar–Apr 2026 |
Big swing: vim mode, LSP integration, git slash commands, HTTP server. 18 commits were README credit wars (oh-my-opencode add/remove/re-add). |
Credit bikeshedding consumed more history than feature work. The branch never merged cleanly. |
rcc/ui-polish |
Mar 2026 |
Empty branch. No code pushed. |
Created, then abandoned when feat/uiux-redesign looked like it would subsume it. Waiting became permanent. |
rust/TUI-ENHANCEMENT-PLAN.md |
Mar 31 2026 |
Excellent 6-phase plan identifying monolith as #1 risk. Proposed Phase 0 extraction. |
Was a plan without a branch. No owner, no timeline, no commits. Phase 0 was labeled "essential" but no one signed up. |
1.3 The Ten Failure Modes & Our Guardrails
| # |
Failure Mode |
Guardrail |
| 1 |
The Monolith Trap |
Weeks 1–6 are exclusively extraction. No TUI code until main.rs <200 lines. |
| 2 |
Scope Panic |
Every slice ≤400 lines. The phrase "too large for this slice" triggers an immediate scope cut. |
| 3 |
Credit Wars |
No README edits during TUI work. Attribution is via Co-Authored-By commit trailers only. |
| 4 |
Placeholder Branches |
Every branch has ≥1 passing test and ≥1 visible change within 24 hours of creation. |
| 5 |
Plan Without Execution |
Each week has a concrete deliverable, a merge point, and a rollback strategy. |
| 6 |
Feature Flags Before Function |
No --tui feature flag until Week 20. Build it first; gate it later. |
| 7 |
REPL Replacement Fear |
TUI is claw tui, a separate entrypoint. REPL is untouched forever. |
| 8 |
Streaming-Not-First |
First real conversation feature is streaming rendering. Static screens come later. |
| 9 |
Dependency Bloat Fear |
Only 3 new crates: ratatui, tui-textarea, unicode-width. No optional deps until Week 20. |
| 10 |
No Test Coverage for UI |
Every widget has #[cfg(test)] with TestBackend assertions. No widget ships without one. |
1.4 Confirmation From Peer Projects
Research into codex-rs/tui, aichat, crush, and ecosystem guides confirms:
ratatui + crossterm is the standard Rust TUI stack (codex-rs uses this).
- Extraction-first is mandatory —
codex-rs/tui has app.rs, chat_widget.rs, composer.rs, event_broker.rs as separate modules.
- Thin TUI shell, shared core —
crush separates shell/parser; we separate TUI/ConversationRuntime.
- Streaming-first —
aichat renders markdown incrementally from SSE deltas.
2. Architecture Target State
2.1 Module Structure (End of Week 6)
rust/crates/rusty-claude-cli/src/
├── main.rs # Entrypoint only. Calls LiveCli::run() or tui::run().
│ # Target: <150 lines.
│
├── cli.rs # CLI argument parsing + CliAction enum.
│ # Target: <300 lines.
│
├── app.rs # LiveCli struct, REPL loop, turn execution.
│ # Moves from main.rs. Target: <800 lines.
│
├── format.rs # All report formatting functions.
│ # Moves from main.rs. Target: <600 lines.
│
├── session_mgr.rs # Session CRUD: create, resume, list, switch, persist, compact.
│ # Moves from main.rs. Target: <500 lines.
│
├── init.rs # Repo initialization (unchanged).
│
├── input.rs # Line editor with rustyline (unchanged, minor extensions).
│
├── render.rs # TerminalRenderer, Spinner, MarkdownStreamState.
│ # Extended but mostly unchanged. Target: <700 lines.
│
├── setup_wizard.rs # Interactive provider wizard (already exists, extracted in recent commits).
│
└── tui/ # NEW: All TUI components.
├── mod.rs # Public entry: run(env) -> io::Result<()>
│ # Terminal init (alternate screen, raw mode),
│ # event loop runner, cleanup on exit.
│
├── app.rs # App state machine.
│ # Screen enum, message state, input state, theme.
│ # Target: <400 lines.
│
├── event.rs # EventBroker.
│ # Bridges crossterm::event::EventStream → AppEvent.
│ # Target: <250 lines.
│
├── theme.rs # Color themes, terminal capability detection.
│ # Target: <200 lines.
│
└── widgets/
├── chat.rs # ChatWidget: scrollable message list.
│ # Renders Vec<MessageCell> as vertical Layout of Paragraphs.
│ # Target: <300 lines.
│
├── input.rs # ComposerWidget: wraps tui_textarea::TextArea.
│ # Handles Enter-to-send, Shift+Enter newline, history.
│ # Target: <200 lines.
│
├── markdown.rs # MarkdownWidget: pulldown-cmark + syntect → ratatui Paragraph.
│ # Adapted from render.rs. Target: <400 lines.
│
├── status_bar.rs # Bottom status line: model, tokens, cost, session.
│ # Target: <150 lines.
│
├── tool_call.rs # ToolCallWidget: inline spinner + result display.
│ # Target: <200 lines.
│
├── permission.rs # PermissionPromptWidget: modal Y/N overlay.
│ # Target: <150 lines.
│
└── sidebar.rs # (Week 37+) Optional right sidebar.
# Target: <250 lines.
2.2 Runtime Integration (Zero Wrappers)
The TUI consumes these existing types directly:
tui::app::App
├─ uses runtime::ConversationRuntime (via shared Arc)
├─ uses runtime::Session (for persistence)
├─ uses runtime::SessionStore (for resume)
├─ uses runtime::ToolExecutor (for tool call dispatch)
├─ uses runtime::PermissionMode (for status display)
├─ uses runtime::TurnProgressReporter (implemented as TuiProgressReporter)
└─ uses render.rs logic (adapted to MarkdownWidget)
If the ConversationRuntime API is awkward for the TUI, we fix the runtime API — never create a wrapper.
2.3 Event Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ EVENT FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────────────┐ ┌──────────────┐ │
│ │ Crossterm │────▶│ EventBroker │────▶│ App │ │
│ │ (keyboard, │ │ (tokio::select!) │ │ (state machine) │ │
│ │ resize) │ │ │ │ │ │
│ └──────────────┘ └──────────────────────┘ └───────┬────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────────────┐ ┌──────────────┐ │
│ │ API Client │────▶│ TuiProgressReporter │────▶│ AppEvent:: │ │
│ │ (SSE stream)│ │ (mpsc channel) │ │ AssistantDelta │ │
│ └──────────────┘ └──────────────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ratatui::Frame::render() │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ │ ChatWidget │ │ Sidebar │ │ StatusBarWidget │ │ │
│ │ │ (top/left) │ │ (right, opt)│ │ (bottom) │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.4 State Machine
// tui/app.rs
pub struct App {
pub screen: Screen,
pub messages: Vec<MessageCell>,
pub scroll: ScrollState,
pub input: ComposerState,
pub status: StatusState,
pub theme: Theme,
pub session_id: Option<String>,
}
pub enum Screen {
Chat,
PermissionPrompt(PermissionRequest),
SessionPicker(Vec<SessionSummary>),
Help,
Error(String),
Quit,
}
pub struct MessageCell {
pub role: MessageRole,
pub content: Vec<ContentBlock>, // completed blocks
pub streaming: Option<StreamingCell>, // in-flight block
pub timestamp: chrono::DateTime<chrono::Utc>,
pub tool_calls: Vec<ToolCallCell>,
}
pub struct StreamingCell {
pub text_buffer: String,
pub parser_state: StreamingMarkdownState, // incremental parse state
}
pub struct ToolCallCell {
pub name: String,
pub status: ToolCallStatus,
pub result_summary: String,
pub full_result: Option<String>,
pub expanded: bool,
}
pub enum ToolCallStatus {
Pending,
Running { start_time: Instant },
Succeeded { duration: Duration },
Failed { duration: Duration, error: String },
}
3. Sprint-by-Sprint Breakdown (Weeks 1–52)
Sprint Structure
- 1 sprint = 2 weeks (26 sprints total).
- Every sprint ships. If scope is too large, cut features — never delay.
- Merge target:
feat/tui via ≤400-line PRs.
- Review rule: No PR >400 lines. Split or cut.
Goal: main.rs <150 lines. No TUI code written until then.
| Field |
Value |
| Deliverable |
LiveCli struct and its impl block moved from main.rs to app.rs. main.rs now only imports LiveCli and calls run(). |
| Files touched |
main.rs (-4500/+20), app.rs (+4520/-0) |
| New files |
app.rs (extracted from main.rs) |
| Test strategy |
All existing tests in main.rs move to app.rs. Add #[test] fn test_app_module_compiles() as a smoke test. |
| Merge criteria |
cargo test --workspace passes. cargo clippy --workspace clean. main.rs <200 lines. |
| Rollback strategy |
git checkout HEAD~1 -- main.rs restores the monolith in one command. |
| Dependencies |
None — pure file move. |
| PR title |
refactor: extract LiveCli to app.rs |
| PR description |
• Moves LiveCli struct and impl from main.rs to new app.rs. • No behavioral changes — pure extraction. • All tests preserved. • main.rs reduced from ~11,000 to ~200 lines. |
| Review checklist |
① No logic changes — only moves. ② All main.rs tests still exist in app.rs. ③ cargo test --workspace passes. ④ main.rs <200 lines. ⑤ No new dependencies. |
| Risk |
Medium — large file move can lose tests. Mitigated by git diff --stat verification. |
| Docs |
Update rust/README.md workspace layout diagram. |
| Harness impact |
None — pure refactor, no behavioral change. |
Verify this week:
cd rust
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
wc -l crates/rusty-claude-cli/src/main.rs # should be <200
| Field |
Value |
| Deliverable |
All format_* functions moved from app.rs/main.rs to format.rs. |
| Files touched |
app.rs (-1200/+50), format.rs (+1200/-0), main.rs (-0/+10) |
| New files |
format.rs |
| Functions moved |
format_status_report, format_cost_report, format_model_report, format_permissions_report, format_compact_report, format_resume_report, format_sandbox_report, format_commit_preflight_report, format_commit_skipped_report, format_pr_report, format_issue_report, format_ultraplan_report, format_bughunter_report, format_doctor_report, format_auto_compaction_notice |
| Test strategy |
Move all format_* tests from app.rs to format.rs. Add #[test] fn test_format_module_exports() smoke test. |
| Merge criteria |
All format tests pass. No changes to output strings. |
| Rollback strategy |
git checkout HEAD~1 -- app.rs — but format functions are pure, so rollback is trivial. |
| Dependencies |
None. |
| PR title |
refactor: extract report formatting to format.rs |
| PR description |
• Extracts 15 format_* functions from app.rs to format.rs. • No output changes — pure move. • All unit tests preserved. |
| Review checklist |
① Every format_* function has a test. ② Output strings unchanged. ③ cargo test passes. ④ cargo clippy clean. ⑤ No new deps. |
| Risk |
Low — pure code move with test coverage. |
| Docs |
Update rust/README.md crate responsibilities. |
| Harness impact |
None. |
| Field |
Value |
| Deliverable |
Session CRUD (create, resume, list, switch, persist, compaction) moved from app.rs to session_mgr.rs. |
| Files touched |
app.rs (-1100/+80), session_mgr.rs (+1100/-0) |
| New files |
session_mgr.rs |
| Functions moved |
create_session, resume_session, list_sessions, switch_session, persist_session, compact_session, load_latest_session, resolve_session_path |
| Test strategy |
Move session tests. Add SessionManager struct with new() -> Self and list() -> Vec<SessionSummary> methods. Unit-test with temp dirs. |
| Merge criteria |
Session persistence round-trip tested. /session slash commands still work in REPL. |
| Rollback strategy |
git checkout HEAD~1 -- app.rs restores session functions. |
| Dependencies |
None. |
| PR title |
refactor: extract session management to session_mgr.rs |
| PR description |
• Extracts session lifecycle from app.rs to session_mgr.rs. • Introduces SessionManager struct for centralized session operations. • Tests use temp dirs for isolation. |
| Review checklist |
① /session list, /resume, /compact work in REPL. ② Session files written to correct path. ③ Tests use tempdir. ④ cargo test passes. ⑤ No behavioral changes. |
| Risk |
Low — session code is well-tested. |
| Docs |
Update USAGE.md session section if SessionManager adds new CLI surfaces. |
| Harness impact |
Session save/resume is harness-tested; verify no regressions. |
Week 4: Create tui/mod.rs Stub
| Field |
Value |
| Deliverable |
tui/mod.rs created with pub fn run(env: CliEnvironment) -> io::Result<()> stub. claw tui CLI dispatch added. Prints "TUI mode coming soon." and exits. |
| Files touched |
cli.rs (+30), main.rs (+15) |
| New files |
tui/mod.rs (40 lines) |
| Test strategy |
Integration test: #[test] fn test_tui_subcommand_exists() that spawns claw tui --help and asserts exit code 0. |
| Merge criteria |
claw tui --help works. claw tui exits cleanly with status 0. |
| Rollback strategy |
Remove tui/ directory and revert cli.rs/main.rs changes. |
| Dependencies |
None — stub only. |
| PR title |
feat: add tui module stub and claw tui command |
| PR description |
• Adds empty tui/ namespace. • Adds claw tui CLI subcommand as stub. • Integration test verifies CLI surface. |
| Review checklist |
① claw tui --help prints help. ② claw tui exits 0. ③ No new dependencies yet. ④ cargo test passes. ⑤ Stub is clearly marked with // TODO comments. |
| Risk |
Very low — no runtime code. |
| Docs |
Update rust/README.md with claw tui in CLI flags. |
| Harness impact |
Add tui_stub_smoke scenario to mock parity harness. |
Code skeleton:
// tui/mod.rs
use std::io;
pub fn run() -> io::Result<()> {
println!("TUI mode coming soon.");
Ok(())
}
Week 5: Runtime Trait Boundary
| Field |
Value |
| Deliverable |
ConversationRuntime access is abstracted through a trait so REPL and TUI can share it without coupling. |
| Files touched |
app.rs (+80), tui/mod.rs (+30) |
| New files |
None |
| Trait defined |
trait RuntimeHandle { fn run_turn(&mut self, prompt: &str) -> Result<TurnResult, RuntimeError>; fn session(&self) -> &Session; } |
| Test strategy |
Mock implementation of RuntimeHandle for testing. |
| Merge criteria |
LiveCli compiles with new trait. No behavioral changes to REPL. |
| Rollback strategy |
Inline the trait methods back into direct ConversationRuntime calls. |
| Dependencies |
None. |
| PR title |
refactor: runtime access through trait boundary |
| PR description |
• Introduces RuntimeHandle trait for shared runtime access. • Enables REPL/TUI duality without duplication. • No behavioral changes. |
| Review checklist |
① Trait has all methods REPL needs. ② Mock impl works in tests. ③ cargo test passes. ④ REPL unaffected. ⑤ Documentation on trait purpose. |
| Risk |
Low — adds abstraction without changing behavior. |
| Docs |
Document the trait in code comments. |
| Harness impact |
None — trait is compile-time only. |
Week 6: Ratatui Skeleton with TestBackend
| Field |
Value |
| Deliverable |
ratatui and crossterm (event-stream feature) added as dependencies. Minimal alternate-screen demo: shows "Hello, claw" Paragraph, exits on q. Uses TestBackend in tests. |
| Files touched |
Cargo.toml (+3), tui/mod.rs (+120) |
| New files |
tui/event.rs (stub, 30 lines) |
| Dependencies added |
ratatui = "0.29", crossterm = { version = "0.28", features = ["event-stream"] } (crossterm already present, added feature) |
| Test strategy |
#[test] fn test_tui_init_buffer() renders to TestBackend and asserts buffer contains "Hello, claw". |
| Merge criteria |
cargo test --workspace passes. cargo check with --features tui passes (but feature flag not added yet). claw tui opens alternate screen and exits on q. |
| Rollback strategy |
Remove ratatui from Cargo.toml, delete tui/ changes. |
| PR title |
feat: ratatui skeleton with TestBackend tests |
| PR description |
• Adds ratatui dependency. • claw tui opens alternate screen, renders "Hello, claw", exits on q. • TestBackend verifies rendered output in CI. |
| Review checklist |
① Terminal restored on exit (no ANSI leak). ② TestBackend test passes. ③ No REPL changes. ④ q key exits cleanly. ⑤ Cargo.lock updated correctly. |
| Risk |
Low — minimal code, well-tested dependency. |
| Docs |
Add ratatui to rust/README.md dependencies list. |
| Harness impact |
Add tui_smoke_open_close to mock parity harness. |
Code skeleton:
// tui/mod.rs
use std::io;
use crossterm::event::{self, Event, KeyCode, KeyEvent};
use ratatui::{
backend::{Backend, CrosstermBackend, TestBackend},
layout::{Constraint, Direction, Layout},
widgets::{Block, Borders, Paragraph},
Frame, Terminal,
};
pub fn run() -> io::Result<()> {
let mut terminal = ratatui::init();
let result = run_app(&mut terminal);
ratatui::restore();
result
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f))?;
if let Event::Key(KeyEvent { code: KeyCode::Char('q'), .. }) = event::read()? {
return Ok(());
}
}
}
fn ui(frame: &mut Frame) {
let paragraph = Paragraph::new("Hello, claw")
.block(Block::default().borders(Borders::ALL).title("claw"));
frame.render_widget(paragraph, frame.area());
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::buffer::Buffer;
#[test]
fn test_tui_init_buffer() {
let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap();
terminal.draw(|f| ui(f)).unwrap();
let buffer = terminal.backend().buffer();
assert!(buffer.content.iter().any(|c| c.symbol() == "H"));
}
}
Phase B: Core TUI Shell (Weeks 7–12) — Make It Run
Goal: claw tui opens, renders a mock chat, accepts input, scrolls, and exits cleanly.
Week 7: Event Broker
| Field |
Value |
| Deliverable |
tui/event.rs: EventBroker bridges crossterm::event::EventStream → AppEvent enum. Unit-test key translation. |
| Files touched |
tui/mod.rs (+40), tui/app.rs (created, stub) |
| New files |
tui/event.rs (180 lines) |
| Types defined |
enum AppEvent { Tick, Key(KeyEvent), Resize(u16, u16), AssistantDelta(String), ToolStart(String), ToolResult(String, String), Error(String), Quit } |
| Test strategy |
#[test] fn test_key_translation() maps Event::Key(KeyCode::Char('q')) → AppEvent::Key(...). #[test] fn test_resize_event(). |
| Merge criteria |
EventBroker runs in tests without a real terminal. All key events translate correctly. |
| Rollback strategy |
Revert tui/event.rs to stub. |
| PR title |
feat: tui event broker with async event stream |
| PR description |
• EventBroker consumes crossterm::event::EventStream and produces AppEvent. • Supports key, resize, and custom backend events. • Unit tests for key translation. |
| Review checklist |
① All key variants handled. ② Resize produces correct dimensions. ③ AppEvent is Clone + Send. ④ Tests use TestBackend. ⑤ No blocking in test. |
| Risk |
Low — crossterm event handling is well-documented. |
| Docs |
Add event flow diagram to docs/TUI.md. |
| Harness impact |
None — event layer doesn't touch the runtime. |
Week 8: App State Machine
| Field |
Value |
| Deliverable |
tui/app.rs: App struct with Screen::Chat, renders static mock messages as Paragraph list. Handles q quit, arrows scroll. |
| Files touched |
tui/mod.rs (+60), tui/app.rs (created, 200 lines) |
| New files |
tui/app.rs |
| Types defined |
struct App { messages: Vec<MockMessage>, scroll: ScrollState, should_quit: bool }, struct MockMessage { role: String, text: String }, struct ScrollState { offset: usize } |
| Test strategy |
#[test] fn test_app_scroll() — add 100 messages, scroll down 5, assert offset == 5. #[test] fn test_app_quit_on_q() — process KeyCode::Char('q'), assert should_quit. |
| Merge criteria |
App renders mock chat. Arrow keys scroll. q quits. Tests pass without TTY. |
| Rollback strategy |
Revert tui/app.rs to stub. |
| PR title |
feat: tui app state machine with mock messages |
| PR description |
• App state machine with Screen::Chat. • Renders static mock messages. • Supports scroll via arrow keys. • q to quit. |
| Review checklist |
① Mock messages visible in TestBackend. ② Scroll offset correct. ③ q quits. ④ 100 messages don't panic. ⑤ Tests headless. |
| Risk |
Low — no runtime integration yet. |
| Docs |
Update docs/TUI.md state machine section. |
| Harness impact |
None. |
Week 9: Composer Input Widget
| Field |
Value |
| Deliverable |
tui/widgets/input.rs: Composer widget wrapping tui_textarea::TextArea. Enter sends, Shift+Enter newline. Mock: prints submitted text to debug overlay. |
| Files touched |
Cargo.toml (+1), tui/app.rs (+60), tui/widgets/input.rs (created, 150 lines) |
| New files |
tui/widgets/input.rs |
| Dependencies added |
tui-textarea = "0.7" |
| Test strategy |
#[test] fn test_composer_enter_sends() — simulate Enter, assert Submit("hello") event. #[test] fn test_composer_shift_enter_newline() — assert cursor moved down. |
| Merge criteria |
Text input works. Enter submits. Shift+Enter inserts newline. History (up arrow) cycles past inputs. |
| Rollback strategy |
Remove tui-textarea from Cargo.toml, revert input widget. |
| PR title |
feat: tui composer input widget |
| PR description |
• Adds ComposerWidget using tui-textarea. • Enter submits, Shift+Enter inserts newline. • Up/down arrow for history. • Tests verify input events. |
| Review checklist |
① Enter produces submit event. ② Shift+Enter doesn't submit. ③ History works. ④ Unicode input handled. ⑤ TestBackend verifies buffer. |
| Risk |
Low — tui-textarea is mature, ~2,000 GitHub stars. |
| Docs |
Add keybindings table to docs/TUI.md. |
| Harness impact |
None. |
Week 10: Chat Message List Widget
| Field |
Value |
| Deliverable |
tui/widgets/chat.rs: ChatWidget renders Vec<MessageCell> as vertical Layout of Paragraphs. Supports PgUp/PgDn. |
| Files touched |
tui/app.rs (+80), tui/widgets/chat.rs (created, 200 lines) |
| New files |
tui/widgets/chat.rs |
| Test strategy |
#[test] fn test_chat_renders_messages() — 5 messages, assert all visible. #[test] fn test_chat_scroll_pagination() — 50 messages in 24-line terminal, PgDn advances by page size. |
| Merge criteria |
Messages render with roles (user/assistant) styled differently. Scroll works. PgUp/PgDn jump by viewport height. |
| Rollback strategy |
Revert chat.rs to stub. |
| PR title |
feat: tui chat message list widget |
| PR description |
• ChatWidget renders message history with role-based styling. • Scrollable with arrows and PgUp/PgDn. • Supports 100+ messages without frame drops. |
| Review checklist |
① User messages right-aligned or differently colored. ② Assistant messages left-aligned. ③ Scroll offset clamped to message count. ④ PgUp/PgDn jump correctly. ⑤ Tests headless. |
| Risk |
Low — pure rendering, no external state. |
| Docs |
Document chat layout in docs/TUI.md. |
| Harness impact |
None. |
Week 11: Terminal Resize & Static Status Bar
| Field |
Value |
| Deliverable |
Terminal resize handling. Status bar at bottom showing model name (static string), permission mode (static), terminal dimensions. |
| Files touched |
tui/app.rs (+60), tui/widgets/status_bar.rs (created, 120 lines) |
| New files |
tui/widgets/status_bar.rs |
| Test strategy |
#[test] fn test_status_bar_renders() — assert model name visible. #[test] fn test_resize_updates_layout() — assert layout recalculates on Resize event. |
| Merge criteria |
Resize event updates layout. Status bar always visible at bottom. Content area adjusts. |
| Rollback strategy |
Remove status bar widget, revert resize handler. |
| PR title |
feat: terminal resize handling and static status bar |
| PR description |
• Status bar shows model, permission mode, terminal size. • Resize events recalculate layout. • Content area shrinks/grows correctly. |
| Review checklist |
① Status bar at bottom in TestBackend. ② Resize doesn't panic. ③ Layout constraints correct (status bar height = 3). ④ Content area height = total - 3. ⑤ Tests pass. |
| Risk |
Low — layout math with ratatui::Layout. |
| Docs |
Document layout constraints. |
| Harness impact |
None. |
Week 12: Integration — Mock Chat + Input + Status Bar
| Field |
Value |
| Deliverable |
claw tui opens a full-screen app with mock chat + input + status bar. Smoke test runs in CI (skips if no TTY). |
| Files touched |
tui/mod.rs (+40), tui/app.rs (+80) |
| New files |
tests/tui_smoke.rs (integration test, 80 lines) |
| Test strategy |
Integration test with TestBackend: send mock messages, assert rendered. CI test that spawns claw tui with timeout 2 and asserts clean exit. |
| Merge criteria |
claw tui runs for 2 seconds and exits cleanly. Full layout visible in TestBackend. CI passes. |
| Rollback strategy |
Revert tui/ changes, keep app.rs/format.rs/session_mgr.rs extractions. |
| PR title |
feat: claw tui opens full-screen TUI with mock data |
| PR description |
• Full TUI layout: chat area, input composer, status bar. • Mock conversation data. • Integration test with TestBackend and CI smoke test. |
| Review checklist |
① Full layout visible in test render. ② claw tui exits on q. ③ CI test passes. ④ No REPL changes. ⑤ Memory usage flat over 10-second run. |
| Risk |
Low — integration of already-tested components. |
| Docs |
Add TUI screenshot instructions to USAGE.md (ASCII art or capture). |
| Harness impact |
Add tui_full_layout_smoke to mock parity harness. |
Phase C: Live Conversation (Weeks 13–20) — Make It Talk
Goal: Real conversations with the model. Streaming text, tool calls, session save/resume, permission modal.
Week 13: Wire ConversationRuntime
| Field |
Value |
| Deliverable |
ConversationRuntime wired into TUI event loop. TuiProgressReporter implements TurnProgressReporter, sends AppEvent::AssistantDelta via tokio::sync::mpsc. |
| Files touched |
tui/app.rs (+120), tui/event.rs (+40), tui/mod.rs (+60) |
| New files |
None |
| Types defined |
struct TuiProgressReporter { tx: mpsc::UnboundedSender<AppEvent> } impl TurnProgressReporter for TuiProgressReporter { ... } |
| Test strategy |
Mock ConversationRuntime that emits fake deltas. #[test] fn test_progress_reporter_sends_delta() — assert AssistantDelta received. |
| Merge criteria |
TUI receives fake streaming events. Events route to App state. No panics. |
| Rollback strategy |
Replace TuiProgressReporter with no-op. |
| PR title |
feat: wire ConversationRuntime into tui event loop |
| PR description |
• TuiProgressReporter bridges runtime events to TUI channel. • Tokio async event loop processes backend + user events. • Mock runtime tests verify event flow. |
| Review checklist |
① AssistantDelta events reach App. ② No deadlocks. ③ Channel bounded correctly. ④ Tests use mock runtime. ⑤ Runtime trait boundary clean. |
| Risk |
Medium — async event loop is first real complexity. Mitigated by bounded channels. |
| Docs |
Update event flow diagram in docs/TUI.md. |
| Harness impact |
Add tui_streaming_mock scenario. |
Week 14: Streaming Assistant Text
| Field |
Value |
| Deliverable |
AssistantEvent::TextDelta appends to active message cell in real time. No markdown rendering yet — raw text. |
| Files touched |
tui/app.rs (+80), tui/widgets/chat.rs (+60) |
| New files |
None |
| Test strategy |
#[test] fn test_streaming_appends_text() — send 5 deltas, assert message text == concatenated deltas. #[test] fn test_streaming_cursor_position() — last line visible. |
| Merge criteria |
Text appears character-by-character in chat widget. Auto-scroll to bottom. No flicker. |
| Rollback strategy |
Buffer deltas instead of displaying incrementally. |
| PR title |
feat: real-time streaming assistant text in tui |
| PR description |
• Assistant text streams into chat in real time. • Auto-scroll to latest delta. • Raw text (no markdown yet). |
| Review checklist |
① Text visible within 50ms of delta. ② No duplicate text. ③ Auto-scroll works. ④ 1000-character stream doesn't OOM. ⑤ Tests headless. |
| Risk |
Medium — rendering performance on fast streams. |
| Docs |
Document streaming behavior and latency targets. |
| Harness impact |
Add tui_streaming_text scenario. |
Week 15: Session Save & Resume
| Field |
Value |
| Deliverable |
On TUI exit, save session to JSONL (same format as REPL). claw tui --resume loads it. |
| Files touched |
tui/app.rs (+80), tui/mod.rs (+40), session_mgr.rs (+20) |
| New files |
None |
| Test strategy |
#[test] fn test_session_save_roundtrip() — create messages, save, load, assert equal. |
| Merge criteria |
Session persists across claw tui invocations. Messages load with correct timestamps. |
| Rollback strategy |
Don't save on exit (lose history). |
| PR title |
feat: tui session save and resume |
| PR description |
• TUI auto-saves session on exit. • --resume flag loads last session. • Same JSONL format as REPL sessions. |
| Review checklist |
① Session file written on exit. ② --resume loads it. ③ Format compatible with REPL sessions. ④ No data loss. ⑤ Tests verify round-trip. |
| Risk |
Low — session persistence already works in REPL. |
| Docs |
Update USAGE.md with claw tui --resume. |
| Harness impact |
Add tui_session_save_resume scenario. |
Week 16: Inline Tool Call Rendering
| Field |
Value |
| Deliverable |
Tool call start shows spinner + name inline. Finish shows ✓/✗ with truncated result (first 5 lines). |
| Files touched |
tui/app.rs (+60), tui/widgets/tool_call.rs (created, 220 lines) |
| New files |
tui/widgets/tool_call.rs |
| Test strategy |
#[test] fn test_tool_call_lifecycle() — ToolStart("bash") → spinner → ToolResult("bash", "ok") → ✓. |
| Merge criteria |
Tool calls animate inline. Results truncated with [+] expand hint. |
| Rollback strategy |
Show plain text tool names instead of widgets. |
| PR title |
feat: inline tool call spinner and result summary |
| PR description |
• Tool calls show animated spinner. • Results show ✓/✗ with truncated preview. • Expandable full output. |
| Review checklist |
① Spinner visible during tool call. ② ✓/✗ correct based on result. ③ Truncation at 5 lines. ④ 10 concurrent tools don't break layout. ⑤ Tests headless. |
| Risk |
Low — tool execution already works in runtime. |
| Docs |
Document tool visualization in docs/TUI.md. |
| Harness impact |
Add tui_tool_call_display scenario. |
Week 17: Permission Prompt Modal
| Field |
Value |
| Deliverable |
Permission prompt overlay (centered block) for Y/N approval. Does not block event loop — shows as Screen::PermissionPrompt state. |
| Files touched |
tui/app.rs (+60), tui/widgets/permission.rs (created, 200 lines) |
| New files |
tui/widgets/permission.rs |
| Test strategy |
#[test] fn test_permission_modal_renders() — assert centered block visible. #[test] fn test_permission_y_approves() — assert approval event sent. |
| Merge criteria |
Modal renders over chat. y approves, n denies. Other keys ignored. Event loop continues. |
| Rollback strategy |
Always deny or always approve (degraded mode). |
| PR title |
feat: permission prompt modal overlay in tui |
| PR description |
• Modal overlay for tool approval. • y to approve, n to deny. • Non-blocking event loop. • Styled with box drawing. |
| Review checklist |
① Modal centered and visible. ② y/n responses correct. ③ Background dimmed. ④ Escape cancels. ⑤ Tests headless. |
| Risk |
Medium — modal state transitions must not deadlock. |
| Docs |
Document permission flow in docs/TUI.md. |
| Harness impact |
Add tui_permission_prompt scenario. |
Week 18: Error Handling
| Field |
Value |
| Deliverable |
Network errors, API rate limits, runtime errors render as styled error messages in chat. No panics. |
| Files touched |
tui/app.rs (+40), tui/widgets/chat.rs (+30) |
| New files |
None |
| Test strategy |
#[test] fn test_error_rendered_in_chat() — inject error, assert red text visible. |
| Merge criteria |
All error paths show user-friendly messages. Panic-free. Stack traces suppressed (logged to file only). |
| Rollback strategy |
Panic on error (ugly but functional). |
| PR title |
feat: graceful error rendering in tui chat |
| PR description |
• Errors rendered as styled messages in chat. • No panics. • Stack traces logged, not displayed. • Auto-scroll to error message. |
| Review checklist |
① Network error → "Connection failed" visible. ② API error → "API error: ..." visible. ③ No panic. ④ Stack trace in log file. ⑤ Tests verify all error paths. |
| Risk |
Low — error types already exist in runtime. |
| Docs |
Document error handling strategy. |
| Harness impact |
Add tui_error_handling scenario. |
Week 19: Multi-Turn Conversation
| Field |
Value |
| Deliverable |
Send follow-up messages. Scrollback preserves full history. Input clears after send. |
| Files touched |
tui/app.rs (+40), tui/widgets/input.rs (+20) |
| New files |
None |
| Test strategy |
#[test] fn test_multi_turn_history() — 3 turns, assert 6 messages. #[test] fn test_input_cleared_after_send(). |
| Merge criteria |
Each turn starts a new streaming cycle. History is complete. Input field resets. |
| Rollback strategy |
Single-turn only (restart TUI for next turn). |
| PR title |
feat: multi-turn conversation in tui |
| PR description |
• Follow-up messages work. • Full scrollback history. • Input cleared after send. • Session auto-saves between turns. |
| Review checklist |
① New turn triggers streaming. ② Old turns frozen (not re-streamed). ③ Input empty after send. ④ History scrollable. ⑤ 10-turn session doesn't OOM. |
| Risk |
Low — builds on Weeks 13–18. |
| Docs |
Update USAGE.md with multi-turn examples. |
| Harness impact |
Add tui_multi_turn scenario. |
Week 20: Feature Flag
| Field |
Value |
| Deliverable |
tui feature flag added to rusty-claude-cli/Cargo.toml. Default OFF. CI builds with --features tui. Binary size documented. |
| Files touched |
Cargo.toml (+5), main.rs (+10), .github/workflows/rust-ci.yml (+10) |
| New files |
docs/TUI-BINARY-SIZE.md |
| Test strategy |
#[test] fn test_tui_feature_compiles() — compile with feature flag. #[test] fn test_without_tui_feature() — verify claw tui is absent without flag. |
| Merge criteria |
cargo build --workspace works without feature. cargo build --workspace --features tui works with feature. Binary size documented. |
| Rollback strategy |
Make tui feature default ON (effectively removing the flag). |
| PR title |
feat: gate tui behind optional feature flag |
| PR description |
• tui feature flag in Cargo.toml. • Default OFF to avoid binary bloat for REPL-only users. • CI builds both configurations. • Binary size impact documented. |
| Review checklist |
① Build without feature passes. ② Build with feature passes. ③ Binary size documented. ④ CI matrix includes both. ⑤ Flag not on by default. |
| Risk |
Low — mechanical change. |
| Docs |
Document feature flag usage in USAGE.md. |
| Harness impact |
CI builds with --features tui. |
Phase D: Rich Rendering (Weeks 21–28) — Make It Beautiful
Goal: Markdown rendering, syntax highlighting, themes, diffs, performance.
Week 21: Markdown Widget (Headings, Lists, Code Blocks)
| Field |
Value |
| Deliverable |
tui/widgets/markdown.rs: Port render.rs (pulldown-cmark + syntect) to ratatui Paragraph. Renders headings, lists, inline code, code blocks with syntax highlighting. |
| Files touched |
render.rs (+0, read-only reference), tui/widgets/markdown.rs (created, 300 lines) |
| New files |
tui/widgets/markdown.rs |
| Test strategy |
#[test] fn test_heading_renders_bold() — assert bold style. #[test] fn test_code_block_highlighted() — assert syntax colors. |
| Merge criteria |
Markdown renders in chat. Code blocks have syntax highlighting. Lists are indented. |
| Rollback strategy |
Use plain text rendering (ugly but readable). |
| PR title |
feat: markdown widget with syntax highlighting |
| PR description |
• MarkdownWidget renders pulldown-cmark events to ratatui Paragraph. • Code blocks with syntect highlighting. • Headings, lists, inline code, blockquotes. |
| Review checklist |
① H1/H2 bold and colored. ② Code blocks highlighted. ③ Lists indented. ④ Inline code colored. ⑤ 1000-line markdown renders in <50ms. |
| Risk |
Medium — markdown parsing + rendering is complex. |
| Docs |
Document supported markdown elements. |
| Harness impact |
Add tui_markdown_render scenario. |
Week 22: Table Rendering
| Field |
Value |
| Deliverable |
pulldown-cmark table events → ratatui Table widget with borders. |
| Files touched |
tui/widgets/markdown.rs (+100) |
| New files |
None |
| Test strategy |
#[test] fn test_table_renders_with_borders() — assert Table widget visible. |
| Merge criteria |
Tables render with borders and alignment. Fit within terminal width. |
| Rollback strategy |
Render tables as plain text. |
| PR title |
feat: table rendering in markdown widget |
| PR description |
• Markdown tables render as ratatui Table. • Borders, headers, cell alignment. • Truncation for narrow terminals. |
| Review checklist |
① Borders visible. ② Headers bold. ③ Cells aligned. ④ Wide table truncates gracefully. ⑤ Tests verify. |
| Risk |
Low — Table widget is standard in ratatui. |
| Docs |
Add table examples to docs. |
| Harness impact |
None. |
Week 23: Blockquote & Link Rendering
| Field |
Value |
| Deliverable |
Blockquote: styled indentation + left border. Links: underlined + clickable (placeholder for mouse week). |
| Files touched |
tui/widgets/markdown.rs (+80), tui/theme.rs (+20) |
| New files |
None |
| Test strategy |
#[test] fn test_blockquote_has_left_border(). #[test] fn test_link_is_underlined(). |
| Merge criteria |
Blockquotes visually distinct. Links underlined. |
| Rollback strategy |
Render as plain text. |
| PR title |
feat: blockquote and link rendering |
| PR description |
• Blockquotes with left border and muted color. • Links underlined. • Mouse click support deferred to Week 35. |
| Review checklist |
① Blockquote left border visible. ② Muted text color. ③ Links underlined. ④ URL displayed on hover or inline. ⑤ Tests verify. |
| Risk |
Low — style changes only. |
| Docs |
Document styling choices. |
| Harness impact |
None. |
Week 24: Incremental Markdown Streaming
| Field |
Value |
| Deliverable |
Incremental parse of markdown as text deltas arrive. Don't re-render from scratch every frame — append to parser state. |
| Files touched |
tui/widgets/markdown.rs (+120), tui/app.rs (+40) |
| New files |
tui/widgets/markdown.rs adds StreamingMarkdownState struct |
| Test strategy |
#[test] fn test_incremental_parse() — feed "# He" then "llo", assert heading recognized. |
| Merge criteria |
Streaming text parses incrementally. No re-render flicker. Performance: <16ms per delta. |
| Rollback strategy |
Buffer full response, then render once (slower but correct). |
| PR title |
feat: incremental markdown streaming render |
| PR description |
• Markdown parser state persists across deltas. • Incremental render avoids full re-parse. • No flicker. • Sub-16ms per delta. |
| Review checklist |
① Heading detected mid-stream. ② Code block fenced detected. ③ No flicker. ④ Performance <16ms. ⑤ Tests incremental state. |
| Risk |
High — incremental parsing is tricky. Mitigated by fallback to full re-parse on parse error. |
| Docs |
Document incremental parsing strategy. |
| Harness impact |
None — optimization only. |
Week 25: Dark/Light Theme System
| Field |
Value |
| Deliverable |
Dark/light theme system. Respect NO_COLOR. Detect truecolor vs. 256-color vs. 16-color terminals and degrade gracefully. |
| Files touched |
tui/theme.rs (created, 250 lines), tui/app.rs (+20) |
| New files |
tui/theme.rs |
| Test strategy |
#[test] fn test_theme_detects_no_color(). #[test] fn test_theme_16_color_fallback(). |
| Merge criteria |
NO_COLOR=1 disables colors. Truecolor terminal shows full palette. 16-color terminal degrades correctly. |
| Rollback strategy |
Hardcode dark theme (no customization). |
| PR title |
feat: color theme system with terminal capability detection |
| PR description |
• Dark, light, solarized themes. • NO_COLOR support. • Truecolor → 256 → 16 fallback. • Configurable via .claw.json. |
| Review checklist |
① NO_COLOR=1 → no ANSI. ② Truecolor → 24-bit colors. ③ 256-color → approximated. ④ 16-color → nearest. ⑤ Configurable. |
| Risk |
Low — color detection is well-understood. |
| Docs |
Document theme configuration. |
| Harness impact |
None — cosmetic only. |
Week 26: Collapsible/Expandable Tool Results
| Field |
Value |
| Deliverable |
Press Enter on a tool result to expand/collapse full output. Scrollable code view for expanded results. |
| Files touched |
tui/widgets/tool_call.rs (+120), tui/app.rs (+40) |
| New files |
None |
| Test strategy |
#[test] fn test_tool_expand_toggle(). #[test] fn test_expanded_scrollable(). |
| Merge criteria |
Toggle works. Expanded result scrollable. Collapsed result shows summary. |
| Rollback strategy |
Always show full result (no collapsing). |
| PR title |
feat: collapsible/expandable tool results |
| PR description |
• Tool results toggle expand/collapse on Enter. • Expanded view scrollable. • Summary visible when collapsed. |
| Review checklist |
① Enter toggles state. ② Expanded view scrolls. ③ Collapsed shows summary. ④ 10 expanded tools don't break layout. ⑤ Tests verify. |
| Risk |
Low — state toggle + scroll. |
| Docs |
Document tool interaction in docs/TUI.md. |
| Harness impact |
None. |
Week 27: Colored Diff Rendering
| Field |
Value |
| Deliverable |
When edit_file or /diff produces output, render unified diff with green additions and red removals. |
| Files touched |
tui/widgets/diff.rs (created, 200 lines), tui/app.rs (+20) |
| New files |
tui/widgets/diff.rs |
| Test strategy |
#[test] fn test_diff_green_additions(). #[test] fn test_diff_red_removals(). |
| Merge criteria |
Diff lines colored correctly. Context lines neutral. Header lines dimmed. |
| Rollback strategy |
Render diff as plain text. |
| PR title |
feat: colored diff rendering in tui |
| PR description |
• Unified diff with green additions, red removals. • Context lines in default color. • Header dimmed. |
| Review checklist |
① + lines green. ② - lines red. ③ Context lines neutral. ④ Headers dimmed. ⑤ Tests verify all line types. |
| Risk |
Low — diff parsing is straightforward. |
| Docs |
Add diff rendering to feature list. |
| Harness impact |
None. |
Week 28: Performance Pass
| Field |
Value |
| Deliverable |
Profile with cargo flamegraph. Ensure 60fps on 100-message session. Optimize if below 30fps. |
| Files touched |
Various — depends on profiler output |
| New files |
benches/tui_render.rs (criterion bench) |
| Test strategy |
Criterion benchmark: render 100 messages, measure frame time. Target: p99 <16ms. |
| Merge criteria |
cargo bench shows p99 frame time <16ms. No regressions in existing tests. |
| Rollback strategy |
Accept 30fps (degraded but functional). |
| PR title |
perf: rendering performance pass |
| PR description |
• Criterion benchmark for 100-message render. • Flamegraph analysis. • Optimizations: reduced allocations, cached widget state. • Target: 60fps. |
| Review checklist |
① Benchmark exists. ② p99 <16ms. ③ No allocations in hot path. ④ Flamegraph reviewed. ⑤ No test regressions. |
| Risk |
Medium — performance tuning can introduce subtle bugs. |
| Docs |
Document performance targets. |
| Harness impact |
None — performance only. |
Phase E: Navigation & Power Features (Weeks 29–38) — Make It Fast to Use
Goal: Keyboard shortcuts, search, session picker, slash commands, mouse, sidebar.
Week 29: Conversation Search (/)
| Field |
Value |
| Deliverable |
Incremental search highlighting matching text. n/N for next/prev match. |
| Files touched |
tui/app.rs (+80), tui/widgets/search.rs (created, 200 lines) |
| New files |
tui/widgets/search.rs |
| Test strategy |
#[test] fn test_search_finds_text(). #[test] fn test_search_highlight(). |
| Merge criteria |
/ opens search. Type filters. n/N navigates. Escape closes. |
| Rollback strategy |
No search (use external grep). |
| PR title |
feat: conversation search with incremental highlighting |
| PR description |
• / opens search overlay. • Incremental filtering. • Match highlighting. • n/N navigation. |
| Review checklist |
① / opens search. ② Typing filters. ③ Matches highlighted. ④ n/N cycles. ⑤ Escape closes. |
| Risk |
Low — string search + highlight. |
| Docs |
Add search keybindings. |
| Harness impact |
None. |
Week 30: Help Overlay (?)
| Field |
Value |
| Deliverable |
? help overlay: keyboard shortcuts panel showing all keybindings. |
| Files touched |
tui/widgets/help.rs (created, 150 lines), tui/app.rs (+20) |
| New files |
tui/widgets/help.rs |
| Test strategy |
#[test] fn test_help_overlay_renders(). |
| Merge criteria |
? toggles overlay. All keybindings listed. Escape or ? closes. |
| Rollback strategy |
No help overlay (document keybindings in README only). |
| PR title |
feat: keyboard shortcuts help overlay |
| PR description |
• ? toggles help overlay. • All keybindings listed with descriptions. • Styled with boxes. |
| Review checklist |
① ? opens. ② All keys listed. ③ Escape closes. ④ Doesn't block input. ⑤ Tests verify. |
| Risk |
Low — static overlay. |
| Docs |
Update keybindings table. |
| Harness impact |
None. |
Week 31: Session Picker (Ctrl+S)
| Field |
Value |
| Deliverable |
Ctrl+S opens fuzzy-filterable list of saved sessions. Up/down arrows, Enter to switch. |
| Files touched |
tui/widgets/session_picker.rs (created, 200 lines), tui/app.rs (+40) |
| New files |
tui/widgets/session_picker.rs |
| Test strategy |
#[test] fn test_session_picker_lists_sessions(). #[test] fn test_session_picker_filters(). |
| Merge criteria |
Ctrl+S opens picker. Typing filters. Enter loads session. Escape cancels. |
| Rollback strategy |
Use /session list slash command within TUI. |
| PR title |
feat: interactive session picker |
| PR description |
• Ctrl+S opens session list. • Fuzzy filter. • Up/down to navigate. • Enter to load. |
| Review checklist |
① Sessions listed. ② Filter works. ③ Enter loads. ④ Escape cancels. ⑤ Tests verify. |
| Risk |
Low — list + filter pattern. |
| Docs |
Document session picker. |
| Harness impact |
None. |
Week 32: Slash Command Picker
| Field |
Value |
| Deliverable |
Type / in input to trigger slash command picker (like REPL). Commands execute within TUI (e.g., /model, /permissions). |
| Files touched |
tui/widgets/input.rs (+80), tui/app.rs (+60) |
| New files |
None |
| Test strategy |
#[test] fn test_slash_picker_opens(). #[test] fn test_slash_command_executes(). |
| Merge criteria |
/ opens picker. Tab completion. Enter executes. Commands like /model work. |
| Rollback strategy |
No slash commands in TUI (use REPL for config changes). |
| PR title |
feat: slash command picker and execution in tui |
| PR description |
• / triggers slash command picker. • Tab completion. • In-TUI execution. • Share logic with REPL. |
| Review checklist |
① / opens picker. ② Tab completes. ③ Enter executes. ④ /model switches model. ⑤ Tests verify. |
| Risk |
Medium — command execution can affect runtime state. |
| Docs |
Document TUI slash commands. |
| Harness impact |
None. |
Week 33: /compact with Toast
| Field |
Value |
| Deliverable |
/compact in TUI runs compaction, shows toast notification with tokens saved. |
| Files touched |
tui/app.rs (+40), tui/widgets/toast.rs (created, 80 lines) |
| New files |
tui/widgets/toast.rs |
| Test strategy |
#[test] fn test_toast_shows_message(). |
| Merge criteria |
Toast appears for 3 seconds. Shows tokens saved. Does not block input. |
| Rollback strategy |
Print compaction result in chat (no toast). |
| PR title |
feat: compact command with toast notification |
| PR description |
• /compact shows toast. • Auto-dismisses after 3s. • Non-blocking. |
| Review checklist |
① Toast visible. ② Message correct. ③ Auto-dismisses. ④ Input not blocked. ⑤ Tests verify. |
| Risk |
Low — timed notification. |
| Docs |
Document toast behavior. |
| Harness impact |
None. |
Week 34: Copy to Clipboard
| Field |
Value |
| Deliverable |
y on a message or code block copies to clipboard via arboard. |
| Files touched |
Cargo.toml (+1), tui/app.rs (+40), tui/widgets/chat.rs (+30) |
| New files |
None |
| Dependencies added |
arboard = "3" |
| Test strategy |
#[test] fn test_copy_event_sent(). Full clipboard test is platform-dependent; mock the clipboard backend. |
| Merge criteria |
y copies selected message. Toast confirms. Works on Linux (wlroots/X11), macOS, Windows. |
| Rollback strategy |
No copy feature (use terminal selection). |
| PR title |
feat: copy message to clipboard |
| PR description |
• y copies message to clipboard. • arboard handles cross-platform paste. • Toast confirms. |
| Review checklist |
① y copies. ② Toast confirms. ③ Linux (X11/Wayland). ④ macOS. ⑤ Windows. |
| Risk |
Low — arboard is mature. |
| Docs |
Document copy keybinding. |
| Harness impact |
None. |
Week 35: Mouse Support
| Field |
Value |
| Deliverable |
Click to expand tool results. Scroll wheel on conversation. Click to focus input. |
| Files touched |
tui/event.rs (+60), tui/app.rs (+40), tui/widgets/chat.rs (+20) |
| New files |
None |
| Test strategy |
#[test] fn test_mouse_scroll_updates_offset(). Mouse click tests use TestBackend with synthetic mouse events. |
| Merge criteria |
Mouse scroll works. Click on tool result toggles expand. Click on input focuses. |
| Rollback strategy |
Keyboard-only (no mouse). |
| PR title |
feat: mouse support for tool expansion and scrolling |
| PR description |
• Scroll wheel scrolls conversation. • Click toggles tool expand. • Click focuses input. |
| Review checklist |
① Scroll wheel works. ② Click toggles expand. ③ Click focuses input. ④ Keyboard still works. ⑤ Tests verify. |
| Risk |
Low — crossterm mouse events are standard. |
| Docs |
Document mouse support. |
| Harness impact |
None. |
| Field |
Value |
| Deliverable |
For long outputs (/status, /config), open full-screen pager with j/k/q. |
| Files touched |
tui/widgets/pager.rs (created, 180 lines), tui/app.rs (+40) |
| New files |
tui/widgets/pager.rs |
| Test strategy |
#[test] fn test_pager_scrolls(). #[test] fn test_pager_quits_on_q(). |
| Merge criteria |
Long outputs open pager. j/k scroll. q quits back to chat. |
| Rollback strategy |
Dump long output directly in chat (scrolling mess). |
| PR title |
feat: internal pager for long outputs |
| PR description |
• Long outputs open pager overlay. • j/k scroll. • q returns to chat. |
| Review checklist |
① Pager opens for long output. ② j/k scroll. ③ q quits. ④ Line numbers optional. ⑤ Tests verify. |
| Risk |
Low — scrollable text view. |
| Docs |
Document pager keybindings. |
| Harness impact |
None. |
| Field |
Value |
| Deliverable |
Ctrl+P toggles right sidebar showing active tool status or todo list. |
| Files touched |
tui/widgets/sidebar.rs (created, 250 lines), tui/app.rs (+60) |
| New files |
tui/widgets/sidebar.rs |
| Test strategy |
#[test] fn test_sidebar_toggles(). #[test] fn test_sidebar_shows_tools(). |
| Merge criteria |
Sidebar toggles on/off. Shows tool list. Doesn't break chat layout. |
| Rollback strategy |
No sidebar (single-pane only). |
| PR title |
feat: optional right sidebar for tool status |
| PR description |
• Ctrl+P toggles sidebar. • Shows active tools and status. • Resizes chat area. |
| Review checklist |
① Toggle works. ② Tool status visible. ③ Chat area shrinks. ④ No layout breaks. ⑤ Tests verify. |
| Risk |
Low — layout toggle. |
| Docs |
Document sidebar. |
| Harness impact |
None. |
Week 38: Custom Keybindings
| Field |
Value |
| Deliverable |
Read keybindings from .claw.json. Remap any action to any key chord. |
| Files touched |
tui/event.rs (+60), tui/app.rs (+20) |
| New files |
tui/keybindings.rs (created, 150 lines) |
| Test strategy |
#[test] fn test_custom_keybinding_loaded(). #[test] fn test_keybinding_override(). |
| Merge criteria |
Config file read on startup. Overrides apply. Invalid config shows error in chat. |
| Rollback strategy |
Hardcoded keybindings (ignore config). |
| PR title |
feat: user-configurable keybindings |
| PR description |
• Keybindings from .claw.json. • Override any action. • Validation with error messages. |
| Review checklist |
① Config loaded. ② Overrides apply. ③ Invalid config errors. ④ Defaults documented. ⑤ Tests verify. |
| Risk |
Low — config parsing. |
| Docs |
Document keybinding config format. |
| Harness impact |
None. |
Phase F: Polish & Hardening (Weeks 39–46) — Make It Production-Ready
Goal: Stable, tested, accessible, well-documented.
Week 39: Screen Reader Accessibility
| Field |
Value |
| Deliverable |
Screen reader support: crossterm title updates, status announcements via SetTitle. |
| Files touched |
tui/app.rs (+40), tui/event.rs (+20) |
| New files |
None |
| Test strategy |
#[test] fn test_title_updates_on_message(). |
| Merge criteria |
Terminal title reflects app state. Status messages announced. |
| Rollback strategy |
No accessibility features. |
| PR title |
feat: screen reader accessibility annotations |
| PR description |
• Terminal title updates. • Status announcements. • ARIA-like labels on widgets. |
| Review checklist |
① Title updates. ② Status announced. ③ No visual changes. ④ Tested with orca or NVDA. ⑤ Tests verify. |
| Risk |
Low — title updates only. |
| Docs |
Document accessibility features. |
| Harness impact |
None. |
Week 40: Small Terminal Degradation
| Field |
Value |
| Deliverable |
If terminal <30 rows or <80 cols, show warning banner and disable sidebar. |
| Files touched |
tui/app.rs (+80), tui/theme.rs (+20) |
| New files |
None |
| Test strategy |
#[test] fn test_small_terminal_warning(). #[test] fn test_sidebar_disabled_narrow(). |
| Merge criteria |
Warning visible on small terminals. Sidebar auto-disabled. Chat still functional. |
| Rollback strategy |
No degradation (layout may break on small terminals). |
| PR title |
feat: small terminal graceful degradation |
| PR description |
• Warning on <30 rows or <80 cols. • Sidebar disabled. • Layout adjusted. |
| Review checklist |
① Warning visible. ② Sidebar disabled. ③ Chat scrollable. ④ No panic. ⑤ Tests verify. |
| Risk |
Low — conditional layout. |
| Docs |
Document minimum terminal size. |
| Harness impact |
None. |
Week 41: Unicode Width Handling
| Field |
Value |
| Deliverable |
Use unicode-width for CJK and emoji so layouts don't break. |
| Files touched |
Cargo.toml (+1), tui/widgets/chat.rs (+30), tui/widgets/markdown.rs (+30) |
| New files |
None |
| Dependencies added |
unicode-width = "0.2" |
| Test strategy |
#[test] fn test_cjk_width(). #[test] fn test_emoji_width(). |
| Merge criteria |
CJK characters take 2 columns. Emoji take 2 columns (on supported terminals). Layouts don't break. |
| Rollback strategy |
Assume ASCII width (breaks on CJK/emoji). |
| PR title |
fix: unicode width for layout correctness |
| PR description |
• unicode-width for correct column counting. • CJK and emoji handled. • Layout integrity. |
| Review checklist |
① CJK correct. ② Emoji correct. ③ Layout intact. ④ No truncation. ⑤ Tests verify. |
| Risk |
Low — well-tested crate. |
| Docs |
Document unicode support. |
| Harness impact |
None. |
Week 42: Snapshot Testing with insta
| Field |
Value |
| Deliverable |
insta snapshot tests for complex conversations: tables, code blocks, diffs. |
| Files touched |
Cargo.toml (+1, dev-dependency), tests/snapshots/ (new dir) |
| New files |
tests/tui_snapshots.rs (150 lines) |
| Dependencies added |
insta = "1" (dev-dependency) |
| Test strategy |
Render complex markdown to TestBackend, snapshot with insta::assert_snapshot. |
| Merge criteria |
Snapshots committed. CI checks them. Any rendering change updates snapshot intentionally. |
| Rollback strategy |
Remove snapshot tests (more manual QA needed). |
| PR title |
test: snapshot tests for tui rendering |
| PR description |
• insta snapshot tests for rendering. • Tables, code blocks, diffs covered. • CI enforces no drift. |
| Review checklist |
① Snapshots committed. ② CI checks. ③ cargo insta review documented. ④ Coverage: table, code, diff. ⑤ No test regressions. |
| Risk |
Low — adds tests only. |
| Docs |
Document snapshot workflow in CONTRIBUTING.md. |
| Harness impact |
None. |
Week 43: Stress Testing
| Field |
Value |
| Deliverable |
1,000-message session. No memory leaks, OOM, or frame drops. |
| Files touched |
benches/tui_stress.rs (new) |
| New files |
benches/tui_stress.rs |
| Test strategy |
Criterion benchmark: create 1000 messages, render, measure memory and time. |
| Merge criteria |
Memory stable (<100MB). Render time <100ms for 1000 messages. |
| Rollback strategy |
Document performance limits. |
| PR title |
test: stress test for large sessions |
| PR description |
• 1000-message stress test. • Memory and time measured. • No leaks. • Documented limits. |
| Review checklist |
① 1000 messages created. ② Memory <100MB. ③ Render <100ms. ④ No leaks. ⑤ Documented. |
| Risk |
Low — test only. |
| Docs |
Document performance limits. |
| Harness impact |
None. |
Week 44: Windows Testing
| Field |
Value |
| Deliverable |
Verified in PowerShell and Windows Terminal. Fix cursor/resize issues. |
| Files touched |
tui/event.rs (+20), tui/mod.rs (+20) |
| New files |
None |
| Test strategy |
Manual QA on Windows. Automated tests already run in CI (GitHub Actions windows-latest). |
| Merge criteria |
claw tui works on Windows. Resize handled. No cursor glitches. |
| Rollback strategy |
Document Windows as best-effort. |
| PR title |
fix: windows terminal compatibility |
| PR description |
• Windows Terminal compatibility verified. • Resize fixes. • Cursor fixes. • PowerShell tested. |
| Review checklist |
① Opens on Windows. ② Resize works. ③ No cursor artifacts. ④ CI passes. ⑤ PowerShell smoke test. |
| Risk |
Medium — Windows terminal quirks are unpredictable. |
| Docs |
Document Windows setup. |
| Harness impact |
None. |
Week 45: TUI User Guide
| Field |
Value |
| Deliverable |
docs/TUI-USAGE.md — comprehensive user guide with ASCII art screenshots, keybindings, tips. |
| Files touched |
docs/TUI-USAGE.md (created, 300 lines) |
| New files |
docs/TUI-USAGE.md |
| Test strategy |
None — documentation only. |
| Merge criteria |
Document covers: installation, first run, keybindings, slash commands, tips, troubleshooting. |
| Rollback strategy |
N/A — docs. |
| PR title |
docs: tui user guide |
| PR description |
• Comprehensive TUI guide. • ASCII art screenshots. • Keybindings table. • Tips and tricks. |
| Review checklist |
① All keys documented. ② ASCII art accurate. ③ Troubleshooting section. ④ Installation steps. ⑤ Proofread. |
| Risk |
None — docs only. |
| Docs |
Self-documenting. |
| Harness impact |
None. |
Week 46: claw doctor --tui Diagnostic
| Field |
Value |
| Deliverable |
claw doctor --tui runs a self-test: opens TUI, verifies rendering, reports issues. |
| Files touched |
cli.rs (+30), tui/mod.rs (+60) |
| New files |
None |
| Test strategy |
#[test] fn test_doctor_tui_smoke(). |
| Merge criteria |
claw doctor --tui reports TUI health. Useful for bug reports. |
| Rollback strategy |
No --tui flag for doctor. |
| PR title |
feat: tui diagnostic mode in doctor |
| PR description |
• --tui flag for claw doctor. • Self-test rendering. • Reports terminal capabilities, colors, size. |
| Review checklist |
① --tui flag works. ② Reports terminal info. ③ Detects issues. ④ Useful output. ⑤ Tests verify. |
| Risk |
Low — diagnostic only. |
| Docs |
Document doctor TUI mode. |
| Harness impact |
Add doctor_tui scenario. |
Phase G: Advanced Features (Weeks 47–52) — Make It Best-in-Class
Goal: Features that differentiate claw from other AI CLI TUIs.
Week 47: Image Support (Vision Input)
| Field |
Value |
| Deliverable |
When vision input lands in the API (ROADMAP #220), render image attachments inline using sixel or iTerm2 image protocols. Text fallback for unsupported terminals. |
| Files touched |
tui/widgets/image.rs (created, 200 lines), tui/app.rs (+20) |
| New files |
tui/widgets/image.rs |
| Test strategy |
#[test] fn test_image_fallback_text(). #[test] fn test_sixel_detection(). |
| Merge criteria |
Images render on iTerm2/kitty. Fallback text on other terminals. |
| Rollback strategy |
Text placeholder: [image: filename.png]. |
| PR title |
feat: inline image rendering in tui |
| PR description |
• Sixel and iTerm2 image protocols. • Inline rendering. • Text fallback. |
| Review checklist |
① iTerm2 renders. ② kitty renders. ③ Fallback on standard terminal. ④ No panic. ⑤ Tests verify. |
| Risk |
High — image protocols are complex and terminal-specific. |
| Docs |
Document image support. |
| Harness impact |
Deferred until vision API lands. |
Week 48: Notebook Editing
| Field |
Value |
| Deliverable |
/notebook opens notebook editor in split pane (VS Code-like notebook view). |
| Files touched |
tui/widgets/notebook.rs (created, 250 lines), tui/app.rs (+40) |
| New files |
tui/widgets/notebook.rs |
| Test strategy |
#[test] fn test_notebook_renders_cells(). #[test] fn test_notebook_cell_edit(). |
| Merge criteria |
Notebook cells render. Cells editable. Save/execute work. |
| Rollback strategy |
No notebook editing in TUI (use REPL or external editor). |
| PR title |
feat: notebook editing in tui |
| PR description |
• Notebook cell view. • Edit cells in TUI. • Save back to file. |
| Review checklist |
① Cells render. ② Editable. ③ Save works. ④ Execute works. ⑤ Tests verify. |
| Risk |
Medium — notebook format is complex. |
| Docs |
Document notebook editing. |
| Harness impact |
None. |
Week 49: Agent Team Dashboard
| Field |
Value |
| Deliverable |
Ctrl+A shows real-time view of running agents, their status, outputs — like htop for agents. |
| Files touched |
tui/widgets/agent_dashboard.rs (created, 250 lines), tui/app.rs (+40) |
| New files |
tui/widgets/agent_dashboard.rs |
| Test strategy |
#[test] fn test_dashboard_shows_agents(). #[test] fn test_dashboard_updates(). |
| Merge criteria |
Agents listed with status. Output streams visible. Sortable by status. |
| Rollback strategy |
No dashboard (use /agents slash command). |
| PR title |
feat: agent team dashboard in tui |
| PR description |
• Real-time agent monitoring. • Like htop for agents. • Output streams. • Sortable. |
| Review checklist |
① Agents listed. ② Status visible. ③ Output streams. ④ Sort works. ⑤ Tests verify. |
| Risk |
Medium — requires agent runtime integration. |
| Docs |
Document dashboard. |
| Harness impact |
None. |
Week 50: MCP Server Inspector
| Field |
Value |
| Deliverable |
Ctrl+M opens pane showing connected MCP servers, their tools, recent calls. |
| Files touched |
tui/widgets/mcp_inspector.rs (created, 200 lines), tui/app.rs (+20) |
| New files |
tui/widgets/mcp_inspector.rs |
| Test strategy |
#[test] fn test_mcp_inspector_shows_servers(). |
| Merge criteria |
MCP servers listed. Tools visible. Recent calls scrollable. |
| Rollback strategy |
No inspector (use /mcp slash command). |
| PR title |
feat: mcp server inspector sidebar |
| PR description |
• MCP server list. • Tool inventory. • Recent call log. |
| Review checklist |
① Servers listed. ② Tools visible. ③ Calls logged. ④ Scrollable. ⑤ Tests verify. |
| Risk |
Low — MCP data is already available in runtime. |
| Docs |
Document MCP inspector. |
| Harness impact |
None. |
Week 51: Teleport Mode (Fuzzy File Finder)
| Field |
Value |
| Deliverable |
Ctrl+G opens fuzzy file finder (like fzf). Jump to file, send /read tool call. |
| Files touched |
tui/widgets/teleport.rs (created, 180 lines), tui/app.rs (+40) |
| New files |
tui/widgets/teleport.rs |
| Test strategy |
#[test] fn test_teleport_lists_files(). #[test] fn test_teleport_selects_file(). |
| Merge criteria |
Ctrl+G opens finder. Typing filters. Enter selects file. /read sent automatically. |
| Rollback strategy |
No teleport (use /read manually). |
| PR title |
feat: fuzzy file finder teleport in tui |
| PR description |
• Ctrl+G opens file finder. • Fuzzy filter. • Enter sends /read. |
| Review checklist |
① Files listed. ② Filter works. ③ Enter selects. ④ /read sent. ⑤ Tests verify. |
| Risk |
Low — file listing + filter. |
| Docs |
Document teleport mode. |
| Harness impact |
None. |
Week 52: TUI as Default
| Field |
Value |
| Deliverable |
TUI is the default for claw when a TTY is detected. --no-tui or piped input uses REPL. Update ROADMAP.md. Cut release notes. |
| Files touched |
main.rs (+20), cli.rs (+15), ROADMAP.md (+5), docs/RELEASE-NOTES.md (new) |
| New files |
docs/RELEASE-NOTES.md |
| Test strategy |
#[test] fn test_default_tui_on_tty(). #[test] fn test_repl_on_pipe(). |
| Merge criteria |
claw opens TUI on TTY. claw --no-tui opens REPL. Piped input uses REPL. |
| Rollback strategy |
Make REPL default again (one-line change in main.rs). |
| PR title |
feat: make tui the default interactive mode |
| PR description |
• TUI is default when TTY detected. • --no-tui for REPL. • Piped input uses REPL. • Release notes. |
| Review checklist |
① TTY → TUI. ② --no-tui → REPL. ③ Pipe → REPL. ④ Release notes written. ⑤ ROADMAP updated. |
| Risk |
Low — behavioral toggle only. |
| Docs |
Update all docs, release notes. |
| Harness impact |
Update harness for default TUI. |
4. Implementation Specs (Weeks 1–12 in Detail)
Function signatures to preserve:
// app.rs
pub struct LiveCli {
pub client: Arc<ConversationRuntime>,
pub session: Session,
pub config: RuntimeConfig,
pub permission_mode: PermissionMode,
pub tool_executor: Arc<dyn ToolExecutor>,
}
impl LiveCli {
pub fn new(/* ... */) -> Self;
pub fn run(&mut self) -> Result<(), RuntimeError>;
pub fn run_turn(&mut self, prompt: &str) -> Result<TurnResult, RuntimeError>;
}
Extraction steps:
- Copy
main.rs to app.rs.
- In
app.rs, remove fn main() and arg parsing.
- In
main.rs, remove LiveCli struct and impl, keep only fn main() and let mut cli = LiveCli::new(...); cli.run();.
- Add
mod app; and use app::LiveCli; in main.rs.
- Verify
cargo check.
- Run tests.
- Verify
main.rs <200 lines.
Test preservation checklist:
Week 4: TUI Stub — Detailed Spec
// cli.rs
pub enum CliAction {
// ... existing variants ...
Tui,
}
// In main.rs match on CliAction::Tui:
CliAction::Tui => {
tui::run()?;
}
// tui/mod.rs
use std::io;
pub fn run() -> io::Result<()> {
eprintln!("TUI mode coming soon.");
Ok(())
}
Integration test:
// tests/tui_smoke.rs
use std::process::Command;
#[test]
fn test_tui_subcommand_exists() {
let output = Command::new("cargo")
.args(["run", "-p", "rusty-claude-cli", "--", "tui", "--help"])
.output()
.expect("cargo run failed");
assert!(output.status.success(), "claw tui --help should succeed");
}
Week 6: Ratatui Skeleton — Detailed Spec
// tui/mod.rs
use std::io;
use crossterm::event::{self, Event, KeyCode, KeyEvent};
use ratatui::{
backend::{Backend, CrosstermBackend},
widgets::{Block, Borders, Paragraph},
Terminal, Frame,
};
pub fn run() -> io::Result<()> {
ratatui::init();
let result = run_app();
ratatui::restore();
result
}
fn run_app() -> io::Result<()> {
let mut terminal = ratatui::try_init()?;
loop {
terminal.draw(|f| ui(f))?;
if let Event::Key(KeyEvent { code: KeyCode::Char('q'), .. }) = event::read()? {
return Ok(());
}
}
}
fn ui(frame: &mut Frame) {
let area = frame.area();
let paragraph = Paragraph::new("Hello, claw")
.block(Block::default().borders(Borders::ALL).title("claw TUI"));
frame.render_widget(paragraph, area);
}
Cargo.toml changes:
[dependencies]
# existing deps ...
ratatui = "0.29"
crossterm = { version = "0.28", features = ["event-stream"] }
TestBackend test:
#[cfg(test)]
mod tests {
use super::*;
use ratatui::backend::TestBackend;
#[test]
fn test_tui_init_buffer() {
let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap();
terminal.draw(|f| ui(f)).unwrap();
let buffer = terminal.backend().buffer();
// "Hello, claw" is at least 11 chars
let content: String = buffer.content.iter().map(|c| c.symbol()).collect();
assert!(content.contains("Hello, claw"));
}
}
Week 9: Composer Widget — Detailed Spec
// tui/widgets/input.rs
use tui_textarea::TextArea;
pub struct ComposerWidget {
textarea: TextArea<'static>,
history: Vec<String>,
history_index: Option<usize>,
}
impl ComposerWidget {
pub fn new() -> Self;
pub fn handle_event(&mut self, key: KeyEvent) -> ComposerAction;
pub fn text(&self) -> &str;
pub fn clear(&mut self);
}
pub enum ComposerAction {
None,
Submit(String),
HistoryPrev,
HistoryNext,
}
Key mappings:
Enter → Submit(text)
Shift+Enter → insert newline (handled by TextArea)
Ctrl+C → clear
Up → HistoryPrev
Down → HistoryNext
5. Quality Assurance & Testing
5.1 Unit Test Strategy
| Module |
Test Approach |
Backend |
tui/event.rs |
Key event translation |
No backend (pure functions) |
tui/app.rs |
State transitions |
TestBackend |
tui/widgets/chat.rs |
Message rendering, scroll |
TestBackend |
tui/widgets/input.rs |
Key handling, history |
TestBackend |
tui/widgets/markdown.rs |
Markdown parsing → buffer |
TestBackend |
tui/widgets/status_bar.rs |
Layout, content |
TestBackend |
tui/widgets/tool_call.rs |
Lifecycle, expand |
TestBackend |
tui/widgets/permission.rs |
Modal rendering |
TestBackend |
5.2 Integration Test Strategy
// tests/tui_integration.rs
#[tokio::test]
async fn test_full_conversation() {
let mut app = App::new(mock_runtime());
// Simulate user typing "hello"
app.handle_event(AppEvent::Key(KeyCode::Char('h')));
// ... simulate Enter ...
app.handle_event(AppEvent::AssistantDelta("Hi!".to_string()));
// Assert message count == 2
assert_eq!(app.messages.len(), 2);
}
5.3 Mock Parity Harness — New Scenarios
| Scenario |
Week Added |
What It Tests |
tui_stub_smoke |
4 |
claw tui exits cleanly |
tui_smoke_open_close |
6 |
Alternate screen opens and closes |
tui_full_layout_smoke |
12 |
Full layout renders with mock data |
tui_streaming_mock |
13 |
Event loop processes streaming events |
tui_streaming_text |
14 |
Text appears in chat |
tui_session_save_resume |
15 |
Session persistence |
tui_tool_call_display |
16 |
Tool call inline rendering |
tui_permission_prompt |
17 |
Modal overlay |
tui_error_handling |
18 |
Error messages in chat |
tui_multi_turn |
19 |
Multiple conversation turns |
tui_markdown_render |
21 |
Markdown widget |
doctor_tui |
46 |
claw doctor --tui |
5.4 CI Strategy
Add to .github/workflows/rust-ci.yml:
- name: Build with TUI feature
run: cargo build --workspace --features tui
- name: Build without TUI feature
run: cargo build --workspace
- name: Run TUI tests
run: cargo test --workspace --features tui
- name: Run benchmarks
run: cargo bench --workspace --features tui
if: github.ref == 'refs/heads/main'
6. Risk Management & Rollback
6.1 Phase Rollback Matrix
| Phase |
Rollback Command |
Functionality Lost |
Time to Rollback |
| A (Weeks 1–6) |
git revert <week-6-commit> |
TUI stub, claw tui command |
5 min |
| B (Weeks 7–12) |
git checkout HEAD~6 -- tui/ |
Full-screen TUI |
10 min |
| C (Weeks 13–20) |
git revert <week-20-commit> |
Live conversation, streaming |
15 min |
| D (Weeks 21–28) |
Disable markdown widget, use plain text |
Rich rendering |
20 min |
| E (Weeks 29–38) |
Disable search, sidebar, mouse |
Power features |
30 min |
| F (Weeks 39–46) |
Remove snapshot tests, docs |
Tests, docs |
10 min |
| G (Weeks 47–52) |
--no-tui default |
Default-on TUI |
5 min |
6.2 Nuclear Option
If the TUI introduces critical instability:
# Remove TUI code, keep extractions
git checkout main -- rust/crates/rusty-claude-cli/src/tui/
git revert HEAD~N..HEAD # where N = weeks since Week 4
# The REPL and all other functionality are untouched
# `claw tui` command is removed
# `app.rs`, `format.rs`, `session_mgr.rs` remain (pure wins)
7. Reviewer Guide & PR Template
7.1 How to Review a TUI PR
- Check the week number — does this PR match the week's deliverable? If not, ask for scope justification.
- Verify line count — is it ≤400 lines? If not, ask for a split.
- Check tests — does every new file have at least one
#[cfg(test)] block with a TestBackend assertion?
- Check no REPL changes — does the PR touch
input.rs, render.rs, or the REPL loop in ways that could break existing behavior?
- Run
cargo test --workspace — does it pass?
7.2 PR Template for TUI PRs
## Week X: [Deliverable Name]
### What changed
- [One-line summary]
### Files
- `tui/foo.rs` (+N/-M)
- `app.rs` (+N/-M)
### Tests
- [ ] Every new module has a `TestBackend` test
- [ ] `cargo test --workspace` passes
- [ ] `cargo clippy --workspace` clean
- [ ] `cargo fmt --all --check` passes
### Verification
```bash
cd rust
cargo test --workspace
cargo run -p rusty-claude-cli -- tui --help
Rollback
If this PR causes issues: git revert HEAD
Risk
[low/medium/high] — [one sentence]
---
## 8. Appendices
### A. Dependency Matrix
| Crate | Version | License | Purpose | Added Week |
|---|---|---|---|---|
| `ratatui` | `0.29` | MIT | TUI framework, layout engine, widgets | 6 |
| `crossterm` (event-stream) | `0.28` | MIT | Already present; event-stream feature added for async events | 6 |
| `tui-textarea` | `0.7` | MIT | Multi-line text input widget | 9 |
| `unicode-width` | `0.2` | MIT | Correct column counting for CJK/emoji | 41 |
| `arboard` | `3` | MIT | Cross-platform clipboard | 34 |
| `insta` (dev) | `1` | Apache-2.0 | Snapshot testing | 42 |
**MSRV impact:** None. All crates support Rust 1.70+.
### B. Performance Budget
| Metric | Target | Measured At |
|---|---|---|
| Frame rate | 60 FPS (p99 <16ms) | Week 28 |
| Memory per 1000 messages | <100 MB | Week 43 |
| Binary size (without TUI) | No change | Week 20 |
| Binary size (with TUI) | +2–3 MB | Week 20 |
| Clean build time | <5 min | Week 6 |
| Incremental build time | <30 sec | Week 6 |
### C. Glossary
| Term | Definition |
|---|---|
| **TUI** | Terminal User Interface — full-screen, alternate-buffer terminal application |
| **REPL** | Read-Eval-Print Loop — the existing line-based interactive shell |
| **Monolith** | The 11,282-line `main.rs` containing all CLI logic |
| **TestBackend** | ratatui's in-memory terminal buffer for headless testing |
| **EventBroker** | Bridges crossterm events to TUI application events |
| **Composer** | The text input widget at the bottom of the chat |
| **Streaming cell** | The in-progress assistant message being rendered incrementally |
---
*Generated: 2026-06-12 | Author: TheArchitectit | Review status: Draft for upstream PR*
*Based on post-mortem of `feat/ui-hardening`, `fix/ui-parity`, `feat/uiux-redesign`, `rcc/ui-polish`, and `rust/TUI-ENHANCEMENT-PLAN.md`*