tazuna 0.1.0

TUI tool for managing multiple Claude Code sessions in parallel
Documentation
//! Application state types.
//!
//! State machine and snapshot types for TUI application.

use crate::session::{SessionId, SessionStatus};
use crate::tui::popup::LoadingOperation;

/// Flow context for error recovery transitions
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowContext {
    /// Base state
    Normal,
    /// Workspace popup flow
    WorkspaceFlow,
    /// Issue selection flow
    IssueFlow,
}

/// Application state machine
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum AppState {
    /// Normal mode - terminal view with tab bar
    #[default]
    Normal,
    /// Workspace popup displayed (sessions + worktrees)
    WorkspacePopup,
    /// Confirm quit popup displayed
    ConfirmQuit,
    /// Error popup displayed
    ErrorPopup {
        /// Error message to display
        message: String,
        /// True if error occurred from workspace popup
        from_popup: bool,
    },
    /// Session terminated popup (Close/Keep choice)
    SessionTerminatedPopup {
        /// Terminated session ID
        session_id: SessionId,
        /// Exit code if available
        exit_code: Option<i32>,
    },
    /// Loading state for issue (fetching details + generating actions)
    IssueLoading {
        /// Issue number being loaded
        issue_number: u32,
        /// Current loading phase
        phase: LoadingOperation,
    },
    /// Action selection popup for issue
    ActionSelectPopup {
        /// Selected issue number
        issue_number: u32,
        /// Generated action choices
        choices: Vec<crate::github::ActionChoice>,
    },
    /// Confirm dangerous permissions popup
    ConfirmPermissions {
        /// Branch to be created
        branch: String,
        /// Prompt to be injected
        prompt: String,
        /// Whether "Yes" is selected
        selected_yes: bool,
    },
}

impl AppState {
    /// Determine flow context for error recovery
    #[must_use]
    pub fn flow_context(&self) -> FlowContext {
        match self {
            Self::Normal | Self::ConfirmQuit | Self::SessionTerminatedPopup { .. } => {
                FlowContext::Normal
            }
            Self::WorkspacePopup => FlowContext::WorkspaceFlow,
            Self::IssueLoading { .. }
            | Self::ActionSelectPopup { .. }
            | Self::ConfirmPermissions { .. } => FlowContext::IssueFlow,
            Self::ErrorPopup { from_popup, .. } => {
                if *from_popup {
                    FlowContext::WorkspaceFlow
                } else {
                    FlowContext::Normal
                }
            }
        }
    }
}

/// Session snapshot for display
#[derive(Debug, Clone)]
pub struct SessionSnapshot {
    /// Session ID
    pub id: SessionId,
    /// Session name
    pub name: String,
    /// Session status
    pub status: SessionStatus,
    /// Branch name (if associated with a worktree)
    pub branch: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::rstest;

    #[rstest]
    #[case(AppState::Normal, FlowContext::Normal)]
    #[case(AppState::ConfirmQuit, FlowContext::Normal)]
    #[case(AppState::SessionTerminatedPopup { session_id: SessionId::new_v4(), exit_code: None }, FlowContext::Normal)]
    #[case(AppState::WorkspacePopup, FlowContext::WorkspaceFlow)]
    #[case(AppState::IssueLoading { issue_number: 1, phase: LoadingOperation::FetchingIssue { issue_number: 1 } }, FlowContext::IssueFlow)]
    #[case(AppState::ActionSelectPopup { issue_number: 1, choices: vec![] }, FlowContext::IssueFlow)]
    #[case(AppState::ConfirmPermissions { branch: String::new(), prompt: String::new(), selected_yes: false }, FlowContext::IssueFlow)]
    #[case(AppState::ErrorPopup { message: String::new(), from_popup: true }, FlowContext::WorkspaceFlow)]
    #[case(AppState::ErrorPopup { message: String::new(), from_popup: false }, FlowContext::Normal)]
    fn flow_context_mapping(#[case] state: AppState, #[case] expected: FlowContext) {
        assert_eq!(state.flow_context(), expected);
    }
}