AI Skill Report Card
Building Tui Apps Rust
YAML--- name: building-tui-apps-rust description: Builds terminal user interfaces in Rust using ratatui with interactive lists and selection capabilities. Use when creating CLI tools that need rich terminal interfaces with navigation, selection, and real-time updates. --- # Building TUI Apps in Rust
Quick Start
Rust// Cargo.toml [dependencies] ratatui = "0.26" crossterm = "0.27" tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } reqwest = { version = "0.11", features = ["json"] } // src/main.rs use ratatui::{ backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, style::{Color, Modifier, Style}, text::Span, widgets::{Block, Borders, List, ListItem, ListState}, Frame, Terminal, }; use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use std::io; #[derive(Clone)] struct Repo { name: String, url: String, marked_for_deletion: bool, } struct App { repos: Vec<Repo>, selected: usize, list_state: ListState, } impl App { fn new() -> Self { let mut state = ListState::default(); state.select(Some(0)); Self { repos: vec![], selected: 0, list_state: state, } } fn toggle_selection(&mut self) { if let Some(selected) = self.list_state.selected() { self.repos[selected].marked_for_deletion = !self.repos[selected].marked_for_deletion; } } }
Recommendation▾
Add complete working examples showing the GitHub API integration and async handling - current examples only show UI components in isolation
Workflow
Progress:
- Set up project structure with ratatui and crossterm
- Implement GitHub API client for fetching repositories
- Create main app state management
- Build interactive list component with selection
- Add keyboard navigation (arrows, space, enter)
- Implement deletion marking/unmarking
- Add confirmation dialog for batch deletion
- Handle API calls for actual deletion
- Add error handling and status messages
Core Implementation Steps
- Terminal Setup
Rustfn setup_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>, Box<dyn std::error::Error>> { enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let terminal = Terminal::new(backend)?; Ok(terminal) }
- Event Loop
Rustfn run_app() -> Result<(), Box<dyn std::error::Error>> { let mut terminal = setup_terminal()?; let mut app = App::new(); loop { terminal.draw(|f| ui(f, &mut app))?; if let Event::Key(key) = event::read()? { match key.code { KeyCode::Char('q') => break, KeyCode::Down => app.next(), KeyCode::Up => app.previous(), KeyCode::Char(' ') => app.toggle_selection(), KeyCode::Enter => app.confirm_deletions(), _ => {} } } } restore_terminal()?; Ok(()) }
- UI Rendering
Rustfn ui(f: &mut Frame, app: &mut App) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(3)]) .split(f.size()); let items: Vec<ListItem> = app.repos .iter() .map(|repo| { let style = if repo.marked_for_deletion { Style::default().fg(Color::Red).add_modifier(Modifier::CROSSED_OUT) } else { Style::default() }; let prefix = if repo.marked_for_deletion { "❌ " } else { " " }; ListItem::new(Span::styled(format!("{}{}", prefix, repo.name), style)) }) .collect(); let list = List::new(items) .block(Block::default().borders(Borders::ALL).title("Repositories")) .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black)); f.render_stateful_widget(list, chunks[0], &mut app.list_state); }
Recommendation▾
Include actual error handling patterns and recovery strategies rather than just mentioning them in best practices
Examples
Example 1: Basic Repository List Input: GitHub API response with user repositories
JSON[ {"name": "old-project", "html_url": "https://github.com/user/old-project"}, {"name": "test-repo", "html_url": "https://github.com/user/test-repo"} ]
Output: Interactive TUI showing:
┌Repositories──────────────────┐
│ old-project │
│ > test-repo │
└──────────────────────────────┘
[Space] Mark/Unmark [Enter] Delete Selected [q] Quit
Example 2: Marked for Deletion After pressing Space on selected items:
┌Repositories──────────────────┐
│ ❌ old-project │
│ > test-repo │
└──────────────────────────────┘
Recommendation▾
Provide a complete minimal working application that can be copied and run immediately, not just fragmented code snippets
Best Practices
- Use
ListStatefor proper selection management - Implement proper error boundaries for API calls
- Use
tokio::spawnfor non-blocking GitHub API requests - Store GitHub token in environment variables, not code
- Implement confirmation dialogs for destructive actions
- Use consistent color coding (red for deletion, yellow for selection)
- Handle terminal resize events gracefully
- Provide clear visual feedback for all actions
Common Pitfalls
- Blocking the UI thread - Always use async/await for API calls
- Not handling terminal restoration - Always restore terminal state on exit
- Forgetting rate limits - GitHub API has rate limits, implement backoff
- No confirmation step - Deletion should require explicit confirmation
- Poor error messages - Network errors need user-friendly display
- Missing escape handling - Always provide quit mechanism (q key)
- State desync - Keep list selection in bounds when filtering/removing items
Rust// Good: Proper cleanup impl Drop for App { fn drop(&mut self) { let _ = disable_raw_mode(); let _ = execute!( io::stdout(), LeaveAlternateScreen, DisableMouseCapture ); } }