From 5c3d204c0162d11271276834268a505313568569 Mon Sep 17 00:00:00 2001 From: Tryanks Date: Sat, 3 Oct 2026 13:21:48 +0800 Subject: [PATCH] fix: show the composer trigger menus on the phone The compact layout cleared the active trigger on every input change, so typing / @ or $ on a phone never opened a menu, and /later was sent to the agent as text instead of being scheduled. Both guards came with the first mobile client; every data path behind the menus is protocol-backed, so the phone uses the same trigger owner as the desktop. --- .../src/composer/components/trigger_menu.rs | 49 +++++++++++++++---- crates/ui/src/composer/mod.rs | 3 +- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/crates/ui/src/composer/components/trigger_menu.rs b/crates/ui/src/composer/components/trigger_menu.rs index bf04da43e..0b65a2c3e 100644 --- a/crates/ui/src/composer/components/trigger_menu.rs +++ b/crates/ui/src/composer/components/trigger_menu.rs @@ -9,10 +9,6 @@ impl Composer { /// highlight (and un-dismissing) when the trigger identity changes, and /// lazily loading the workspace listing for `@`-mentions. pub(in super::super) fn recompute_trigger(&mut self, cx: &mut Context) { - if self.compact { - self.active_trigger = None; - return; - } let (text, cursor) = { let state = self.input.read(cx); (state.value().to_string(), state.cursor()) @@ -411,10 +407,16 @@ fn render_menu_row( #[cfg(test)] mod tests { use super::*; - use gpui::{TestAppContext, size}; + use gpui::{TestAppContext, VisualTestContext, size}; - #[gpui::test] - fn arrow_keys_keep_the_highlighted_mention_laid_out(cx: &mut TestAppContext) { + fn composer_window( + compact: bool, + cx: &mut TestAppContext, + ) -> ( + Entity, + Entity, + &mut VisualTestContext, + ) { cx.update(crate::theme::init); let host = tcode_runtime::pipe::spawn_host( tcode_services::store::SessionStore::open_at(std::env::temp_dir().join(format!( @@ -436,8 +438,37 @@ mod tests { store.update(cx, |store, cx| { store.set_session_replica_for_test(session_id, timeline, cx); }); - let (composer, cx) = - cx.add_window_view(|window, cx| Composer::new(store.clone(), window, cx)); + let (composer, cx) = cx.add_window_view(|window, cx| { + Composer::new_with_layout(store.clone(), compact, window, cx) + }); + (store, composer, cx) + } + + #[gpui::test] + fn the_phone_composer_opens_the_same_trigger_menus(cx: &mut TestAppContext) { + let (store, composer, cx) = composer_window(true, cx); + cx.simulate_resize(size(px(393.), px(852.))); + composer.update_in(cx, |composer, window, cx| { + let cwd = store.read(cx).composer_state().active_cwd.unwrap(); + composer.workspace = Some((cwd, vec![PathEntry::from_rel("file.rs".into(), false)])); + window.focus(&composer.input.read(cx).focus_handle(cx), cx); + }); + for (trigger, first_row) in [("/", "/model"), ("@", "file.rs")] { + composer.update_in(cx, |composer, window, cx| { + composer.set_draft("", window, cx) + }); + cx.simulate_input(trigger); + cx.update(|window, cx| _ = window.draw(cx)); + assert!(cx.debug_bounds("menu-row-0").is_some(), "{trigger}"); + composer.read_with(cx, |composer, cx| { + assert_eq!(composer.menu_rows(cx).0[0].primary, first_row, "{trigger}"); + }); + } + } + + #[gpui::test] + fn arrow_keys_keep_the_highlighted_mention_laid_out(cx: &mut TestAppContext) { + let (store, composer, cx) = composer_window(false, cx); cx.simulate_resize(size(px(800.), px(600.))); composer.update_in(cx, |composer, window, cx| { let cwd = store.read(cx).composer_state().active_cwd.unwrap(); diff --git a/crates/ui/src/composer/mod.rs b/crates/ui/src/composer/mod.rs index 4611d77c3..886296ec5 100644 --- a/crates/ui/src/composer/mod.rs +++ b/crates/ui/src/composer/mod.rs @@ -612,8 +612,7 @@ impl Composer { window.push_notification(Notification::info(crate::tr!("composer.no_session")), cx); return; } - if !self.compact - && terminal_contexts.is_empty() + if terminal_contexts.is_empty() && let Some(later) = parse_later(&text, Local::now()) { let Ok((fire_at_unix_secs, message)) = later else {