# Ratatui TUI Guide

This guide documents the enhanced terminal UI system using `ratatui` for `mecha10 dev` mode.

## Overview

**Why Ratatui?**

We've added `ratatui` (formerly tui-rs) to provide sophisticated terminal UIs with features like:
- ✅ Split-pane layouts (sidebar for logs)
- ✅ Scrollable text widgets
- ✅ Real-time updates
- ✅ Color-coded logs
- ✅ Professional terminal interface

**Current Status:** Framework is in place, integration pending.

---

## Architecture

### Components

1. **`LogBuffer`** (`dev_tui.rs`)
   - Thread-safe log collection (Arc<Mutex<Vec<String>>>)
   - Automatic size management (keeps last N logs)
   - Clone-able for sharing across tasks

2. **`DevModeTui`** (`dev_tui.rs`)
   - Example TUI with logs sidebar
   - 60/40 split layout (main content / logs)
   - Scrollable logs with PgUp/PgDn

3. **`TeleopTui`** (`teleop_tui.rs`)
   - Enhanced teleop with real-time logs
   - Status display (velocity, commands sent)
   - Color-coded log levels

---

## Usage Examples

### Basic Example: Standalone TUI

```rust
use mecha10_cli::ui::{LogBuffer, DevModeTui};

#[tokio::main]
async fn main() -> Result<()> {
    // Create log buffer (keeps last 1000 logs)
    let log_buffer = LogBuffer::new(1000);

    // Populate with some test logs
    log_buffer.push("INFO: Starting system".to_string());
    log_buffer.push("WARN: Low battery".to_string());
    log_buffer.push("ERROR: Connection timeout".to_string());

    // Create and run TUI
    let mut tui = DevModeTui::new(log_buffer.clone());
    tui.run()?;

    Ok(())
}
```

### Integration Example: Teleop with Logs

```rust
use mecha10_cli::ui::{LogBuffer, TeleopTui};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

#[tokio::main]
async fn main() -> Result<()> {
    let redis_url = "redis://localhost:6379".to_string();
    let log_buffer = LogBuffer::new(1000);

    // Spawn background task to collect logs from Redis
    tokio::spawn({
        let buffer = log_buffer.clone();
        async move {
            // Subscribe to node logs and populate buffer
            // (implementation depends on your logging setup)
            collect_logs_from_redis(buffer, redis_url.clone()).await
        }
    });

    // Create teleop TUI with logs sidebar
    let running = Arc::new(AtomicBool::new(true));
    let mut teleop = TeleopTui::new(redis_url, log_buffer);
    teleop.run_with_interrupt(running).await?;

    Ok(())
}
```

### Collecting Logs from Nodes

**Option 1: Redis Pub/Sub**

```rust
async fn collect_logs_from_redis(log_buffer: LogBuffer, redis_url: String) -> Result<()> {
    let client = redis::Client::open(redis_url)?;
    let conn = client.get_connection()?;
    let mut pubsub = conn.as_pubsub();

    // Subscribe to log topics
    pubsub.psubscribe("/logs/*")?;

    // Stream logs into buffer
    loop {
        let msg = pubsub.get_message()?;
        if let Ok(log_line) = msg.get_payload::<String>() {
            log_buffer.push(log_line);
        }
    }
}
```

**Option 2: Tracing Subscriber**

```rust
use tracing_subscriber::layer::SubscriberExt;

// Custom layer that sends logs to LogBuffer
struct LogBufferLayer {
    buffer: LogBuffer,
}

impl<S> Layer<S> for LogBufferLayer
where
    S: Subscriber,
{
    fn on_event(&self, event: &Event, _ctx: Context<S>) {
        let log_line = format!("{:?}", event);
        self.buffer.push(log_line);
    }
}

// Setup
let log_buffer = LogBuffer::new(1000);
let subscriber = tracing_subscriber::registry()
    .with(LogBufferLayer { buffer: log_buffer.clone() });
tracing::subscriber::set_global_default(subscriber)?;
```

---

## Layout System

Ratatui uses a constraint-based layout system:

