//! Single source of truth for the desktop keyboard bindings (Phase J). //! //! Two static hotkey tables had already diverged (onboarding's slide //! and Help's controls reference); every future drift multiplies. This //! module owns THE table: the onboarding slide renders the //! [`HotkeyRow::essential`] subset, the hold-`/` cheat sheet //! ([`crate::cheat_sheet_plugin`]) renders everything. //! //! The table is hand-maintained but **pinned by test** against the //! handlers that actually consume each key — adding a binding without //! updating this table (or vice versa) is designed to fail review, not //! runtime. A registry generated from the input systems themselves is //! the eventual ideal; this is the honest 90 % at 1 % of the cost. /// One row of the hotkey table. #[derive(Debug, Clone, Copy)] pub struct HotkeyRow { /// Display form of the key(s), e.g. `"D / Space"`. pub keys: &'static str, /// One-line action description. pub description: &'static str, /// `true` for the beginner-relevant subset the onboarding slide /// shows; the cheat sheet always shows every row. pub essential: bool, } /// Every desktop keyboard binding, in teaching order. pub const HOTKEYS: &[HotkeyRow] = &[ HotkeyRow { keys: "D / Space", description: "Draw from stock", essential: true, }, HotkeyRow { keys: "U", description: "Undo last move (hold to repeat)", essential: true, }, HotkeyRow { keys: "H", description: "Hint (repeat to cycle alternatives)", essential: true, }, HotkeyRow { keys: "Tab → Enter", description: "Pick a card; arrows pick where; Enter to drop", essential: true, }, HotkeyRow { keys: "N", description: "New Classic game", essential: true, }, HotkeyRow { keys: "M", description: "Open Home (then 1–6 to pick a mode)", essential: true, }, HotkeyRow { keys: "Esc", description: "Pause / resume; close the top dialog", essential: true, }, HotkeyRow { keys: "F1", description: "Help / controls", essential: true, }, HotkeyRow { keys: "S", description: "Stats & progression", essential: true, }, HotkeyRow { keys: "A", description: "Achievements", essential: true, }, HotkeyRow { keys: "O", description: "Settings", essential: true, }, HotkeyRow { keys: "P", description: "Profile", essential: false, }, HotkeyRow { keys: "L", description: "Leaderboard", essential: false, }, HotkeyRow { keys: "C", description: "Daily Challenge", essential: false, }, HotkeyRow { keys: "Z", description: "Zen mode", essential: false, }, HotkeyRow { keys: "X", description: "Challenge mode", essential: false, }, HotkeyRow { keys: "T", description: "Time Attack", essential: false, }, HotkeyRow { keys: "G", description: "Give up the current deal", essential: false, }, HotkeyRow { keys: "[ / ]", description: "Volume down / up", essential: false, }, HotkeyRow { keys: "F11", description: "Toggle fullscreen", essential: false, }, HotkeyRow { keys: "/ (hold)", description: "This cheat sheet", essential: false, }, ]; #[cfg(test)] mod tests { use super::*; #[test] fn table_is_populated_and_well_formed() { assert!(!HOTKEYS.is_empty()); for row in HOTKEYS { assert!(!row.keys.trim().is_empty(), "empty keys cell"); assert!( !row.description.trim().is_empty(), "empty description for {}", row.keys ); } } #[test] fn essential_subset_is_a_teachable_size() { let essentials = HOTKEYS.iter().filter(|r| r.essential).count(); assert!( (6..=12).contains(&essentials), "the onboarding slide wants a skimmable essential set, got {essentials}" ); } #[test] fn keys_cells_are_unique() { let mut seen = std::collections::HashSet::new(); for row in HOTKEYS { assert!(seen.insert(row.keys), "duplicate hotkey row: {}", row.keys); } } }