tazuna 0.1.0

TUI tool for managing multiple Claude Code sessions in parallel
Documentation
//! Welcome screen widget for empty session state.
//!
//! Displays centered guidance when no sessions exist.

use ratatui::{
    buffer::Buffer,
    layout::{Alignment, Rect},
    style::{Color, Style},
    text::{Line, Span},
    widgets::{Paragraph, Widget, Wrap},
};

/// Welcome screen displayed when no sessions exist.
///
/// Shows centered title and keyboard hint to guide users.
#[derive(Debug, Clone, Default)]
pub struct WelcomeScreen;

impl WelcomeScreen {
    /// Create new welcome screen widget.
    #[must_use]
    pub const fn new() -> Self {
        Self
    }

    /// Content lines for the welcome message.
    fn content_lines() -> Vec<Line<'static>> {
        let style = Style::default().fg(Color::DarkGray);
        vec![
            Line::from(""),
            Line::from(Span::styled("Welcome to tazuna", style)),
            Line::from(""),
            Line::from(Span::styled("Press Ctrl+s to open Workspace", style)),
            Line::from(Span::styled("and create your first session", style)),
        ]
    }

    /// Height of content (excluding block borders).
    const fn content_height() -> u16 {
        5 // 1 blank + title + 1 blank + 2 hint lines
    }
}

impl Widget for WelcomeScreen {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if area.height == 0 || area.width == 0 {
            return;
        }

        let content_height = Self::content_height();
        let vertical_padding = area.height.saturating_sub(content_height) / 2;

        let mut lines: Vec<Line<'static>> = Vec::new();
        for _ in 0..vertical_padding {
            lines.push(Line::from(""));
        }
        lines.extend(Self::content_lines());

        let paragraph = Paragraph::new(lines)
            .alignment(Alignment::Center)
            .wrap(Wrap { trim: false });

        paragraph.render(area, buf);
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::tui::test_utils::{buffer_to_text, render_to_snapshot};

    #[test]
    fn new_creates_instance() {
        let _screen = WelcomeScreen::new();
    }

    #[test]
    fn renders_title() {
        let screen = WelcomeScreen::new();
        let area = Rect::new(0, 0, 60, 15);
        let mut buf = Buffer::empty(area);
        screen.render(area, &mut buf);

        let output = buffer_to_text(&buf);
        assert!(output.contains("Welcome to tazuna"));
    }

    #[test]
    fn renders_ctrl_s_hint() {
        let screen = WelcomeScreen::new();
        let area = Rect::new(0, 0, 60, 15);
        let mut buf = Buffer::empty(area);
        screen.render(area, &mut buf);

        let output = buffer_to_text(&buf);
        assert!(output.contains("Ctrl+s"));
        assert!(output.contains("create your first session"));
    }

    #[test]
    fn handles_zero_height() {
        let screen = WelcomeScreen::new();
        let area = Rect::new(0, 0, 60, 0);
        let mut buf = Buffer::empty(area);
        // Should not panic
        screen.render(area, &mut buf);
    }

    #[test]
    fn handles_zero_width() {
        let screen = WelcomeScreen::new();
        let area = Rect::new(0, 0, 0, 15);
        let mut buf = Buffer::empty(area);
        // Should not panic
        screen.render(area, &mut buf);
    }

    #[test]
    fn handles_minimal_area() {
        let screen = WelcomeScreen::new();
        // Just borders, no inner space
        let area = Rect::new(0, 0, 2, 2);
        let mut buf = Buffer::empty(area);
        // Should not panic
        screen.render(area, &mut buf);
    }

    mod snapshots {
        use super::*;
        use insta::assert_snapshot;

        #[test]
        fn standard_size() {
            let screen = WelcomeScreen::new();
            assert_snapshot!(render_to_snapshot(screen, 60, 15));
        }

        #[test]
        fn small_size() {
            let screen = WelcomeScreen::new();
            assert_snapshot!(render_to_snapshot(screen, 40, 8));
        }

        #[test]
        fn wide_size() {
            let screen = WelcomeScreen::new();
            assert_snapshot!(render_to_snapshot(screen, 80, 10));
        }
    }
}