```rust
use ratatui::layout::{Layout, Direction, Constraint};

// Horizontal split: 60% left, 40% right
let chunks = Layout::default()
    .direction(Direction::Horizontal)
    .constraints([
        Constraint::Percentage(60),  // Main content
        Constraint::Percentage(40),  // Logs sidebar
    ])
    .split(frame.area());

// Vertical split: fixed header, flexible content, fixed footer
let chunks = Layout::default()
    .direction(Direction::Vertical)
    .constraints([
        Constraint::Length(3),       // Header
        Constraint::Min(0),          // Content (fills remaining)
        Constraint::Length(1),       // Footer
    ])
    .split(frame.area());
```

---

## Styling

### Colors

```rust
use ratatui::style::{Color, Style, Modifier};

// Basic colors
Style::default().fg(Color::Red)
Style::default().bg(Color::Blue)

// RGB colors
Style::default().fg(Color::Rgb(255, 128, 0))

// Modifiers
Style::default()
    .fg(Color::Cyan)
    .add_modifier(Modifier::BOLD)
    .add_modifier(Modifier::ITALIC)
```

### Log Level Colors

```rust
fn style_for_log(log: &str) -> Style {
    if log.contains("ERROR") {
        Style::default().fg(Color::Red)
    } else if log.contains("WARN") {
        Style::default().fg(Color::Yellow)
    } else if log.contains("INFO") {
        Style::default().fg(Color::Green)
    } else if log.contains("DEBUG") {
        Style::default().fg(Color::Blue)
    } else {
        Style::default().fg(Color::White)
    }
}
```

---

## Widgets

### List (for logs)

```rust
use ratatui::widgets::{List, ListItem};

let items: Vec<ListItem> = logs
    .iter()
    .map(|log| {
        let style = style_for_log(log);
        ListItem::new(log.as_str()).style(style)
    })
    .collect();

let list = List::new(items)
    .block(Block::default()
        .borders(Borders::ALL)
        .title(" Logs "));

frame.render_widget(list, area);
```

### Paragraph (for status)

```rust
use ratatui::widgets::Paragraph;
use ratatui::text::{Line, Span};

let text = vec![
    Line::from(vec![
        Span::raw("Status: "),
        Span::styled("Running", Style::default().fg(Color::Green)),
    ]),
    Line::from(vec![
        Span::raw("Linear: "),
        Span::styled(format!("{:.2}", velocity), Style::default().fg(Color::Cyan)),
    ]),
];

let paragraph = Paragraph::new(text)
    .block(Block::default()
        .borders(Borders::ALL)
        .title(" Status "));

frame.render_widget(paragraph, area);
```

---

## Integration into `mecha10 dev`

### Step 1: Add Log Collection

In `ops.rs` or `session.rs`:

```rust
pub async fn run_interactive_mode(
    ctx: &mut CliContext,
    config: InteractiveModeConfig<'_>,
    state: &mut InteractiveModeState,
) -> Result<()> {
    // Create log buffer
    let log_buffer = LogBuffer::new(1000);

    // Spawn background log collector
    let redis_url = ctx.redis_url().to_string();
    tokio::spawn({
        let buffer = log_buffer.clone();
        async move {
            collect_node_logs(buffer, redis_url).await
        }
    });

    // ... rest of implementation
}
```

### Step 2: Replace Teleop UI

In `enter_teleop_mode()`:

```rust
pub async fn enter_teleop_mode(
    config: &ProjectConfig,
    pids: &HashMap<String, u32>,
    redis_url: &str,
    running: &Arc<AtomicBool>,
    log_buffer: LogBuffer,  // Add this parameter
) -> Result<()> {
    // Check teleop availability...

    if has_teleop && teleop_running {
        // Use enhanced TUI instead of simple UI
        let mut teleop_tui = TeleopTui::new(redis_url.to_string(), log_buffer);
        teleop_tui.run_with_interrupt(running.clone()).await?;
    }

    Ok(())
}
```

### Step 3: Add Feature Flag (Optional)

```toml
# In mecha10.json or as CLI flag
"dev": {
  "enhanced_ui": true,  // Enable ratatui TUI
  "logs_sidebar": true
}
```

```rust
// In code
if config.dev.enhanced_ui {
    // Use ratatui TUI
} else {
    // Use simple crossterm UI
}
```

---

## Keyboard Controls

### Standard Controls

- `↑ / ↓` or `PgUp / PgDn` - Scroll logs
- `q` or `Esc` - Exit
- `Ctrl+C` - Force exit

