This document summarizes how VT Code applies best practices from the Ratatui FAQ.
VT Code is built on Ratatui and uses its terminal UI patterns extensively. The Ratatui FAQ documents many important patterns and anti-patterns. This integration ensures VT Code follows these best practices consistently.
Ratatui FAQ: Why am I getting duplicate key events on Windows?
VT Code Implementation:
- File:
src/tui.rs:152-159 - Pattern: Filter crossterm events to
KeyEventKind::Pressonly - Benefit: Prevents duplicate key events on Windows while working correctly on macOS/Linux
if key.kind == KeyEventKind::Press {
let _ = _event_tx.send(Event::Key(key));
}Ratatui FAQ: When should I use tokio and async/await?
VT Code Implementation:
- File:
src/tui.rs:118-182(event loop),src/agent/runloop/unified/(tool execution) - Pattern: Use
tokio::select!to multiplex independent event sources - Benefit: Non-blocking event handling, concurrent tool execution, streaming responses
Three reasons VT Code uses async:
- Event multiplexing: Terminal input, ticks, renders in one async loop
- Concurrent tool execution: MCP tools, PTY sessions, LLM calls run in parallel
- Streaming responses: Token-by-token streaming without blocking the event loop
Ratatui FAQ: Can you use multiple terminal.draw() calls consequently?
VT Code Implementation:
-
File:
vtcode-core/src/ui/tui/session.rs:render()method -
Pattern: All UI components render in a single
terminal.draw()closure per frame -
Benefit: Correct use of Ratatui's double buffering; only one screen update per frame
Incorrect pattern (avoided in VT Code):
terminal.draw(|f| widget1.render(...))?;
terminal.draw(|f| widget2.render(...))?; // This overwrites the first!
terminal.draw(|f| widget3.render(...))?;Correct pattern (used in VT Code):
terminal.draw(|f| {
widget1.render(...);
widget2.render(...);
widget3.render(...);
})?;Ratatui FAQ: Should I use stdout or stderr?
VT Code Implementation:
- File:
src/tui.rs:73 - Choice: Renders to
stderr(viaCrosstermBackend::new(std::io::stderr())) - Benefit: Allows piping output (
vtcode ask "task" | jq) without breaking the TUI
Why stderr over stdout:
- No special TTY detection needed
- Works out-of-the-box in pipes
- Unconventional but more flexible
Ratatui FAQ: Can you change font size in a terminal using ratatui?
VT Code Implementation:
- File:
src/tui.rs:44(Resize event definition) - Pattern: Listen for
Event::Resize(w, h)and recalculate layout - Benefit: VT Code adapts gracefully to terminal size changes
Ratatui FAQ: Spawn vim from Ratatui (recipe, not FAQ)
VT Code Implementation:
- File:
src/tui.rs:303-357(ExternalAppLauncher trait) - Pattern: Suspend TUI → disable raw mode → run external app → resume TUI
- Critical step: Drain pending events before disabling raw mode
- Benefit: Clean terminal state for external editors/git clients
Ratatui FAQ: How do I avoid panics due to out of range calls on the Buffer?
VT Code Implementation:
- Pattern: Use
area.intersection(buf.area)to clamp rendering regions - Iterators: Use
Rect::columns()andRect::rows()for safe iteration - Benefit: Prevents panics from off-bounds rendering
VT Code now includes comprehensive guides based on these best practices:
Common questions about VT Code's architecture, terminal behavior, and configuration. Includes Q&A on duplicate key events, async usage, terminal resizing, and character rendering.
Detailed guide to VT Code's event-driven architecture:
- Platform-specific event filtering
- Async event loop with tokio::select!
- Graceful shutdown patterns
- External app suspension
- Event types and configuration
- Best practices and anti-patterns
Comprehensive guide to VT Code's async/tokio design:
- When and why VT Code uses async
- Tokio patterns (event handler, blocking I/O, concurrent tasks)
- Graceful shutdown with CancellationToken
- Shared state management
- Anti-patterns and pitfalls
- Integration with the main event loop
Guide to widget rendering and UI composition:
- Single-draw pattern explanation
- Viewport management and double buffering
- Layout computation and constraint-based designs
- Widget composition patterns
- Text reflow and terminal resize handling
- Color and styling in Ratatui
- Performance considerations
- Common rendering issues and solutions
Added clarifying comments to src/tui.rs explaining the cross-platform key event filtering:
// Filter to Press events only for cross-platform compatibility.
// Windows emits both KeyEventKind::Press and KeyEventKind::Release for each
// keypress, while macOS and Linux emit only Press. This prevents duplicate key
// events on Windows. See https://ratatui.rs/faq/#why-am-i-getting-duplicate-key-events-on-windowsVT Code includes tests for event handling patterns:
- Key event filtering: Verified via
src/interactive_list.rsand event handler tests - Async patterns: Tested via
#[tokio::test]throughout the codebase - Rendering: Tested via Ratatui's
TestBackendinvtcode-core
This integration improves VT Code by:
- Correctness: Ensures cross-platform compatibility (Windows key events)
- Performance: Confirms async architecture for non-blocking event handling
- Robustness: Applies defensive programming (bounds checking, graceful shutdown)
- Maintainability: Documents why architectural decisions were made
- Developer Experience: Provides clear patterns for future contributions
- docs/FAQ.md - VT Code FAQ
- docs/guides/tui-event-handling.md - Event handling guide
- docs/guides/async-architecture.md - Async architecture guide
- docs/guides/terminal-rendering-best-practices.md - Rendering guide
- docs/ARCHITECTURE.md - VT Code system architecture