# TUI Integration Guide Complete guide for integrating RataTui-based TUI into any Tools ecosystem project. ## Overview The `tools-tui-shared` library provides a production-ready foundation for building terminal user interfaces (TUIs) across the Tools ecosystem. All TUI implementations follow strict architectural patterns and code quality standards. ## Quick Start: 5 Steps ### Step 1: Add Dependency ```toml [dependencies] tools-tui-shared = { path = "../../../shared/rust-tui" } ratatui = { version = "0.30.0-beta.0", features = ["all-widgets"] } crossterm = "0.29" tui-textarea = "0.7" ``` ### Step 2: Create App State ```rust use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ScreenType { List, Detail, } #[derive(Clone, Debug)] pub struct AppState { pub current_screen: ScreenType, pub items: Vec, pub selected_index: usize, pub dirty: bool, pub should_quit: bool, } impl AppState { pub fn new() -> Self { AppState { current_screen: ScreenType::List, items: Vec::new(), selected_index: 0, dirty: true, should_quit: false, } } } ``` ### Step 3: Implement Screens ```rust use crate::app::AppState; use tools_tui_shared::prelude::*; use ratatui::prelude::*; use ratatui::widgets::{Block, Borders, List, ListItem}; pub struct ListScreen; impl ListScreen { pub fn render(state: &AppState, frame: &mut ratatui::Frame, area: Rect) { let theme = Theme::dark(); let items: Vec = state.items.iter() .enumerate() .map(|(idx, item)| { let marker = if idx == state.selected_index { "★ " } else { " " }; ListItem::new(format!("{}{}", marker, item.name)) }) .collect(); let list = List::new(items) .block(Block::default() .title("Items") .borders(Borders::ALL) .border_style(theme.focused_style())) .style(theme.normal_style()); frame.render_widget(list, area); } } ``` ### Step 4: Create lib.rs ```rust #![forbid(unsafe_code)] #![warn(missing_docs)] pub mod app; pub mod screens; pub use app::{AppState, ScreenType}; pub mod prelude { pub use crate::{AppState, ScreenType}; pub use crate::screens::{ListScreen, DetailScreen}; } ``` ### Step 5: Run Tests ```bash cargo test --lib ``` ## Architecture Patterns ### R-STATE-SEPARATION **Principle**: Complete separation of state from rendering logic. ```rust // ✅ CORRECT: State methods don't render impl AppState { pub fn select_next(&mut self) { if !self.items.is_empty() { self.selected_index = (self.selected_index + 1) % self.items.len(); self.dirty = true; } } } // ✅ CORRECT: Render methods only read state impl ListScreen { pub fn render(state: &AppState, frame: &mut ratatui::Frame, area: Rect) { // Never call state.select_next() or other mutation methods // Only read: state.selected_index, state.items, etc. } } // ❌ WRONG: Don't put rendering logic in state impl AppState { pub fn render(&self) { } // DON'T DO THIS } ``` ### R-WIDGET-COMPOSITION **Principle**: Build complex UIs from simple, reusable widgets. ```rust // Use shared widgets from tools-tui-shared use tools_tui_shared::widgets::{Menu, Form, Table, StatusBar}; // Compose widgets for your screens let menu = Menu::new(vec![ MenuItem::new("Save".to_string()).with_key_binding("Ctrl+S"), MenuItem::new("Exit".to_string()).with_key_binding("Ctrl+Q"), ]); let form = Form::new() .add_field(FormField::new("name", FieldType::Text)) .add_field(FormField::new("email", FieldType::Text)); ``` ### R-CONSISTENT-THEMING **Principle**: Single theme source for all colors and styles. ```rust let theme = Theme::dark(); // All UI elements use the same theme let primary_style = theme.selected_style(); // Header, emphasis let success_style = theme.success_style(); // Success messages let warning_style = theme.warning_style(); // Warnings let error_style = theme.error_style(); // Errors let normal_style = theme.normal_style(); // Default text let dimmed_style = theme.dimmed_style(); // Disabled/background ``` ### R-EVENT-LOOP **Principle**: Non-blocking event polling with timeout. ```rust use tools_tui_shared::events::EventHandler; let event_handler = EventHandler::new(100)?; // 100ms tick rate loop { if app_state.dirty { terminal.draw(|f| render(f, &app_state))?; app_state.reset_dirty(); } // Non-blocking poll with timeout match event_handler.poll()? { AppEvent::Tick => { /* Update */}, AppEvent::Key(key) => { /* Handle key */}, AppEvent::Mouse(evt) => { /* Handle mouse */}, AppEvent::Resize(w, h) => { /* Handle resize */}, } } ``` ### R-GRACEFUL-SHUTDOWN **Principle**: RAII pattern ensures terminal cleanup on exit. ```rust use tools_tui_shared::terminal::TerminalGuard; // Terminal is automatically set up and cleaned up let _guard = TerminalGuard::setup()?; let mut terminal = _guard.terminal()?; // Even if panic occurs, terminal is restored // Drop trait automatically handles cleanup ``` ## Common Patterns ### Dirty Flag Optimization Only redraw when state changes: ```rust pub fn is_dirty(&self) -> bool { self.dirty } pub fn reset_dirty(&mut self) { self.dirty = false; } pub fn mark_dirty(&mut self) { self.dirty = true; } // In main event loop: if app_state.is_dirty() { terminal.draw(|f| render(f, &app_state))?; app_state.reset_dirty(); } ``` ### Selection Navigation Implement consistent navigation: ```rust pub fn select_next(&mut self) { if !self.items.is_empty() { self.selected_index = (self.selected_index + 1) % self.items.len(); self.dirty = true; } } pub fn select_previous(&mut self) { if !self.items.is_empty() { self.selected_index = if self.selected_index == 0 { self.items.len() - 1 } else { self.selected_index - 1 }; self.dirty = true; } } ``` ### Screen Navigation Manage multiple screens: ```rust pub fn set_screen(&mut self, screen: ScreenType) { if self.current_screen != screen { self.current_screen = screen; self.dirty = true; } } // In render loop: match state.current_screen { ScreenType::List => ListScreen::render(state, frame, area), ScreenType::Detail => DetailScreen::render(state, frame, area), } ``` ## Testing Guidelines Each module should have comprehensive tests: ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_app_state_new() { let state = AppState::new(); assert!(state.dirty); assert!(!state.should_quit); } #[test] fn test_select_next() { let mut state = AppState::new(); state.items.push(/* ... */); state.select_next(); assert_eq!(state.selected_index, 1); } #[test] fn test_screen_navigation() { let mut state = AppState::new(); state.set_screen(ScreenType::Detail); assert_eq!(state.current_screen, ScreenType::Detail); } } ``` **Minimum test coverage**: 15+ tests per module ## Code Quality Checklist - [ ] `#![forbid(unsafe_code)]` at crate root - [ ] `#![warn(missing_docs)]` for public items - [ ] All public types implement Debug - [ ] All public functions documented with examples - [ ] Result for all fallible operations - [ ] No unwrap() in production code - [ ] 15+ tests per module - [ ] cargo fmt applied - [ ] cargo clippy passes - [ ] cargo test --workspace passes ## Integration with Dashboard To register your tool in the multi-tool dashboard: ```rust use tools_tui_shared::integration::ToolSummaryProvider; pub struct MyToolTui; impl ToolSummaryProvider for MyToolTui { fn name(&self) -> &str { "my-tool" } fn summary(&self) -> Result { Ok(ToolSummary::new( "my-tool".to_string(), "My Tool".to_string(), "Description of my tool".to_string(), ToolMetrics::new(10, 5, 3, chrono::Utc::now().to_rfc2822()), ).with_commands(vec!["list".to_string(), "create".to_string()])) } fn execute_command(&self, command: &str, _args: &[String]) -> Result { match command { "list" => Ok("Listing items...".to_string()), "create" => Ok("Creating item...".to_string()), _ => Err(format!("Unknown command: {}", command)), } } } ``` ## Common Issues & Solutions ### Issue: Compilation errors with ratatui widgets **Solution**: Ensure you're using ratatui 0.30.0-beta.0 or later: ```toml ratatui = { version = "0.30.0-beta.0", features = ["all-widgets", "underline-color"] } ``` ### Issue: Terminal not being cleaned up properly **Solution**: Use TerminalGuard: ```rust use tools_tui_shared::terminal::TerminalGuard; let _guard = TerminalGuard::setup()?; let mut terminal = _guard.terminal()?; // Terminal is auto-cleaned up when _guard is dropped ``` ### Issue: UI not updating **Solution**: Check dirty flag logic: ```rust // Always mark dirty when state changes pub fn some_action(&mut self) { self.items.push(new_item); self.dirty = true; // Don't forget this! } // Always reset dirty after rendering if app_state.is_dirty() { terminal.draw(|f| render(f, &app_state))?; app_state.reset_dirty(); // Don't forget this! } ``` ## Performance Tips 1. **Use dirty flag** to avoid unnecessary redraws 2. **Limit event polling** with reasonable tick rates (100-200ms) 3. **Cache theme** instead of creating new Theme each render 4. **Use Constraint::Percentage** for responsive layouts instead of hardcoded sizes 5. **Avoid cloning** large data structures in render methods ## References - **RataTui Documentation**: https://ratatui.rs - **Crossterm Documentation**: https://docs.rs/crossterm - **Tools Shared Library**: `/Users/Akasha/Tools/shared/rust-tui/` - **Example Implementation**: `/Users/Akasha/Tools/hello-tool/crates/hello-tui/` ## Getting Help 1. Check existing tool implementations in `/Users/Akasha/Tools/*/crates/*-tui/` 2. Review `tools-tui-shared` source code for patterns 3. Run tests to validate your implementation 4. Check compilation with `cargo check` 5. Lint with `cargo clippy --all-targets --all-features`