### Teleop Controls

- `W / ↑` - Forward
- `S / ↓` - Backward
- `A / ←` - Turn left
- `D / →` - Turn right
- `Space / X` - Stop
- `E` - Emergency stop

---

## Performance Considerations

### Log Buffer Size

```rust
// For development (lots of logs)
let log_buffer = LogBuffer::new(10000);

// For embedded/low memory
let log_buffer = LogBuffer::new(500);
```

### Update Rate

```rust
// In event loop
if event::poll(Duration::from_millis(100))? {  // 10 FPS
    // Handle events and redraw
}

// For high-frequency updates
if event::poll(Duration::from_millis(16))? {  // ~60 FPS
    // Handle events and redraw
}
```

### Log Filtering

```rust
impl LogBuffer {
    pub fn push_filtered(&self, log: String, min_level: &str) {
        // Only add logs above minimum level
        if should_log(&log, min_level) {
            self.push(log);
        }
    }
}

fn should_log(log: &str, min_level: &str) -> bool {
    match min_level {
        "ERROR" => log.contains("ERROR"),
        "WARN" => log.contains("WARN") || log.contains("ERROR"),
        "INFO" => !log.contains("DEBUG"),
        _ => true,
    }
}
```

---

## Advanced Features

### Multiple Panes

```rust
// Three-column layout: controls | logs | metrics
let chunks = Layout::default()
    .direction(Direction::Horizontal)
    .constraints([
        Constraint::Percentage(33),  // Controls
        Constraint::Percentage(34),  // Logs
        Constraint::Percentage(33),  // Metrics
    ])
    .split(frame.area());
```

### Tabs

```rust
use ratatui::widgets::Tabs;

let tabs = Tabs::new(vec!["Logs", "Metrics", "Diagnostics"])
    .select(selected_tab)
    .style(Style::default().fg(Color::White))
    .highlight_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));

frame.render_widget(tabs, area);
```

### Charts

```rust
use ratatui::widgets::{Chart, Dataset, GraphType, Axis};

let datasets = vec![
    Dataset::default()
        .name("Velocity")
        .marker(Marker::Dot)
        .graph_type(GraphType::Line)
        .style(Style::default().fg(Color::Cyan))
        .data(&velocity_history),
];

let chart = Chart::new(datasets)
    .block(Block::default().title("Velocity History"))
    .x_axis(Axis::default().title("Time"))
    .y_axis(Axis::default().title("m/s"));

frame.render_widget(chart, area);
```

---

## Debugging TUI

### Terminal State Issues

If terminal gets corrupted:

```bash
# Reset terminal
reset

# Or clear and reset
clear && reset
```

### Adding Debug Logs

```rust
// Write debug logs to file instead of terminal
use std::fs::OpenOptions;
use std::io::Write;

let mut file = OpenOptions::new()
    .create(true)
    .append(true)
    .open("/tmp/mecha10_tui_debug.log")?;

writeln!(file, "DEBUG: Current state: {:?}", self)?;
```

---

## Next Steps

1. **Integrate into dev mode**
   - Add `LogBuffer` to `DevSession`
   - Spawn log collection task
   - Replace simple UI with ratatui TUI

2. **Add metrics panel**
   - CPU, memory, network usage
   - Message throughput
   - Node status

3. **Add search/filter**
   - Filter logs by level
   - Search logs by keyword
   - Regex matching

4. **Add export**
   - Save logs to file
   - Export metrics to CSV
   - Screenshot current view

---

## Resources

- **Ratatui Docs**: https://ratatui.rs/
- **Examples**: https://github.com/ratatui/ratatui/tree/main/examples
- **Widgets**: https://docs.rs/ratatui/latest/ratatui/widgets/index.html
- **Crossterm Backend**: https://docs.rs/crossterm/latest/crossterm/

---

## FAQ

**Q: Why not just use `println!` for logs?**
A: In teleop mode, you need to see both controls and logs simultaneously. A TUI provides this without terminal conflicts.

**Q: Can I use this in embedded systems?**
A: Yes, but consider memory constraints. Use a smaller log buffer and lower update rate.

**Q: Does this work over SSH?**
A: Yes! Ratatui works perfectly over SSH connections.

**Q: Can I add custom panels?**
A: Absolutely! See "Advanced Features" section for examples.
