From f71b1616bcff9faaeaba8b5e16167f62a4142e9d Mon Sep 17 00:00:00 2001 From: Pratik Rai Date: Sun, 3 May 2026 15:20:44 +0530 Subject: [PATCH 1/4] fix(ui): Gate Voice Input in toolbar layout editor based on features and settings (Fixes Bug 2 from #9958) --- .../agent_view/agent_input_footer/editor.rs | 42 ++++++++++++++----- .../agent_input_footer/toolbar_item.rs | 16 +++++-- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs index 0625e63c2..fa9d5a910 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs @@ -17,6 +17,7 @@ use crate::terminal::session_settings::{ AgentToolbarChipSelection, CLIAgentToolbarChipSelection, SessionSettings, SessionSettingsChangedEvent, ToolbarChipSelection, }; +use crate::settings::AISettings; use crate::Appearance; use settings::Setting as _; @@ -75,7 +76,8 @@ fn open_toolbar_items_from_settings( ) { let appearance = Appearance::as_ref(ctx); let session_settings = SessionSettings::as_ref(ctx); - let (current_left, current_right, available) = match mode { + let voice_input_enabled = AISettings::as_ref(ctx).is_voice_input_enabled(ctx); + let (current_left, current_right, mut available) = match mode { AgentToolbarEditorMode::AgentView => { let selection = session_settings.agent_footer_chip_selection.clone(); ( @@ -93,6 +95,9 @@ fn open_toolbar_items_from_settings( ) } }; + if !voice_input_enabled { + available.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + } chip_configurator.open_left_right_zones_with_items( current_left, current_right, @@ -107,25 +112,37 @@ fn open_default_toolbar_items( ctx: &mut ViewContext, ) { let appearance = Appearance::as_ref(ctx); - let (left, right, available) = AgentToolbarItemKind::defaults_for_mode(mode); + let voice_input_enabled = AISettings::as_ref(ctx).is_voice_input_enabled(ctx); + let (mut left, mut right, mut available) = AgentToolbarItemKind::defaults_for_mode(mode); + if !voice_input_enabled { + left.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + right.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + available.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + } chip_configurator.open_left_right_zones_with_items(left, right, available, appearance); } fn is_toolbar_editor_at_defaults( mode: AgentToolbarEditorMode, chip_configurator: &ChipConfigurator, + voice_input_enabled: bool, ) -> bool { let left = chip_configurator.left_item_kinds(); let right = chip_configurator.right_item_kinds(); - toolbar_items_match_defaults(mode, &left, &right) + toolbar_items_match_defaults(mode, &left, &right, voice_input_enabled) } fn toolbar_items_match_defaults( mode: AgentToolbarEditorMode, left: &[AgentToolbarItemKind], right: &[AgentToolbarItemKind], + voice_input_enabled: bool, ) -> bool { - let (default_left, default_right, _) = AgentToolbarItemKind::defaults_for_mode(mode); + let (mut default_left, mut default_right, _) = AgentToolbarItemKind::defaults_for_mode(mode); + if !voice_input_enabled { + default_left.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + default_right.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + } default_left.as_slice() == left && default_right.as_slice() == right } @@ -169,8 +186,9 @@ impl AgentToolbarInlineEditor { save_toolbar_selection(self.mode, left, right, ctx); } - fn is_at_defaults(&self) -> bool { - is_toolbar_editor_at_defaults(self.mode, &self.chip_configurator) + fn is_at_defaults(&self, app: &AppContext) -> bool { + let voice_input_enabled = AISettings::as_ref(app).is_voice_input_enabled(app); + is_toolbar_editor_at_defaults(self.mode, &self.chip_configurator, voice_input_enabled) } } @@ -213,7 +231,7 @@ impl View for AgentToolbarInlineEditor { &self.chip_configurator, ChipEditorSectionsConfig { available_section_label: "Available chips", - is_at_defaults: self.is_at_defaults(), + is_at_defaults: self.is_at_defaults(app), reset_action: AgentToolbarInlineEditorAction::ResetDefault, activate_action: AgentToolbarInlineEditorAction::Activate, chip_action_wrapper: AgentToolbarInlineEditorAction::Chip, @@ -240,7 +258,8 @@ fn save_toolbar_selection( right: Vec, ctx: &mut ViewContext, ) { - let is_default = toolbar_items_match_defaults(mode, &left, &right); + let voice_input_enabled = AISettings::as_ref(ctx).is_voice_input_enabled(ctx); + let is_default = toolbar_items_match_defaults(mode, &left, &right, voice_input_enabled); match mode { AgentToolbarEditorMode::AgentView => { let selection = if is_default { @@ -346,8 +365,9 @@ impl TypedActionView for AgentToolbarEditorModal { } impl AgentToolbarEditorModal { - fn is_at_defaults(&self) -> bool { - is_toolbar_editor_at_defaults(self.mode, &self.chip_configurator) + fn is_at_defaults(&self, app: &AppContext) -> bool { + let voice_input_enabled = AISettings::as_ref(app).is_voice_input_enabled(app); + is_toolbar_editor_at_defaults(self.mode, &self.chip_configurator, voice_input_enabled) } } @@ -363,7 +383,7 @@ impl View for AgentToolbarEditorModal { ChipEditorModalConfig { title: self.modal_title(), available_section_label: "Available chips", - is_at_defaults: self.is_at_defaults(), + is_at_defaults: self.is_at_defaults(app), is_dirty: self.is_dirty, cancel_action: AgentToolbarEditorAction::Cancel, save_action: AgentToolbarEditorAction::Save, diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs index 2d47ffa0e..d6786e467 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs @@ -177,7 +177,9 @@ impl AgentToolbarItemKind { { items.push(Self::ShareSession); } - items.push(Self::VoiceInput); + if cfg!(feature = "voice_input") { + items.push(Self::VoiceInput); + } items.push(Self::FileAttach); items } @@ -191,10 +193,12 @@ impl AgentToolbarItemKind { items.extend([ Self::ModelSelector, Self::NLDToggle, - Self::VoiceInput, Self::FileAttach, Self::ContextWindowUsage, ]); + if cfg!(feature = "voice_input") { + items.push(Self::VoiceInput); + } if FeatureFlag::FastForwardAutoexecuteButton.is_enabled() { items.push(Self::FastForwardToggle); } @@ -210,9 +214,11 @@ impl AgentToolbarItemKind { pub fn cli_default_left() -> Vec { let mut items = vec![ Self::FileAttach, - Self::VoiceInput, Self::ContextChip(ContextChipKind::GitDiffStats), ]; + if cfg!(feature = "voice_input") { + items.push(Self::VoiceInput); + } if FeatureFlag::CreatingSharedSessions.is_enabled() && FeatureFlag::HOARemoteControl.is_enabled() { @@ -244,9 +250,11 @@ impl AgentToolbarItemKind { Self::FileExplorer, Self::RichInput, Self::FileAttach, - Self::VoiceInput, Self::Settings, ]); + if cfg!(feature = "voice_input") { + items.push(Self::VoiceInput); + } if FeatureFlag::CreatingSharedSessions.is_enabled() && FeatureFlag::HOARemoteControl.is_enabled() { From 9f9f1dce7e08cef657cfca4ff93e2ce59ace9e7c Mon Sep 17 00:00:00 2001 From: Pratik Rai Date: Sun, 3 May 2026 15:43:36 +0530 Subject: [PATCH 2/4] fix(ui): address review feedback for voice input gating and ordering --- app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs | 4 +++- .../blocklist/agent_view/agent_input_footer/toolbar_item.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs index fa9d5a910..ef5e43cdd 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs @@ -77,7 +77,7 @@ fn open_toolbar_items_from_settings( let appearance = Appearance::as_ref(ctx); let session_settings = SessionSettings::as_ref(ctx); let voice_input_enabled = AISettings::as_ref(ctx).is_voice_input_enabled(ctx); - let (current_left, current_right, mut available) = match mode { + let (mut current_left, mut current_right, mut available) = match mode { AgentToolbarEditorMode::AgentView => { let selection = session_settings.agent_footer_chip_selection.clone(); ( @@ -96,6 +96,8 @@ fn open_toolbar_items_from_settings( } }; if !voice_input_enabled { + current_left.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + current_right.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); available.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); } chip_configurator.open_left_right_zones_with_items( diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs index d6786e467..e6668aba3 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs @@ -217,7 +217,7 @@ impl AgentToolbarItemKind { Self::ContextChip(ContextChipKind::GitDiffStats), ]; if cfg!(feature = "voice_input") { - items.push(Self::VoiceInput); + items.insert(1, Self::VoiceInput); } if FeatureFlag::CreatingSharedSessions.is_enabled() && FeatureFlag::HOARemoteControl.is_enabled() From 1156e8626c811b5cccb974aba7267907dce2c698 Mon Sep 17 00:00:00 2001 From: Pratik Rai Date: Sun, 3 May 2026 17:02:59 +0530 Subject: [PATCH 3/4] fix(ui): subscribe to AISettingsChangedEvent to refresh toolbar editor state --- .../agent_view/agent_input_footer/editor.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs index ef5e43cdd..88219cd97 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs @@ -17,7 +17,7 @@ use crate::terminal::session_settings::{ AgentToolbarChipSelection, CLIAgentToolbarChipSelection, SessionSettings, SessionSettingsChangedEvent, ToolbarChipSelection, }; -use crate::settings::AISettings; +use crate::settings::{AISettings, AISettingsChangedEvent}; use crate::Appearance; use settings::Setting as _; @@ -175,6 +175,18 @@ impl AgentToolbarInlineEditor { } }); + ctx.subscribe_to_model(&AISettings::handle(ctx), |me, _, event, ctx| { + if matches!( + event, + AISettingsChangedEvent::VoiceInputEnabled { .. } + | AISettingsChangedEvent::IsAnyAIEnabled { .. } + ) && me.chip_configurator.current_dragging_state.is_none() + { + me.reset_from_settings(ctx); + ctx.notify(); + } + }); + editor } From 1c937bb834cde2e2228cc3a32d6c9dcb4b7514d5 Mon Sep 17 00:00:00 2001 From: Pratik Rai Date: Sun, 3 May 2026 17:15:48 +0530 Subject: [PATCH 4/4] fix(ui): preserve Voice Input placement during disabled-state saves --- .../agent_view/agent_input_footer/editor.rs | 79 +++++++++++++++---- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs index 88219cd97..007681cad 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/editor.rs @@ -43,12 +43,14 @@ pub struct AgentToolbarEditorModal { chip_configurator: ChipConfigurator, mode: AgentToolbarEditorMode, is_dirty: bool, + hidden_voice_input: HiddenVoiceInputItems, } pub struct AgentToolbarInlineEditor { mouse_handles: ChipEditorMouseHandles, chip_configurator: ChipConfigurator, mode: AgentToolbarEditorMode, + hidden_voice_input: HiddenVoiceInputItems, } #[derive(Clone, Copy, Debug)] @@ -69,15 +71,25 @@ pub enum AgentToolbarInlineEditorAction { Activate, } +/// Tracks VoiceInput items stripped from the ChipConfigurator display so they +/// can be re-inserted on save, preventing destructive removal when voice input +/// is temporarily disabled. +#[derive(Default)] +struct HiddenVoiceInputItems { + left: Vec, + right: Vec, +} + fn open_toolbar_items_from_settings( chip_configurator: &mut ChipConfigurator, mode: AgentToolbarEditorMode, + hidden_voice_input: &mut HiddenVoiceInputItems, ctx: &mut ViewContext, ) { let appearance = Appearance::as_ref(ctx); let session_settings = SessionSettings::as_ref(ctx); let voice_input_enabled = AISettings::as_ref(ctx).is_voice_input_enabled(ctx); - let (mut current_left, mut current_right, mut available) = match mode { + let (current_left, current_right, mut available) = match mode { AgentToolbarEditorMode::AgentView => { let selection = session_settings.agent_footer_chip_selection.clone(); ( @@ -95,17 +107,32 @@ fn open_toolbar_items_from_settings( ) } }; + hidden_voice_input.left.clear(); + hidden_voice_input.right.clear(); if !voice_input_enabled { - current_left.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); - current_right.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + let (visible_left, hidden_left): (Vec<_>, Vec<_>) = current_left + .into_iter() + .partition(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + let (visible_right, hidden_right): (Vec<_>, Vec<_>) = current_right + .into_iter() + .partition(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + hidden_voice_input.left = hidden_left; + hidden_voice_input.right = hidden_right; available.retain(|item| !matches!(item, AgentToolbarItemKind::VoiceInput)); + chip_configurator.open_left_right_zones_with_items( + visible_left, + visible_right, + available, + appearance, + ); + } else { + chip_configurator.open_left_right_zones_with_items( + current_left, + current_right, + available, + appearance, + ); } - chip_configurator.open_left_right_zones_with_items( - current_left, - current_right, - available, - appearance, - ); } fn open_default_toolbar_items( @@ -154,6 +181,7 @@ impl AgentToolbarInlineEditor { mouse_handles: Default::default(), chip_configurator: ChipConfigurator::new(ChipConfiguratorLayout::LeftRightZones), mode, + hidden_voice_input: HiddenVoiceInputItems::default(), }; editor.reset_from_settings(ctx); @@ -191,13 +219,18 @@ impl AgentToolbarInlineEditor { } fn reset_from_settings(&mut self, ctx: &mut ViewContext) { - open_toolbar_items_from_settings(&mut self.chip_configurator, self.mode, ctx); + open_toolbar_items_from_settings( + &mut self.chip_configurator, + self.mode, + &mut self.hidden_voice_input, + ctx, + ); } fn save_current_selection(&self, ctx: &mut ViewContext) { let left = self.chip_configurator.left_item_kinds(); let right = self.chip_configurator.right_item_kinds(); - save_toolbar_selection(self.mode, left, right, ctx); + save_toolbar_selection(self.mode, left, right, &self.hidden_voice_input, ctx); } fn is_at_defaults(&self, app: &AppContext) -> bool { @@ -224,6 +257,8 @@ impl TypedActionView for AgentToolbarInlineEditor { } Self::Action::ResetDefault => { open_default_toolbar_items(&mut self.chip_configurator, self.mode, ctx); + self.hidden_voice_input.left.clear(); + self.hidden_voice_input.right.clear(); self.save_current_selection(ctx); ctx.notify(); } @@ -268,10 +303,16 @@ pub fn init(app: &mut AppContext) { fn save_toolbar_selection( mode: AgentToolbarEditorMode, - left: Vec, - right: Vec, + mut left: Vec, + mut right: Vec, + hidden: &HiddenVoiceInputItems, ctx: &mut ViewContext, ) { + // Re-insert any VoiceInput items that were hidden while the feature was + // disabled so the user's original placement is never destroyed. + left.extend(hidden.left.clone()); + right.extend(hidden.right.clone()); + let voice_input_enabled = AISettings::as_ref(ctx).is_voice_input_enabled(ctx); let is_default = toolbar_items_match_defaults(mode, &left, &right, voice_input_enabled); match mode { @@ -309,13 +350,19 @@ impl AgentToolbarEditorModal { chip_configurator: ChipConfigurator::new(ChipConfiguratorLayout::LeftRightZones), mode: AgentToolbarEditorMode::default(), is_dirty: false, + hidden_voice_input: HiddenVoiceInputItems::default(), } } pub fn open(&mut self, mode: AgentToolbarEditorMode, ctx: &mut ViewContext) { self.reset(); self.mode = mode; - open_toolbar_items_from_settings(&mut self.chip_configurator, mode, ctx); + open_toolbar_items_from_settings( + &mut self.chip_configurator, + mode, + &mut self.hidden_voice_input, + ctx, + ); ctx.notify(); } @@ -326,7 +373,7 @@ impl AgentToolbarEditorModal { let left = self.chip_configurator.left_item_kinds(); let right = self.chip_configurator.right_item_kinds(); - save_toolbar_selection(self.mode, left, right, ctx); + save_toolbar_selection(self.mode, left, right, &self.hidden_voice_input, ctx); } fn reset(&mut self) { @@ -369,6 +416,8 @@ impl TypedActionView for AgentToolbarEditorModal { Self::Action::ResetDefault => { self.is_dirty = true; open_default_toolbar_items(&mut self.chip_configurator, self.mode, ctx); + self.hidden_voice_input.left.clear(); + self.hidden_voice_input.right.clear(); ctx.notify(); } Self::Action::Activate => {