tododo 0.1.2

A minimal terminal todo manager built with Rust and Ratatui
Documentation
use ratatui::widgets::TableState;
use ratatui_textarea::TextArea;
use std::cell::RefCell;

use crate::domain::todo::Todo;
use crate::domain::SortMode;

#[derive(Debug, Clone, PartialEq)]
pub enum Screen {
    List,
    Detail,
    NoteEditor,
    CreateTodo,
    DeleteConfirm,
}

pub struct App {
    pub screen: Screen,
    pub previous_screen: Option<Screen>,
    pub todos: Vec<Todo>,
    pub list_state: TableState,
    pub selected_id: Option<String>,
    pub error_message: Option<String>,
    pub should_quit: bool,
    pub sort_mode: SortMode,
    pub note_editor: Option<RefCell<TextArea<'static>>>,
    pub create_todo_editor: Option<RefCell<TextArea<'static>>>,
}

impl Default for App {
    fn default() -> Self {
        Self::new()
    }
}

impl App {
    pub fn new() -> Self {
        Self {
            screen: Screen::List,
            previous_screen: None,
            todos: Vec::new(),
            list_state: TableState::default(),
            selected_id: None,
            error_message: None,
            should_quit: false,
            sort_mode: SortMode::Priority,
            note_editor: None,
            create_todo_editor: None,
        }
    }

    pub fn init_note_editor(&mut self, content: String) {
        let lines: Vec<String> = if content.is_empty() {
            vec![]
        } else {
            content.lines().map(|s| s.to_string()).collect()
        };
        let ta = TextArea::new(lines);
        self.note_editor = Some(RefCell::new(ta));
    }

    pub fn init_create_todo_editor(&mut self) {
        let mut ta = TextArea::new(vec![String::new()]);
        ta.set_placeholder_text("Enter todo title...");
        self.create_todo_editor = Some(RefCell::new(ta));
    }

    pub fn select_previous(&mut self) {
        let len = self.todos.len();
        if len == 0 {
            return;
        }
        let current = self.list_state.selected().unwrap_or(0);
        let new_idx = if current == 0 { len - 1 } else { current - 1 };
        self.list_state.select(Some(new_idx));
        self.selected_id = self.todos.get(new_idx).map(|t| t.id.clone());
    }

    pub fn select_next(&mut self) {
        let len = self.todos.len();
        if len == 0 {
            return;
        }
        let current = self.list_state.selected().unwrap_or(0);
        let new_idx = if current >= len - 1 { 0 } else { current + 1 };
        self.list_state.select(Some(new_idx));
        self.selected_id = self.todos.get(new_idx).map(|t| t.id.clone());
    }

    /// Transitions to a new screen, tracking the current screen as previous.
    /// This ensures Esc can always return to the correct previous screen.
    pub fn transition_to(&mut self, screen: Screen) {
        self.previous_screen = Some(self.screen.clone());
        self.screen = screen;
    }
}