From aed9a5eec083230c5a84b5ef2c16260afea1445d Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Wed, 2 Sep 2026 17:58:24 +0100 Subject: [PATCH] feat(maestro): AoE-style bare q/tab/esc keybinds in the TUI Normal mode only exists while the sidebar is focused (Insert mode always pairs with Focus::Pane), so bare `q` and `tab` there never reach a pty: `q` quits, `tab` focuses the highlighted pane (same as Enter). `esc` is added as a second, fixed way out of Insert alongside the leader. Co-Authored-By: Claude Sonnet 5 --- crates/maestro-tui/src/config.rs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/maestro-tui/src/config.rs b/crates/maestro-tui/src/config.rs index db75b8c..b031068 100644 --- a/crates/maestro-tui/src/config.rs +++ b/crates/maestro-tui/src/config.rs @@ -48,6 +48,10 @@ const DEFAULT_NORMAL: &[(&str, &str)] = &[ ("c", "ToggleSidebarGroup"), // Catalog actions ("enter", "AttachHighlighted"), + // Tab focuses the highlighted pane, mirroring the web keybind — bare + // because Normal mode only exists while the sidebar is focused, so + // nothing forwards it to a pty. + ("tab", "AttachHighlighted"), ("s", "OpenSpawnModal"), // Fast-forward spawn (#67): the spawn key's neighbour does the same // thing with every default resolved and no form. @@ -63,14 +67,17 @@ const DEFAULT_NORMAL: &[(&str, &str)] = &[ ("ctrl-w b", "ToggleSidebar"), ("?", "ShowHelp"), ("ctrl-w q", "Quit"), + // Bare quit, AoE-style: only reachable from the sidebar (Normal mode + // never forwards keys to a pty), same reasoning as bare `tab` above. + ("q", "Quit"), // Mouse ("mouse:dblclick(sidebar-row)", "AttachHighlighted"), ]; -/// Default Insert-mode bindings. Empty by default — the only Insert binding -/// is the leader, which is inserted dynamically by [`TuiConfig::load`] so -/// rebinding the leader rebinds its Insert escape automatically. -const DEFAULT_INSERT: &[(&str, &str)] = &[]; +/// Default Insert-mode bindings. The leader (`LeaveInsert`) is inserted +/// dynamically by [`TuiConfig::load`] so rebinding it rebinds its Insert +/// escape automatically; `esc` is a second, fixed way back to the sidebar. +const DEFAULT_INSERT: &[(&str, &str)] = &[("esc", "LeaveInsert")]; impl TuiConfig { /// Build a runtime config from a [`TuiSection`] (typically the `tui` @@ -222,6 +229,23 @@ mod tests { ); } + /// AoE-style defaults (#shortcuts): `q` and `tab` are bare because Normal + /// mode only exists while the sidebar is focused, so neither can leak + /// into a pty; `esc` is a second way out of Insert alongside the leader. + #[test] + fn aoe_style_defaults_are_bound() { + let cfg = TuiConfig::load(&TuiSection::default()).unwrap(); + assert_eq!(cfg.keymap.resolve(Mode::Normal, "q"), Some(Action::Quit)); + assert_eq!( + cfg.keymap.resolve(Mode::Normal, "tab"), + Some(Action::AttachHighlighted), + ); + assert_eq!( + cfg.keymap.resolve(Mode::Insert, "esc"), + Some(Action::LeaveInsert), + ); + } + #[test] fn default_leader_is_ctrl_a_and_escapes_insert() { let cfg = TuiConfig::load(&TuiSection::default()).unwrap();