agent_ui: Refine UI design displaying sandbox information (#59980)

Adding a series of UI tweaks to refine how we display whether a thread
is covered by sandbox. Most notably, separating all the tooltip UI stuff
into a dedicated component so we can get a preview and iterate more
easily on all of its states.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal authored and GitHub committed 2026-06-29 10:12:28 +00:00
1 parent 8ee64e6145
commit f0e8b4c555
10 files changed
+372 -235

No files matched your search

+4 -2
View File
@@ -1,4 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 6.5C3.25 5.80964 3.80964 5.25 4.5 5.25H11.5C12.1904 5.25 12.75 5.80964 12.75 6.5V12.5C12.75 13.1904 12.1904 13.75 11.5 13.75H4.5C3.80964 13.75 3.25 13.1904 3.25 12.5V6.5ZM8.75 9.66146C8.90559 9.48517 9 9.25361 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.25361 7.09441 9.48517 7.25 9.66146V11C7.25 11.4142 7.58579 11.75 8 11.75C8.41421 11.75 8.75 11.4142 8.75 11V9.66146Z" fill="black"/>
<path d="M8 9V11" stroke="#DCE0E5" stroke-width="1.2" stroke-linecap="round"/>
<path d="M8 10C8.55228 10 9 9.55228 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.55228 7.44772 10 8 10Z" fill="#DCE0E5"/>
<path d="M11 5.75H5C4.30964 5.75 3.75 6.30964 3.75 7V12C3.75 12.6904 4.30964 13.25 5 13.25H11C11.6904 13.25 12.25 12.6904 12.25 12V7C12.25 6.30964 11.6904 5.75 11 5.75Z" stroke="#DCE0E5" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M7.99998 2.79321C6.367 2.79321 5.04321 4.117 5.04321 5.74998H10.9568C10.9568 4.117 9.63296 2.79321 7.99998 2.79321Z" stroke="#DCE0E5" stroke-width="1.2" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 735 B

File renamed without changes.
+6
View File
@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 9V11" stroke="#DCE0E5" stroke-width="1.2" stroke-linecap="round"/>
<path d="M8 10C8.55228 10 9 9.55228 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.55228 7.44772 10 8 10Z" fill="#DCE0E5"/>
<path d="M11 5.75H5C4.30964 5.75 3.75 6.30964 3.75 7V12C3.75 12.6904 4.30964 13.25 5 13.25H11C11.6904 13.25 12.25 12.6904 12.25 12V7C12.25 6.30964 11.6904 5.75 11 5.75Z" stroke="#DCE0E5" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M5.779 5.74999C5.17527 4.23271 5.91585 2.5133 7.43313 1.90957C8.34244 1.54775 9.32436 1.66876 10.0952 2.15042" stroke="#DCE0E5" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 752 B

-7
View File
@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.2"/>
<path d="M8 9V11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
<circle cx="8" cy="9" r="1" fill="black"/>
<rect x="3.75" y="5.75" width="8.5" height="7.5" rx="1.25" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M2.5 2.5L13.5 13.5" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 540 B

+9 -2
View File
@@ -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)),
@@ -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<Self>) -> Option<AnyElement> {
pub fn render_sandbox_status(&mut self, cx: &mut Context<Self>) -> Option<AnyElement> {
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<AnyElement> {
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<AnyElement> {
fn sandbox_git_rows(git: &GitSandboxPolicy) -> Vec<SandboxRow> {
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<AnyElement> {
fn sandbox_fs_rows(fs: &SandboxFsPolicy) -> Vec<SandboxRow> {
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<AnyElement> {
fn sandbox_network_rows(network: &SandboxNetPolicy) -> Vec<SandboxRow> {
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<AnyElement>) -> 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<Self>) -> List {
let max_content_width = AgentSettings::get_global(cx).max_content_width;
+2
View File
@@ -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
@@ -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<SharedString>) -> Self {
Self::Message(message.into())
}
pub fn path(path: impl Into<PathBuf>) -> Self {
Self::Path(path.into())
}
pub fn git(path: impl Into<PathBuf>) -> Self {
Self::Git(path.into())
}
pub fn domain(domain: impl Into<SharedString>) -> 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<SandboxRow>,
}
impl SandboxGroup {
pub fn new(heading: impl Into<SharedString>) -> 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<Item = SandboxRow>) -> 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<SandboxGroup>,
}
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<SandboxSection>,
},
DisabledForThread {
settings: SandboxSection,
},
DisabledInSettings,
}
impl SandboxStatusTooltip {
pub fn enabled(settings: SandboxSection, thread: Option<SandboxSection>) -> 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()
}
}
@@ -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!{"
+2 -2
View File
@@ -183,8 +183,8 @@ pub enum IconName {
ListX,
LoadCircle,
LocationEdit,
LockOutlined,
LockOutlinedOff,
Lock,
LockOff,
MagnifyingGlass,
Maximize,
MaximizeAlt,