yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
//! The keyboard-navigation keymap (DESIGN §11): a **pure** key → intent table.
//!
//! Keyboard nav is split the same way every widget is (§12): a pure, tested
//! mapping here plus thin egui event plumbing in `src/shell/*` (coverage-
//! excluded) that lifts an `egui::Key` into a [`Key`] and dispatches the
//! resulting [`KeyAction`] to the matching [`AppModel`](crate::AppModel) call.
//! Nothing in this module touches egui, so every branch is table-tested.
//!
//! The bindings (§11 three altitudes): ↑/↓ step the focus through the roster's
//! flattened (workspace, agent) order — the altitude-0/1 selection — and the
//! digit keys select an altitude-2 inspector tab.

/// A logical key the shell has already lifted out of an egui event (the
/// `egui::Key` → this translation is the excluded plumbing). `Digit` carries
/// the pressed digit's value (1–9); other keys are named directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {
    Up,
    Down,
    Digit(u8),
}

/// The §11 Altitude-2 inspector tabs (per selected agent). The digit keys
/// select these; the tab is per-instance viewport ephemera (§5.3), held in RAM
/// on [`Focus`](crate::Focus).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InspectorTab {
    #[default]
    Transcript,
    Steps,
    Inbox,
    Files,
    Config,
}

impl InspectorTab {
    /// The tab a digit key selects (§11 tab order): 1 Transcript … 5 Config.
    /// Digits outside 1–5 select no tab.
    pub fn from_digit(n: u8) -> Option<Self> {
        Some(match n {
            1 => Self::Transcript,
            2 => Self::Steps,
            3 => Self::Inbox,
            4 => Self::Files,
            5 => Self::Config,
            _ => return None,
        })
    }

    /// The tab's header label (§11 inspector).
    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::Transcript => "Transcript",
            Self::Steps => "Steps",
            Self::Inbox => "Inbox",
            Self::Files => "Files",
            Self::Config => "Config",
        }
    }

    /// The five tabs in §11 order — the shell's tab strip and the digit map
    /// both derive from this one list.
    pub fn all() -> [Self; 5] {
        [
            Self::Transcript,
            Self::Steps,
            Self::Inbox,
            Self::Files,
            Self::Config,
        ]
    }
}

/// A navigation intent — the pure keymap's output, dispatched by the shell to
/// the matching [`AppModel`](crate::AppModel) method (§11).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyAction {
    /// ↑ — step focus to the previous roster (workspace, agent) entry.
    RosterPrev,
    /// ↓ — step focus to the next roster (workspace, agent) entry.
    RosterNext,
    /// A digit — select an inspector tab.
    Tab(InspectorTab),
}

/// The §11 keymap: a logical [`Key`] to its navigation intent, or `None` for an
/// unbound key (a digit with no tab). Pure and total.
pub fn keymap(key: Key) -> Option<KeyAction> {
    match key {
        Key::Up => Some(KeyAction::RosterPrev),
        Key::Down => Some(KeyAction::RosterNext),
        Key::Digit(n) => InspectorTab::from_digit(n).map(KeyAction::Tab),
    }
}

#[cfg(test)]
mod tests;