diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg index 10ae33869a..4d21d5db07 100644 --- a/assets/icons/file_icons/lock.svg +++ b/assets/icons/file_icons/lock.svg @@ -1,4 +1,6 @@ - - + + + + diff --git a/assets/icons/lock_outlined.svg b/assets/icons/lock.svg similarity index 100% rename from assets/icons/lock_outlined.svg rename to assets/icons/lock.svg diff --git a/assets/icons/lock_off.svg b/assets/icons/lock_off.svg new file mode 100644 index 0000000000..9efaa51aa8 --- /dev/null +++ b/assets/icons/lock_off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/lock_outlined_off.svg b/assets/icons/lock_outlined_off.svg deleted file mode 100644 index 74e5851dd7..0000000000 --- a/assets/icons/lock_outlined_off.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index a26d2c652e..a26935e859 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -6234,6 +6234,13 @@ impl AgentPanel { .with_handle(self.new_thread_menu_handle.clone()) .menu(move |window, cx| new_thread_menu_builder(window, cx)); + let sandbox_status = self + .active_conversation_view() + .and_then(|conversation_view| conversation_view.read(cx).root_thread_view()) + .and_then(|thread_view| { + thread_view.update(cx, |thread_view, cx| thread_view.render_sandbox_status(cx)) + }); + base_container .child( h_flex() @@ -6256,11 +6263,11 @@ impl AgentPanel { ) .child( h_flex() + .px_1() .h_full() .flex_none() .gap_1() - .pl_1() - .pr_1() + .children(sandbox_status) .when(can_create_entries, |this| this.child(new_thread_menu)) .child(full_screen_button) .child(self.render_panel_options_menu(window, cx)), diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index 2519d28628..ed4bbeda81 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -24,6 +24,7 @@ use sandbox::{GitSandboxPolicy, SandboxFsPolicy, SandboxNetPolicy, SandboxPolicy use crate::completion_provider::AvailableSkill; use crate::message_editor::SharedSessionCapabilities; +use crate::ui::{SandboxGroup, SandboxRow, SandboxSection, SandboxStatusTooltip}; use db::kvp::KeyValueStore; use gpui::List; @@ -4141,7 +4142,6 @@ impl ThreadView { .gap_0p5() .child(self.render_add_context_button(cx)) .child(self.render_follow_toggle(cx)) - .children(self.render_sandbox_status(cx)) .children(self.render_fast_mode_control(cx)) .children(self.render_thinking_control(cx)), ) @@ -4619,13 +4619,7 @@ impl ThreadView { } } - /// A small lock icon summarizing the sandbox state. Hovering shows the - /// write-access paths and network-access domains from the user's settings - /// and the per-thread overrides. The lock is struck through when sandboxing - /// is turned off (in settings, or for this thread). Shown whenever - /// sandboxing is *applicable* to the project, even when disabled, so the - /// user can always see and change the state. Clicking opens the settings. - fn render_sandbox_status(&mut self, cx: &mut Context) -> Option { + pub fn render_sandbox_status(&mut self, cx: &mut Context) -> Option { let status = self.refresh_sandbox_status(cx)?; let settings_sandbox = status.settings_sandbox.clone(); let thread_sandbox = status.thread_sandbox.clone(); @@ -4634,52 +4628,59 @@ impl ThreadView { // The lock is struck only when the *merged* result is unsandboxed (the // agent runs with ambient permissions). A layer that is merely wide open // but still sandboxed keeps the closed lock. - let icon = if settings_sandbox + let (icon, icon_color) = if settings_sandbox .clone() .merge(thread_sandbox.clone()) .is_unsandboxed() { - IconName::LockOutlinedOff + (IconName::LockOff, Color::Muted) } else { - IconName::LockOutlined + (IconName::Lock, Color::Default) }; - let state = match (settings_sandbox, thread_sandbox) { + let tooltip = match (settings_sandbox, thread_sandbox) { // No sandbox at all because the user turned it off in settings: the // per-thread layer is moot, so don't show it. - (ThreadSandbox::Unsandboxed, _) => SandboxTooltipState::DisabledInSettings, + (ThreadSandbox::Unsandboxed, _) => SandboxStatusTooltip::disabled_in_settings(), // Sandboxed by settings, but disabled for this thread: show the // settings scope (greyed) for context above the disabled status. (ThreadSandbox::Sandboxed(settings_policy), ThreadSandbox::Unsandboxed) => { - SandboxTooltipState::DisabledForThread { - settings: augment_settings_sandbox_policy(settings_policy, baseline), - } + let settings = augment_settings_sandbox_policy(settings_policy, baseline); + SandboxStatusTooltip::disabled_for_thread(sandbox_section(&settings, true)) } ( ThreadSandbox::Sandboxed(settings_policy), ThreadSandbox::Sandboxed(thread_policy), - ) => SandboxTooltipState::Enabled { - settings: augment_settings_sandbox_policy(settings_policy, baseline), - thread: thread_policy, - }, + ) => { + let settings = augment_settings_sandbox_policy(settings_policy, baseline); + // Omit the per-thread section when it grants nothing extra. + let thread = (!sandbox_policy_grants_nothing(&thread_policy)) + .then(|| sandbox_section(&thread_policy, false)); + SandboxStatusTooltip::enabled(sandbox_section(&settings, true), thread) + } }; Some( - IconButton::new("sandbox-status", icon) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .tooltip(Tooltip::element(move |_window, cx| { - render_sandbox_status_tooltip(&state, cx) - })) - .on_click(|_, window, cx| { - window.dispatch_action( - Box::new(zed_actions::OpenSettingsAt { - path: zed_actions::AGENT_SANDBOX_SETTINGS_PATH.to_string(), - target: None, + h_flex() + .gap_1() + .child( + IconButton::new("sandbox-status", icon) + .icon_size(IconSize::Small) + .icon_color(icon_color) + .tooltip(Tooltip::element(move |_window, _cx| { + tooltip.clone().into_any_element() + })) + .on_click(|_, window, cx| { + window.dispatch_action( + Box::new(zed_actions::OpenSettingsAt { + path: zed_actions::AGENT_SANDBOX_SETTINGS_PATH.to_string(), + target: None, + }), + cx, + ); }), - cx, - ); - }) + ) + .child(Divider::vertical().h_4()) .into_any_element(), ) } @@ -5596,74 +5597,6 @@ impl Render for TokenUsageTooltip { } } -/// Which sandbox state the status tooltip describes. -#[derive(Clone)] -enum SandboxTooltipState { - /// Sandboxing is on: show the settings policy and the per-thread overrides. - Enabled { - settings: SandboxPolicy, - thread: SandboxPolicy, - }, - /// Turned off via the persistent `allow_unsandboxed` setting. - DisabledInSettings, - /// Turned off for this thread (the "run without sandbox for this thread" - /// fallback). The settings policy is still shown, greyed out, for context. - DisabledForThread { settings: SandboxPolicy }, -} - -/// Build the hover tooltip for the sandbox status icon. Always opens with a -/// "Sandboxing settings" title, then describes the active state. -/// -/// Returns the raw content only: `Tooltip::element` wraps it in the tooltip -/// container, so wrapping it again here would double the background. -fn render_sandbox_status_tooltip(state: &SandboxTooltipState, cx: &App) -> AnyElement { - let mut body = v_flex() - .min_w(rems(15.)) - .gap_2() - .child(Label::new("Sandboxing settings")) - .child(Divider::horizontal()); - - match state { - SandboxTooltipState::DisabledInSettings => { - body = body.child( - Label::new("Sandboxing is disabled in settings") - .size(LabelSize::Small) - .color(Color::Muted), - ); - } - SandboxTooltipState::DisabledForThread { settings } => { - // The settings policy is moot while disabled, so show it greyed out - // for context above the active "disabled for this thread" status. - let settings_section = - render_sandbox_policy_section("From your settings", settings, true, cx); - body = body - .children( - settings_section - .map(|section| div().opacity(0.5).child(section).into_any_element()), - ) - .child(Divider::horizontal()) - .child(Label::new("Sandboxing is disabled for this thread").size(LabelSize::Small)); - } - SandboxTooltipState::Enabled { settings, thread } => { - let settings_section = - render_sandbox_policy_section("From your settings", settings, true, cx); - let thread_section = - render_sandbox_policy_section("Allowed in this thread", thread, false, cx); - body = body.children(settings_section); - if let Some(section) = thread_section { - body = body.child(Divider::horizontal()).child(section); - } - } - } - - body.into_any_element() -} - -/// Render one section of the sandbox status tooltip. -/// -/// When `show_empty` is true the section is always rendered, and groups that -/// grant nothing show "None". When false, empty groups are omitted and the -/// whole section is dropped (`None`) if it grants nothing at all. /// Fold the always-granted baseline writable paths (the project's worktree /// roots, derived from the same source the terminal tool uses) and, on Linux, /// the host-isolated `/tmp` overlay into a settings policy for display. These @@ -5692,41 +5625,34 @@ fn augment_settings_sandbox_policy( policy } -fn render_sandbox_policy_section( - title: &str, - policy: &SandboxPolicy, - show_empty: bool, - cx: &App, -) -> Option { - let write_rows = sandbox_fs_rows(&policy.fs, cx); - let network_rows = sandbox_network_rows(&policy.network, cx); +fn sandbox_section(policy: &SandboxPolicy, show_empty: bool) -> SandboxSection { let write_empty = fs_grants_nothing(&policy.fs); let network_empty = network_grants_nothing(&policy.network); - // Git access is only ever surfaced when *granted* (the `.git` dirs become - // writable), so it never shows a "None" row — unlike write/network, it's - // omitted entirely when denied, regardless of `show_empty`. + // Git access is only surfaced when granted, so it never shows a "None" row. let git_empty = git_grants_nothing(&policy.git); - if !show_empty && write_empty && network_empty && git_empty { - return None; + let mut section = SandboxSection::new(); + if show_empty || !write_empty { + section = + section.group(SandboxGroup::new("Write Access").rows(sandbox_fs_rows(&policy.fs))); } + if show_empty || !network_empty { + section = section + .group(SandboxGroup::new("Network Access").rows(sandbox_network_rows(&policy.network))); + } + if !git_empty { + section = section + .group(SandboxGroup::new("Git Metadata Access").rows(sandbox_git_rows(&policy.git))); + } + section +} - let write_group = - (show_empty || !write_empty).then(|| sandbox_status_group("Write access", write_rows)); - let network_group = (show_empty || !network_empty) - .then(|| sandbox_status_group("Network access", network_rows)); - let git_group = (!git_empty) - .then(|| sandbox_status_group("Git metadata access", sandbox_git_rows(&policy.git, cx))); - - Some( - v_flex() - .gap_1() - .child(Label::new(title.to_string())) - .children(write_group) - .children(network_group) - .children(git_group) - .into_any_element(), - ) +/// Whether a policy grants nothing worth surfacing, used to decide whether to +/// show the per-thread overrides section at all. +fn sandbox_policy_grants_nothing(policy: &SandboxPolicy) -> bool { + fs_grants_nothing(&policy.fs) + && network_grants_nothing(&policy.network) + && git_grants_nothing(&policy.git) } /// Git access grants nothing to surface unless `.git` writes are allowed *and* @@ -5737,35 +5663,16 @@ fn git_grants_nothing(git: &GitSandboxPolicy) -> bool { /// Rows for the Git-access group: one row per writable `.git` directory (these /// may live outside the project for a linked worktree). -fn sandbox_git_rows(git: &GitSandboxPolicy, cx: &App) -> Vec { +fn sandbox_git_rows(git: &GitSandboxPolicy) -> Vec { match git { GitSandboxPolicy::Allowed { git_dirs } if !git_dirs.is_empty() => git_dirs .iter() - .map(|path| sandbox_git_path_row(path, cx)) + .map(|path| SandboxRow::git(path.clone())) .collect(), _ => Vec::new(), } } -/// A `.git` directory row, tagged with a Git icon to distinguish it from plain -/// writable paths. -fn sandbox_git_path_row(path: &Path, cx: &App) -> AnyElement { - h_flex() - .min_w_0() - .gap_1() - .child( - Icon::new(IconName::GitBranch) - .color(Color::Muted) - .size(IconSize::Small), - ) - .child( - Label::new(path.display().to_string()) - .size(LabelSize::XSmall) - .buffer_font(cx), - ) - .into_any_element() -} - fn fs_grants_nothing(fs: &SandboxFsPolicy) -> bool { matches!(fs, SandboxFsPolicy::Restricted { writable_paths } if writable_paths.is_empty()) } @@ -5780,95 +5687,35 @@ fn network_grants_nothing(network: &SandboxNetPolicy) -> bool { /// Rows for the write-access group: a message for the "all"/"none" cases, or one /// row per granted path. -fn sandbox_fs_rows(fs: &SandboxFsPolicy, cx: &App) -> Vec { +fn sandbox_fs_rows(fs: &SandboxFsPolicy) -> Vec { match fs { - SandboxFsPolicy::Unrestricted => vec![sandbox_message_row("All paths (unrestricted)")], + SandboxFsPolicy::Unrestricted => vec![SandboxRow::message("All paths (unrestricted)")], SandboxFsPolicy::Restricted { writable_paths } if writable_paths.is_empty() => { - vec![sandbox_message_row("None")] + vec![SandboxRow::message("None")] } SandboxFsPolicy::Restricted { writable_paths } => writable_paths .iter() - .map(|path| sandbox_path_row(path, cx)) + .map(|path| SandboxRow::path(path.clone())) .collect(), } } /// Rows for the network-access group: a message for the "all"/"none" cases, or /// one row per allowed domain. -fn sandbox_network_rows(network: &SandboxNetPolicy, cx: &App) -> Vec { +fn sandbox_network_rows(network: &SandboxNetPolicy) -> Vec { match network { - SandboxNetPolicy::Unrestricted => vec![sandbox_message_row("All domains (unrestricted)")], - SandboxNetPolicy::Blocked => vec![sandbox_message_row("None")], + SandboxNetPolicy::Unrestricted => vec![SandboxRow::message("All domains (unrestricted)")], + SandboxNetPolicy::Blocked => vec![SandboxRow::message("None")], SandboxNetPolicy::Restricted { allowed_domains } if allowed_domains.is_empty() => { - vec![sandbox_message_row("None")] + vec![SandboxRow::message("None")] } SandboxNetPolicy::Restricted { allowed_domains } => allowed_domains .iter() - .map(|domain| sandbox_domain_row(domain, cx)) + .map(|domain| SandboxRow::domain(domain.clone())) .collect(), } } -/// A "Write access" / "Network access" group: a muted header, then the indented -/// rows. -fn sandbox_status_group(heading: &str, rows: Vec) -> impl IntoElement { - v_flex() - .gap_0p5() - .child( - Label::new(heading.to_string()) - .size(LabelSize::Small) - .color(Color::Muted), - ) - .child(v_flex().pl_3().gap_0p5().children(rows)) -} - -/// A non-path row (e.g. "All paths (unrestricted)", "None") in a status group. -fn sandbox_message_row(message: &str) -> AnyElement { - Label::new(message.to_string()) - .size(LabelSize::XSmall) - .color(Color::Muted) - .into_any_element() -} - -fn sandbox_path_row(path: &Path, cx: &App) -> AnyElement { - let display_path = path.display().to_string(); - let icon = FileIcons::get_icon(path, cx) - .map(Icon::from_path) - .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) - .unwrap_or_else(|| { - Icon::new(IconName::Folder) - .color(Color::Muted) - .size(IconSize::Small) - }); - h_flex() - .min_w_0() - .gap_1() - .child(icon) - .child( - Label::new(display_path) - .size(LabelSize::XSmall) - .buffer_font(cx), - ) - .into_any_element() -} - -fn sandbox_domain_row(domain: &str, cx: &App) -> AnyElement { - h_flex() - .min_w_0() - .gap_1() - .child( - Icon::new(IconName::Public) - .color(Color::Muted) - .size(IconSize::Small), - ) - .child( - Label::new(domain.to_string()) - .size(LabelSize::XSmall) - .buffer_font(cx), - ) - .into_any_element() -} - impl ThreadView { fn render_entries(&mut self, cx: &mut Context) -> List { let max_content_width = AgentSettings::get_global(cx).max_content_width; diff --git a/crates/agent_ui/src/ui.rs b/crates/agent_ui/src/ui.rs index 4d355de28e..5fdb2afb5f 100644 --- a/crates/agent_ui/src/ui.rs +++ b/crates/agent_ui/src/ui.rs @@ -2,12 +2,14 @@ mod agent_notification; mod end_trial_upsell; mod mention_crease; mod model_selector_components; +mod sandbox_status_tooltip; mod undo_reject_toast; pub use agent_notification::*; pub use end_trial_upsell::*; pub use mention_crease::*; pub use model_selector_components::*; +pub use sandbox_status_tooltip::*; pub use undo_reject_toast::*; /// Returns the appropriate [`DocumentationSide`] for documentation asides diff --git a/crates/agent_ui/src/ui/sandbox_status_tooltip.rs b/crates/agent_ui/src/ui/sandbox_status_tooltip.rs new file mode 100644 index 0000000000..506d437b53 --- /dev/null +++ b/crates/agent_ui/src/ui/sandbox_status_tooltip.rs @@ -0,0 +1,280 @@ +use std::path::PathBuf; + +use file_icons::FileIcons; +use gpui::{AnyElement, App}; +use ui::{Divider, prelude::*}; + +#[derive(Clone)] +pub enum SandboxRow { + Message(SharedString), + Path(PathBuf), + Git(PathBuf), + Domain(SharedString), +} + +impl SandboxRow { + pub fn message(message: impl Into) -> Self { + Self::Message(message.into()) + } + + pub fn path(path: impl Into) -> Self { + Self::Path(path.into()) + } + + pub fn git(path: impl Into) -> Self { + Self::Git(path.into()) + } + + pub fn domain(domain: impl Into) -> Self { + Self::Domain(domain.into()) + } + + fn render(self, cx: &App) -> AnyElement { + let icon_basic = |icon_name: IconName| { + Icon::new(icon_name) + .color(Color::Muted) + .size(IconSize::Small) + }; + + let (icon, label) = match self { + SandboxRow::Message(message) => { + return Label::new(message) + .size(LabelSize::XSmall) + .color(Color::Muted) + .into_any_element(); + } + SandboxRow::Path(path) => { + let icon = FileIcons::get_icon(&path, cx) + .map(|icon| { + Icon::from_path(icon) + .color(Color::Muted) + .size(IconSize::Small) + }) + .unwrap_or_else(|| icon_basic(IconName::Folder)); + (icon, path.display().to_string()) + } + SandboxRow::Git(path) => (icon_basic(IconName::GitBranch), path.display().to_string()), + SandboxRow::Domain(domain) => (icon_basic(IconName::Public), domain.to_string()), + }; + + h_flex() + .min_w_0() + .w_full() + .gap_1() + .child(icon) + .child( + Label::new(label) + .size(LabelSize::XSmall) + .buffer_font(cx) + .truncate_start(), + ) + .into_any_element() + } +} + +/// A labelled group of rows (e.g. "Write Access", "Network Access"). +#[derive(Clone)] +pub struct SandboxGroup { + heading: SharedString, + rows: Vec, +} + +impl SandboxGroup { + pub fn new(heading: impl Into) -> Self { + Self { + heading: heading.into(), + rows: Vec::new(), + } + } + + pub fn row(mut self, row: SandboxRow) -> Self { + self.rows.push(row); + self + } + + pub fn rows(mut self, rows: impl IntoIterator) -> Self { + self.rows.extend(rows); + self + } + + fn render(self, cx: &App) -> impl IntoElement { + v_flex() + .gap_1() + .child( + Label::new(self.heading) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .children(self.rows.into_iter().map(|row| row.render(cx))) + } +} + +#[derive(Clone, Default)] +pub struct SandboxSection { + groups: Vec, +} + +impl SandboxSection { + pub fn new() -> Self { + Self::default() + } + + pub fn group(mut self, group: SandboxGroup) -> Self { + self.groups.push(group); + self + } + + fn render(self, cx: &App) -> AnyElement { + let mut section = v_flex().gap_1(); + for (index, group) in self.groups.into_iter().enumerate() { + if index > 0 { + section = section.child(Divider::horizontal()); + } + section = section.child(group.render(cx)); + } + section.into_any_element() + } +} + +#[derive(Clone, IntoElement, RegisterComponent)] +pub enum SandboxStatusTooltip { + Enabled { + settings: SandboxSection, + thread: Option, + }, + DisabledForThread { + settings: SandboxSection, + }, + DisabledInSettings, +} + +impl SandboxStatusTooltip { + pub fn enabled(settings: SandboxSection, thread: Option) -> Self { + Self::Enabled { settings, thread } + } + + pub fn disabled_for_thread(settings: SandboxSection) -> Self { + Self::DisabledForThread { settings } + } + + pub fn disabled_in_settings() -> Self { + Self::DisabledInSettings + } +} + +impl RenderOnce for SandboxStatusTooltip { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + let content = match self { + SandboxStatusTooltip::DisabledInSettings => v_flex() + .child( + Label::new("You have sandboxing disabled in settings.") + .size(LabelSize::Small) + .color(Color::Muted), + ) + .into_any_element(), + SandboxStatusTooltip::DisabledForThread { settings } => v_flex() + .gap_1() + .child(div().opacity(0.5).child(settings.render(cx))) + .child(Divider::horizontal()) + .child(Label::new("Sandboxing is disabled for this thread").size(LabelSize::Small)) + .into_any_element(), + SandboxStatusTooltip::Enabled { settings, thread } => v_flex() + .child(settings.render(cx)) + .children(thread.map(|thread| { + v_flex() + .child(Divider::horizontal()) + .child(thread.render(cx)) + })) + .into_any_element(), + }; + + v_flex() + .min_w(rems(15.)) + .gap_1() + .child(Label::new("Sandboxing")) + .child(content) + } +} + +impl Component for SandboxStatusTooltip { + fn scope() -> ComponentScope { + ComponentScope::Agent + } + + fn name() -> &'static str { + "Sandbox Status Tooltip" + } + + fn description() -> &'static str { + "The tooltip shown on the sandboxing lock icon in the agent panel, \ + describing the filesystem, network, and Git access granted to the \ + agent for each of the possible sandbox states." + } + + fn preview(_window: &mut Window, cx: &mut App) -> AnyElement { + let settings_section = SandboxSection::new() + .group(SandboxGroup::new("Write Access").rows([ + SandboxRow::path("/Users/you/project"), + SandboxRow::path("/tmp (isolated)"), + ])) + .group(SandboxGroup::new("Network Access").rows([ + SandboxRow::domain("github.com"), + SandboxRow::domain("*.npmjs.org"), + ])); + + let thread_section = SandboxSection::new() + .group( + SandboxGroup::new("Write Access").row(SandboxRow::path("/Users/you/project/build")), + ) + .group(SandboxGroup::new("Network Access").row(SandboxRow::message("None"))) + .group( + SandboxGroup::new("Git Metadata Access") + .row(SandboxRow::git("/Users/you/project/.git")), + ); + + let unrestricted_section = SandboxSection::new() + .group( + SandboxGroup::new("Write Access") + .row(SandboxRow::message("All paths (unrestricted)")), + ) + .group( + SandboxGroup::new("Network Access") + .row(SandboxRow::message("All domains (unrestricted)")), + ); + + let container = || div().p_2().elevation_2(cx).max_w_112(); + + v_flex() + .gap_4() + .child(example_group(vec![ + single_example( + "Enabled", + container() + .child(SandboxStatusTooltip::enabled( + settings_section.clone(), + Some(thread_section), + )) + .into_any_element(), + ), + single_example( + "Enabled (unrestricted, no overrides)", + container() + .child(SandboxStatusTooltip::enabled(unrestricted_section, None)) + .into_any_element(), + ), + single_example( + "Disabled for thread", + container() + .child(SandboxStatusTooltip::disabled_for_thread(settings_section)) + .into_any_element(), + ), + single_example( + "Disabled in settings", + container() + .child(SandboxStatusTooltip::disabled_in_settings()) + .into_any_element(), + ), + ])) + .into_any_element() + } +} diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 62d6209f1f..46a2dc6c74 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -910,7 +910,7 @@ impl EditPredictionButton { menu = menu.item( ContextMenuEntry::new("Configure Excluded Files") - .icon(IconName::LockOutlined) + .icon(IconName::Lock) .icon_color(Color::Muted) .documentation_aside(DocumentationSide::Left, |_| { Label::new(indoc!{" diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 6588d3f07f..a94b43e801 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -183,8 +183,8 @@ pub enum IconName { ListX, LoadCircle, LocationEdit, - LockOutlined, - LockOutlinedOff, + Lock, + LockOff, MagnifyingGlass, Maximize, MaximizeAlt,