Add a setting for Threads Sidebar auto-open (#64259)

Closes https://github.com/zed-industries/zed/discussions/64266

Opening a folder in an existing window force-opens the Threads Sidebar,
and there is no way to prevent it. `open_paths` called
`MultiWorkspace::open_sidebar` unconditionally on the reused window, so
the sidebar reopened on every such open regardless of whether the user
had closed it.

This adds `agent.threads_sidebar_auto_open`, defaulting to `true` so
current behavior is unchanged. With `false`, the folder still opens in
the existing window, but the sidebar stays closed.

This path is reached when `default_open_behavior` or
`cli_default_open_behavior` is set to `existing_window`.

`open_sidebar` was doing double duty: besides showing the sidebar, it
pinned the workspace being navigated away from so it survives the
switch. When the setting is disabled, `retain_active_workspace` is
called instead, so only sidebar visibility changes.


https://github.com/user-attachments/assets/fc299df0-27e2-4ac6-98cb-d817d5f71d0e

## Tests

Two tests in `multi_workspace_tests.rs` cover both branches: the sidebar
opens by default, and stays closed when the setting is disabled while
the folder still opens in the same window.

Release Notes:

- Added `agent.threads_sidebar_auto_open` to control whether opening a
folder in an existing window also opens the Threads Sidebar
This commit is contained in:
Jack Walters authored and GitHub committed 2026-09-16 16:03:50 +00:00
1 parent 2433f1d15d
commit d9abcde22a
9 files changed
+161 -2

No files matched your search

+4
View File
@@ -1157,6 +1157,10 @@
"sidebar_side": "left",
// Default width of the Threads Sidebar.
"threads_sidebar_default_width": 300,
// Whether opening a folder in an existing window automatically opens the
// Threads Sidebar. Applies when `default_open_behavior` or
// `cli_default_open_behavior` is set to "existing_window".
"threads_sidebar_auto_open": true,
// Default fixed width when the agent panel is docked to the left or right and
// `flexible` is false.
"default_width": 640,
+1
View File
@@ -610,6 +610,7 @@ mod tests {
show_merge_conflict_indicator: true,
sidebar_side: Default::default(),
threads_sidebar_default_width: px(300.),
threads_sidebar_auto_open: true,
thinking_display: Default::default(),
}
}
@@ -215,6 +215,7 @@ pub struct AgentSettings {
pub flexible: bool,
pub sidebar_side: SidebarDockPosition,
pub threads_sidebar_default_width: Pixels,
pub threads_sidebar_auto_open: bool,
pub default_width: Pixels,
pub default_height: Pixels,
pub max_content_width: Option<Pixels>,
@@ -774,6 +775,7 @@ impl Settings for AgentSettings {
.unwrap()
.into_gpui()
.clamp(THREADS_LIST_MIN_WIDTH, THREADS_LIST_MAX_WIDTH),
threads_sidebar_auto_open: agent.threads_sidebar_auto_open.unwrap(),
default_width: agent.default_width.unwrap().into_gpui(),
default_height: agent.default_height.unwrap().into_gpui(),
max_content_width: if agent.limit_content_width.unwrap() {
+1
View File
@@ -1012,6 +1012,7 @@ mod tests {
show_merge_conflict_indicator: true,
sidebar_side: Default::default(),
threads_sidebar_default_width: px(300.),
threads_sidebar_auto_open: true,
thinking_display: Default::default(),
};
+11
View File
@@ -226,6 +226,17 @@ pub struct AgentSettingsContent {
/// Default: 300
#[schemars(range(min = 200, max = 800))]
pub threads_sidebar_default_width: Option<crate::PixelSetting>,
/// Whether opening a folder in an existing window automatically opens the
/// Threads Sidebar.
///
/// This applies when a folder opens in an existing window instead of a new
/// one, which happens when `default_open_behavior` or
/// `cli_default_open_behavior` is set to `existing_window`. When disabled,
/// the folder still opens in that window, but the sidebar stays closed
/// until you open it.
///
/// Default: true
pub threads_sidebar_auto_open: Option<bool>,
/// Default fixed width in pixels when the agent panel is docked to the left or right and
/// `flexible` is false.
///
+24 -1
View File
@@ -6829,7 +6829,7 @@ fn panels_page() -> SettingsPage {
]
}
fn agent_panel_section() -> [SettingsPageItem; 8] {
fn agent_panel_section() -> [SettingsPageItem; 9] {
[
SettingsPageItem::SectionHeader("Agent Panel"),
SettingsPageItem::SettingItem(SettingItem {
@@ -6913,6 +6913,29 @@ fn panels_page() -> SettingsPage {
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Threads Sidebar Auto Open",
description: "Whether opening a folder in an existing window automatically opens the Threads Sidebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.threads_sidebar_auto_open"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.threads_sidebar_auto_open
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.threads_sidebar_auto_open = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Default Height",
description: "Default height when the agent panel is docked to the bottom.",
@@ -316,6 +316,93 @@ async fn test_open_new_window_does_not_open_sidebar_on_existing_window(cx: &mut
.unwrap();
}
#[gpui::test]
async fn test_open_directory_in_existing_window_opens_sidebar(cx: &mut TestAppContext) {
init_test(cx);
let app_state = cx.update(AppState::test);
let fs = app_state.fs.as_fake();
fs.insert_tree(path!("/project_a"), json!({ "file.txt": "" }))
.await;
fs.insert_tree(path!("/project_b"), json!({ "file.txt": "" }))
.await;
let project = Project::test(app_state.fs.clone(), [path!("/project_a").as_ref()], cx).await;
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
cx.run_until_parked();
cx.update(|cx| {
open_paths(
&[PathBuf::from(path!("/project_b"))],
app_state,
OpenOptions::default(),
cx,
)
})
.await
.unwrap();
window
.read_with(cx, |mw, _cx| {
assert!(
mw.sidebar_open(),
"adding a directory to an existing window opens the sidebar by default",
);
assert_eq!(mw.workspaces().count(), 2);
})
.unwrap();
}
#[gpui::test]
async fn test_open_directory_in_existing_window_respects_auto_open_setting(
cx: &mut TestAppContext,
) {
init_test(cx);
let app_state = cx.update(AppState::test);
let fs = app_state.fs.as_fake();
fs.insert_tree(path!("/project_a"), json!({ "file.txt": "" }))
.await;
fs.insert_tree(path!("/project_b"), json!({ "file.txt": "" }))
.await;
cx.update(|cx| {
let mut settings = AgentSettings::get_global(cx).clone();
settings.threads_sidebar_auto_open = false;
AgentSettings::override_global(settings, cx);
});
let project = Project::test(app_state.fs.clone(), [path!("/project_a").as_ref()], cx).await;
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
cx.run_until_parked();
cx.update(|cx| {
open_paths(
&[PathBuf::from(path!("/project_b"))],
app_state,
OpenOptions::default(),
cx,
)
})
.await
.unwrap();
window
.read_with(cx, |mw, _cx| {
assert!(
!mw.sidebar_open(),
"the sidebar must stay closed when `threads_sidebar_auto_open` is disabled",
);
assert_eq!(
mw.workspaces().count(),
2,
"the directory is still added to the existing window, and the workspace it \
replaces is retained",
);
})
.unwrap();
}
#[gpui::test]
async fn test_open_directory_in_empty_workspace_does_not_open_sidebar(cx: &mut TestAppContext) {
init_test(cx);
+9 -1
View File
@@ -43,6 +43,7 @@ pub use remote::{
};
pub use toast_layer::{ToastAction, ToastLayer, ToastView};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result, anyhow};
use client::{
ChannelId, Client, ErrorExt, ParticipantIndex, Status, TypedEnvelope, User, UserStore,
@@ -11168,7 +11169,14 @@ pub fn open_paths(
open_options.requesting_window = Some(window);
window
.update(cx, |multi_workspace, _, cx| {
multi_workspace.open_sidebar(cx);
if AgentSettings::get_global(cx).threads_sidebar_auto_open {
multi_workspace.open_sidebar(cx);
} else {
// Opening the sidebar is also what pins the
// workspace we are about to navigate away from,
// so pin it here to keep it in this window.
multi_workspace.retain_active_workspace(cx);
}
})
.log_err();
}
+22
View File
@@ -145,6 +145,28 @@ If you haven’t manually resized the sidebar, its width follows changes to this
Widths saved by older versions of Zed are preserved if they differ from the previous default of 300 pixels. A saved width of 300 pixels uses this setting instead.
### Threads Sidebar Auto Open {#agent-threads-sidebar-auto-open}
- Description: Whether opening a folder in an existing window automatically opens the [Threads Sidebar](../ai/parallel-agents.md#threads-sidebar).
- Setting: `agent.threads_sidebar_auto_open`
- Default: `true`
**Options**
`true` or `false`.
This applies when a folder opens in an existing window instead of a new one, which happens when `default_open_behavior` or `cli_default_open_behavior` is set to `existing_window`. With `false`, the folder still opens in that window, but the sidebar stays closed until you open it with {#action multi_workspace::ToggleWorkspaceSidebar}.
Open the Settings Editor and search for “Threads Sidebar Auto Open”. Or add this to your `settings.json`:
```json [settings]
{
"agent": {
"threads_sidebar_auto_open": false
}
}
```
## Agent UI Font Size
- Description: The font size for text in the agent panel. Inherits the UI font size if unset.