libnoa 0.1.1

AI-native distributed version control system with per-agent workspace isolation, JSONL append-only logs, snapshot-based history, and full git protocol compatibility
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppMode {
    Log,
    Branches,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
    Branches,
    Log,
    Detail,
}

impl Focus {
    pub fn cycle(self, mode: AppMode) -> Self {
        match mode {
            AppMode::Branches => match self {
                Focus::Branches => Focus::Log,
                Focus::Log => Focus::Detail,
                Focus::Detail => Focus::Branches,
            },
            AppMode::Log => match self {
                Focus::Log => Focus::Detail,
                Focus::Detail => Focus::Log,
                _ => Focus::Log,
            },
        }
    }

    pub fn cycle_back(self, mode: AppMode) -> Self {
        match mode {
            AppMode::Branches => match self {
                Focus::Branches => Focus::Detail,
                Focus::Log => Focus::Branches,
                Focus::Detail => Focus::Log,
            },
            AppMode::Log => match self {
                Focus::Log => Focus::Detail,
                Focus::Detail => Focus::Log,
                _ => Focus::Detail,
            },
        }
    }
}

pub struct App {
    pub mode: AppMode,
    pub focus: Focus,
    pub branches: Vec<crate::workspace::Workspace>,
    pub snapshots: Vec<crate::snapshot::Snapshot>,
    pub current_branch: String,
    pub branch_scroll: super::VirtualScroll,
    pub log_scroll: super::VirtualScroll,
    pub should_quit: bool,
}

impl App {
    pub fn for_log(snapshots: Vec<crate::snapshot::Snapshot>, current_branch: String) -> Self {
        let count = snapshots.len();
        Self {
            mode: AppMode::Log,
            focus: Focus::Log,
            branches: vec![],
            snapshots,
            current_branch,
            branch_scroll: super::VirtualScroll::new(0),
            log_scroll: super::VirtualScroll::new(count),
            should_quit: false,
        }
    }

    pub fn for_branches(
        branches: Vec<crate::workspace::Workspace>,
        snapshots: Vec<crate::snapshot::Snapshot>,
        current_branch: String,
    ) -> Self {
        let branch_count = branches.len();
        let log_count = snapshots.len();
        Self {
            mode: AppMode::Branches,
            focus: Focus::Branches,
            branches,
            snapshots,
            current_branch,
            branch_scroll: super::VirtualScroll::new(branch_count),
            log_scroll: super::VirtualScroll::new(log_count),
            should_quit: false,
        }
    }

    pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> bool {
        use crossterm::event::{KeyCode, KeyModifiers};

        match (key.modifiers, key.code) {
            (_, KeyCode::Char('q') | KeyCode::Esc) => {
                self.should_quit = true;
                return true;
            }
            (_, KeyCode::Tab) => {
                self.focus = self.focus.cycle(self.mode);
            }
            (KeyModifiers::SHIFT, KeyCode::BackTab) => {
                self.focus = self.focus.cycle_back(self.mode);
            }
            (_, KeyCode::Up | KeyCode::Char('k')) => self.scroll_up(),
            (_, KeyCode::Down | KeyCode::Char('j')) => self.scroll_down(),
            (KeyModifiers::CONTROL, KeyCode::Char('b')) => {
                if self.mode == AppMode::Log {
                    self.mode = AppMode::Branches;
                    self.focus = Focus::Branches;
                } else {
                    self.mode = AppMode::Log;
                    self.focus = Focus::Log;
                }
            }
            _ => {}
        }
        false
    }

    fn scroll_up(&mut self) {
        match self.focus {
            Focus::Branches => self.branch_scroll.scroll_up(1),
            Focus::Log => self.log_scroll.scroll_up(1),
            Focus::Detail => {}
        }
    }

    fn scroll_down(&mut self) {
        match self.focus {
            Focus::Branches => self.branch_scroll.scroll_down(1),
            Focus::Log => self.log_scroll.scroll_down(1),
            Focus::Detail => {}
        }
    }

    pub fn selected_snapshot(&self) -> Option<&crate::snapshot::Snapshot> {
        let idx = self.log_scroll.selected_index()?;
        self.snapshots.get(idx)
    }
}