From da6b8141ad316cb1202d1fc63017a99f5e19fec0 Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Sat, 6 Jun 2026 15:48:30 +0000 Subject: [PATCH 1/2] fix(tui): prevent AltGr from swallowing @/#/$/!/%/ characters in composer On Windows, AltGr is delivered as Ctrl+Alt by crossterm. European keyboard layouts (French AZERTY, German QWERTZ, etc.) use AltGr to type characters like @ (AltGr+0), # (AltGr+3), etc. The sidebar-focus shortcuts for Alt+@/Alt+!/Alt+#/Alt+$/Alt+%) were matching on "contains ALT" alone, swallowing these AltGr-typed characters instead of inserting them into the composer. Exclude the Ctrl modifier from these sidebar-focus shortcut guards so AltGr-typed glyphs fall through to the catch-all and are inserted as text. This is consistent with the has_ctrl_or_alt / is_altgr philosophy in key_hint.rs, which already treats Ctrl+Alt as AltGr to preserve European keyboard input. Closes #2863 --- crates/tui/src/tui/ui.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 61cb195c7..f24eeea8a 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -2873,29 +2873,35 @@ async fn run_event_loop( apply_alt_4_shortcut(app, key.modifiers); continue; } - KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) => { + // Sidebar focus via Alt+! / Alt+@ / Alt+# / Alt+$ / Alt+%) + // AltGr on European keyboards emits Ctrl+Alt on Windows, so + // exclude Ctrl to avoid swallowing AltGr-typed characters + // like @ (AltGr+0 on French AZERTY) and # (AltGr+3). This + // matches the has_ctrl_or_alt / is_altgr philosophy in + // key_hint.rs: treat Ctrl+Alt as AltGr, not a shortcut. + KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Work); app.status_message = Some("Sidebar focus: work".to_string()); continue; } - KeyCode::Char('@') if key.modifiers.contains(KeyModifiers::ALT) => { + KeyCode::Char('@') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Tasks); app.status_message = Some("Sidebar focus: tasks".to_string()); continue; } - KeyCode::Char('#') if key.modifiers.contains(KeyModifiers::ALT) => { + KeyCode::Char('#') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Agents); app.status_message = Some("Sidebar focus: agents".to_string()); continue; } KeyCode::Char('$') | KeyCode::Char('%') - if key.modifiers.contains(KeyModifiers::ALT) => + if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Context); app.status_message = Some("Sidebar focus: context".to_string()); continue; } - KeyCode::Char(')') if key.modifiers.contains(KeyModifiers::ALT) => { + KeyCode::Char(')') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Auto); app.status_message = Some("Sidebar focus: auto".to_string()); continue; From 700a36edf11b69eb37177b04e74b268f00150079 Mon Sep 17 00:00:00 2001 From: Hunter B Date: Sat, 6 Jun 2026 10:30:17 -0700 Subject: [PATCH 2/2] style(tui): format AltGr sidebar shortcut guards --- crates/tui/src/tui/ui.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index f24eeea8a..e5445e632 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -2879,29 +2879,42 @@ async fn run_event_loop( // like @ (AltGr+0 on French AZERTY) and # (AltGr+3). This // matches the has_ctrl_or_alt / is_altgr philosophy in // key_hint.rs: treat Ctrl+Alt as AltGr, not a shortcut. - KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { + KeyCode::Char('!') + if key.modifiers.contains(KeyModifiers::ALT) + && !key.modifiers.contains(KeyModifiers::CONTROL) => + { app.set_sidebar_focus(SidebarFocus::Work); app.status_message = Some("Sidebar focus: work".to_string()); continue; } - KeyCode::Char('@') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { + KeyCode::Char('@') + if key.modifiers.contains(KeyModifiers::ALT) + && !key.modifiers.contains(KeyModifiers::CONTROL) => + { app.set_sidebar_focus(SidebarFocus::Tasks); app.status_message = Some("Sidebar focus: tasks".to_string()); continue; } - KeyCode::Char('#') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { + KeyCode::Char('#') + if key.modifiers.contains(KeyModifiers::ALT) + && !key.modifiers.contains(KeyModifiers::CONTROL) => + { app.set_sidebar_focus(SidebarFocus::Agents); app.status_message = Some("Sidebar focus: agents".to_string()); continue; } KeyCode::Char('$') | KeyCode::Char('%') - if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => + if key.modifiers.contains(KeyModifiers::ALT) + && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Context); app.status_message = Some("Sidebar focus: context".to_string()); continue; } - KeyCode::Char(')') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { + KeyCode::Char(')') + if key.modifiers.contains(KeyModifiers::ALT) + && !key.modifiers.contains(KeyModifiers::CONTROL) => + { app.set_sidebar_focus(SidebarFocus::Auto); app.status_message = Some("Sidebar focus: auto".to_string()); continue;