diff --git a/assets/settings/default.json b/assets/settings/default.json index 4dc6720bd6..52c048b5c4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1152,8 +1152,11 @@ // // Default: true "flexible": true, - // Where to position the threads sidebar. Can be 'left' or 'right'. + // Where to position the sidebar holding the agent panel and the list of + // agent threads. Can be 'left' or 'right'. "sidebar_side": "left", + // Default width of the Threads Sidebar. + "threads_sidebar_default_width": 300, // Default fixed width when the agent panel is docked to the left or right and // `flexible` is false. "default_width": 640, diff --git a/crates/agent/src/tool_permissions.rs b/crates/agent/src/tool_permissions.rs index 9be962547f..188ee5a23f 100644 --- a/crates/agent/src/tool_permissions.rs +++ b/crates/agent/src/tool_permissions.rs @@ -609,6 +609,7 @@ mod tests { show_turn_stats: false, show_merge_conflict_indicator: true, sidebar_side: Default::default(), + threads_sidebar_default_width: px(300.), thinking_display: Default::default(), } } diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 05e90208a6..4b83f47f9b 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -10,7 +10,7 @@ use anyhow::Context as _; use collections::{HashSet, IndexMap}; use fs::Fs; use futures::channel::oneshot; -use gpui::{App, Pixels, SharedString}; +use gpui::{App, Pixels, SharedString, px}; use language_model::LanguageModel; use project::DisableAiSettings; use schemars::JsonSchema; @@ -31,6 +31,12 @@ pub const SUMMARIZE_THREAD_DETAILED_PROMPT: &str = include_str!("prompts/summarize_thread_detailed_prompt.txt"); pub const COMPACTION_PROMPT: &str = include_str!("prompts/compaction_prompt.txt"); +/// Bounds on the width of the threads list. They constrain the configured +/// default as well as the width the user drags to, so that no width the sidebar +/// can hold is able to crowd out the editor or collapse the list. +pub const THREADS_LIST_MIN_WIDTH: Pixels = px(200.0); +pub const THREADS_LIST_MAX_WIDTH: Pixels = px(800.0); + #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct PanelLayout { pub(crate) agent_dock: Option, @@ -208,6 +214,7 @@ pub struct AgentSettings { pub dock: DockPosition, pub flexible: bool, pub sidebar_side: SidebarDockPosition, + pub threads_sidebar_default_width: Pixels, pub default_width: Pixels, pub default_height: Pixels, pub max_content_width: Option, @@ -760,6 +767,13 @@ impl Settings for AgentSettings { button: agent.button.unwrap(), dock: agent.dock.unwrap(), sidebar_side: agent.sidebar_side.unwrap(), + // Clamped once here so that every reader gets a width the sidebar can + // actually hold, rather than each call site having to remember to. + threads_sidebar_default_width: agent + .threads_sidebar_default_width + .unwrap() + .into_gpui() + .clamp(THREADS_LIST_MIN_WIDTH, THREADS_LIST_MAX_WIDTH), 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() { @@ -991,7 +1005,7 @@ fn compile_regex_rules( #[cfg(test)] mod tests { use super::*; - use gpui::{TestAppContext, UpdateGlobal}; + use gpui::{TestAppContext, UpdateGlobal, px}; use serde_json::json; use settings::ToolPermissionMode; use settings::ToolPermissionsContent; @@ -1091,6 +1105,58 @@ mod tests { } } + #[gpui::test] + fn test_threads_sidebar_default_width(cx: &mut gpui::App) { + let store = SettingsStore::test(cx); + cx.set_global(store); + project::DisableAiSettings::register(cx); + AgentSettings::register(cx); + + assert_eq!( + AgentSettings::get_global(cx).threads_sidebar_default_width, + px(300.), + "default.json supplies the Threads Sidebar width" + ); + + SettingsStore::update_global(cx, |store, cx| { + store + .set_user_settings( + r#"{ "agent": { "threads_sidebar_default_width": 360 } }"#, + cx, + ) + .expect("user settings load"); + }); + + let settings = AgentSettings::get_global(cx); + assert_eq!(settings.threads_sidebar_default_width, px(360.)); + assert_eq!( + settings.default_width, + px(640.), + "setting the Threads Sidebar width leaves the agent panel width unchanged" + ); + assert_eq!( + settings.sidebar_side, + SidebarDockPosition::Left, + "setting the Threads Sidebar width leaves its position unchanged" + ); + + for content in [ + r#"{ "agent": { "threads_sidebar_default_width": null } }"#, + r#"{ "agent": {} }"#, + ] { + SettingsStore::update_global(cx, |store, cx| { + store + .set_user_settings(content, cx) + .expect("user settings load"); + }); + assert_eq!( + AgentSettings::get_global(cx).threads_sidebar_default_width, + px(300.), + "an unset width falls back to the default" + ); + } + } + #[gpui::test] fn test_terminal_init_command_filters_empty_without_trimming(cx: &mut gpui::App) { let store = SettingsStore::test(cx); diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 28fc3c7c62..64780d7398 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -1011,6 +1011,7 @@ mod tests { show_turn_stats: false, show_merge_conflict_indicator: true, sidebar_side: Default::default(), + threads_sidebar_default_width: px(300.), thinking_display: Default::default(), }; diff --git a/crates/settings_content/src/agent.rs b/crates/settings_content/src/agent.rs index 4c39f33ab6..887a70e37b 100644 --- a/crates/settings_content/src/agent.rs +++ b/crates/settings_content/src/agent.rs @@ -214,10 +214,18 @@ pub struct AgentSettingsContent { /// /// Default: true pub flexible: Option, - /// Where to position the threads sidebar. + /// Where to position the sidebar holding the threads list and the agent panel. /// /// Default: left pub sidebar_side: Option, + /// Default width in pixels for the Threads Sidebar. + /// + /// Values range from 200 to 800, matching the widths the sidebar can be + /// dragged to. Values outside that range are clamped into it. + /// + /// Default: 300 + #[schemars(range(min = 200, max = 800))] + pub threads_sidebar_default_width: Option, /// Default fixed width in pixels when the agent panel is docked to the left or right and /// `flexible` is false. /// diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index bbb952fdf2..f8238aba25 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -6829,7 +6829,7 @@ fn panels_page() -> SettingsPage { ] } - fn agent_panel_section() -> [SettingsPageItem; 7] { + fn agent_panel_section() -> [SettingsPageItem; 8] { [ SettingsPageItem::SectionHeader("Agent Panel"), SettingsPageItem::SettingItem(SettingItem { @@ -6890,6 +6890,29 @@ fn panels_page() -> SettingsPage { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Threads Sidebar Default Width", + description: "Default width of the Threads Sidebar. Resizing it takes precedence until you double-click the divider to reset.", + field: Box::new(SettingField { + organization_override: None, + json_path: Some("agent.threads_sidebar_default_width"), + pick: |settings_content| { + settings_content + .agent + .as_ref()? + .threads_sidebar_default_width + .as_ref() + }, + write: |settings_content, value, _| { + settings_content + .agent + .get_or_insert_default() + .threads_sidebar_default_width = 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.", diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 6e0e2593b1..2b3e1ff3bb 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -4,7 +4,7 @@ use acp_thread::ThreadStatus; use action_log::DiffStats; use agent::{ThreadStore, ZED_AGENT_ID}; use agent_client_protocol::schema::v1 as acp; -use agent_settings::AgentSettings; +use agent_settings::{AgentSettings, THREADS_LIST_MAX_WIDTH, THREADS_LIST_MIN_WIDTH}; use agent_ui::terminal_thread_metadata_store::{ TerminalThreadMetadata, TerminalThreadMetadataStore, terminal_title_prefix, }; @@ -47,7 +47,7 @@ use remote::{RemoteConnectionOptions, same_remote_connection_identity}; use ui::utils::platform_title_bar_height; use serde::{Deserialize, Serialize}; -use settings::Settings as _; +use settings::{Settings as _, SettingsStore}; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::mem; @@ -103,10 +103,6 @@ gpui::actions!( ] ); -const DEFAULT_WIDTH: Pixels = px(300.0); -const MIN_WIDTH: Pixels = px(200.0); -const MAX_WIDTH: Pixels = px(800.0); - #[derive(Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] enum SerializedSidebarView { #[default] @@ -125,6 +121,13 @@ enum NewEntryTarget { struct SerializedSidebar { #[serde(default)] width: Option, + /// Whether `width` came from the user dragging the divider. + /// + /// Legacy state recorded every width without this flag. A width other than + /// the old default of 300 pixels still identifies a manual resize. Only + /// that ambiguous default falls back to `agent.threads_sidebar_default_width`. + #[serde(default)] + width_set_by_user: bool, #[serde(default)] active_view: SerializedSidebarView, } @@ -763,6 +766,11 @@ fn create_worktree_in_workspace( pub struct Sidebar { multi_workspace: WeakEntity, width: Pixels, + /// Whether `width` came from the user rather than from + /// `agent.threads_sidebar_default_width`. Only a user-chosen width is persisted, so + /// that changing the setting is not overridden by a width the user never + /// picked. Serialization runs on many triggers besides resizing. + width_set_by_user: bool, focus_handle: FocusHandle, filter_editor: Entity, rename_editor: Entity, @@ -832,6 +840,15 @@ impl Sidebar { AgentThreadWorktreeLabelFlag::watch(cx); + cx.observe_global::(|this, cx| { + let width = AgentSettings::get_global(cx).threads_sidebar_default_width; + if !this.width_set_by_user && this.width != width { + this.width = width; + cx.notify(); + } + }) + .detach(); + let filter_editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); editor.set_placeholder_text("Search threads…", window, cx); @@ -917,7 +934,8 @@ impl Sidebar { Self { multi_workspace: multi_workspace.downgrade(), - width: DEFAULT_WIDTH, + width: AgentSettings::get_global(cx).threads_sidebar_default_width, + width_set_by_user: false, focus_handle, filter_editor, rename_editor, @@ -7792,7 +7810,11 @@ impl WorkspaceSidebar for Sidebar { } fn set_width(&mut self, width: Option, cx: &mut Context) { - self.width = width.unwrap_or(DEFAULT_WIDTH).clamp(MIN_WIDTH, MAX_WIDTH); + // `None` is the reset gesture, which hands the width back to the setting. + self.width_set_by_user = width.is_some(); + self.width = width + .unwrap_or_else(|| AgentSettings::get_global(cx).threads_sidebar_default_width) + .clamp(THREADS_LIST_MIN_WIDTH, THREADS_LIST_MAX_WIDTH); cx.notify(); } @@ -7832,7 +7854,8 @@ impl WorkspaceSidebar for Sidebar { fn serialized_state(&self, _cx: &App) -> Option { let serialized = SerializedSidebar { - width: Some(f32::from(self.width)), + width: self.width_set_by_user.then(|| f32::from(self.width)), + width_set_by_user: self.width_set_by_user, active_view: match self.view { SidebarView::ThreadList => SerializedSidebarView::ThreadList, SidebarView::Archive(_) => SerializedSidebarView::History, @@ -7848,8 +7871,13 @@ impl WorkspaceSidebar for Sidebar { cx: &mut Context, ) { if let Some(serialized) = serde_json::from_str::(state).log_err() { - if let Some(width) = serialized.width { - self.width = px(width).clamp(MIN_WIDTH, MAX_WIDTH); + // Legacy widths other than the old 300-pixel default imply a manual resize + if let Some(width) = serialized + .width + .filter(|width| serialized.width_set_by_user || *width != 300.0) + { + self.width = px(width).clamp(THREADS_LIST_MIN_WIDTH, THREADS_LIST_MAX_WIDTH); + self.width_set_by_user = true; } if serialized.active_view == SerializedSidebarView::History { cx.defer_in(window, |this, window, cx| { diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index 9d1a0abc37..68ef42ea2f 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -14,7 +14,7 @@ use agent_ui::{ }; use chrono::DateTime; use fs::{FakeFs, Fs}; -use gpui::TestAppContext; +use gpui::{TestAppContext, UpdateGlobal}; use pretty_assertions::assert_eq; use project::AgentId; use settings::SettingsStore; @@ -298,6 +298,17 @@ fn setup_sidebar_closed( sidebar } +fn set_threads_sidebar_default_width(width: f32, cx: &mut App) { + SettingsStore::update_global(cx, |store, cx| { + store + .set_user_settings( + &format!(r#"{{"agent": {{"threads_sidebar_default_width": {width}}}}}"#), + cx, + ) + .unwrap(); + }); +} + async fn save_n_test_threads( count: u32, project: &Entity, @@ -871,6 +882,229 @@ async fn test_serialization_round_trip(cx: &mut TestAppContext) { assert_eq!(width1, px(420.0)); } +#[gpui::test] +async fn test_width_reset_returns_configured_default(cx: &mut TestAppContext) { + let project = init_test_project("/my-project", cx).await; + cx.update(|cx| set_threads_sidebar_default_width(360.0, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let sidebar = setup_sidebar(&multi_workspace, cx); + + assert_eq!( + sidebar.read_with(cx, |sidebar, _| sidebar.width), + px(360.0), + "a fresh sidebar should open at the configured width" + ); + + sidebar.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(Some(px(420.0)), cx); + }); + cx.run_until_parked(); + assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.width), px(420.0)); + + sidebar.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(None, cx); + }); + cx.run_until_parked(); + + assert_eq!( + sidebar.read_with(cx, |sidebar, _| sidebar.width), + px(360.0), + "resetting the width should return to the configured width, not a fixed default" + ); +} + +#[gpui::test] +async fn test_width_follows_settings_until_manually_resized(cx: &mut TestAppContext) { + let project = init_test_project("/my-project", cx).await; + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let sidebar = setup_sidebar(&multi_workspace, cx); + + cx.update(|_window, cx| set_threads_sidebar_default_width(360.0, cx)); + cx.run_until_parked(); + assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.width), px(360.0)); + assert!(!sidebar.read_with(cx, |sidebar, _| sidebar.width_set_by_user)); + + sidebar.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(Some(px(420.0)), cx); + }); + cx.update(|_window, cx| set_threads_sidebar_default_width(500.0, cx)); + cx.run_until_parked(); + assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.width), px(420.0)); + + sidebar.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(None, cx); + }); + cx.run_until_parked(); + assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.width), px(500.0)); + + for (configured, expected) in [ + (5.0, THREADS_LIST_MIN_WIDTH), + (360.0, px(360.0)), + (5000.0, THREADS_LIST_MAX_WIDTH), + ] { + cx.update(|_window, cx| set_threads_sidebar_default_width(configured, cx)); + cx.run_until_parked(); + assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.width), expected); + let serialized = sidebar + .read_with(cx, |sidebar, cx| sidebar.serialized_state(cx)) + .expect("sidebar state is serialized"); + let serialized: SerializedSidebar = + serde_json::from_str(&serialized).expect("sidebar state is valid"); + assert_eq!(serialized.width, None); + assert!(!serialized.width_set_by_user); + } +} + +#[gpui::test] +async fn test_restored_width_preserves_legacy_resizes(cx: &mut TestAppContext) { + let project = init_test_project("/my-project", cx).await; + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + + for (state, expected, width_set_by_user) in [ + (r#"{"width":null}"#, 360.0, false), + (r#"{"width":300.0}"#, 360.0, false), + (r#"{"width":420.0}"#, 420.0, true), + (r#"{"width":300.0,"width_set_by_user":true}"#, 300.0, true), + ] { + cx.update(|_window, cx| set_threads_sidebar_default_width(360.0, cx)); + let sidebar = + cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx))); + cx.run_until_parked(); + sidebar.update_in(cx, |sidebar, window, cx| { + sidebar.restore_serialized_state(state, window, cx); + }); + cx.run_until_parked(); + assert_eq!( + sidebar.read_with(cx, |sidebar, _| sidebar.width), + px(expected) + ); + assert_eq!( + sidebar.read_with(cx, |sidebar, _| sidebar.width_set_by_user), + width_set_by_user + ); + + let serialized = sidebar + .read_with(cx, |sidebar, cx| sidebar.serialized_state(cx)) + .expect("sidebar state is serialized"); + let serialized: SerializedSidebar = + serde_json::from_str(&serialized).expect("sidebar state is valid"); + assert_eq!(serialized.width, width_set_by_user.then_some(expected)); + assert_eq!(serialized.width_set_by_user, width_set_by_user); + + cx.update(|_window, cx| set_threads_sidebar_default_width(500.0, cx)); + cx.run_until_parked(); + assert_eq!( + sidebar.read_with(cx, |sidebar, _| sidebar.width), + px(if width_set_by_user { expected } else { 500.0 }) + ); + } +} + +#[gpui::test] +async fn test_configured_width_is_clamped_into_range(cx: &mut TestAppContext) { + let project = init_test_project("/my-project", cx).await; + cx.update(|cx| set_threads_sidebar_default_width(5000.0, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + + let wide_sidebar = + cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx))); + cx.run_until_parked(); + assert_eq!( + wide_sidebar.read_with(cx, |sidebar, _| sidebar.width), + THREADS_LIST_MAX_WIDTH, + "a configured width above the maximum should be clamped at construction" + ); + + wide_sidebar.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(None, cx); + }); + cx.run_until_parked(); + assert_eq!( + wide_sidebar.read_with(cx, |sidebar, _| sidebar.width), + THREADS_LIST_MAX_WIDTH, + "resetting the width should not restore the unclamped configured width" + ); + + cx.update(|_window, cx| set_threads_sidebar_default_width(5.0, cx)); + let narrow_sidebar = + cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx))); + cx.run_until_parked(); + assert_eq!( + narrow_sidebar.read_with(cx, |sidebar, _| sidebar.width), + THREADS_LIST_MIN_WIDTH, + "a configured width below the minimum should be clamped at construction" + ); +} + +#[gpui::test] +async fn test_only_a_user_chosen_width_is_persisted(cx: &mut TestAppContext) { + let project = init_test_project("/my-project", cx).await; + cx.update(|cx| set_threads_sidebar_default_width(360.0, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let sidebar = setup_sidebar(&multi_workspace, cx); + + // Serialization runs on many triggers besides resizing, so a sidebar the + // user has never resized must not record a width at all. + let untouched = sidebar + .read_with(cx, |sidebar, cx| sidebar.serialized_state(cx)) + .expect("serialized_state should return Some"); + assert!( + !untouched.contains("360"), + "an unresized sidebar should not persist its width, got {untouched}" + ); + + // A legacy width matching the old default is ambiguous, so the setting takes precedence + sidebar.update_in(cx, |sidebar, window, cx| { + sidebar.restore_serialized_state(r#"{"width":300.0}"#, window, cx); + }); + cx.run_until_parked(); + assert_eq!( + sidebar.read_with(cx, |sidebar, _| sidebar.width), + px(360.0), + "the legacy default width falls back to the setting" + ); + + // A width the user picked survives, and keeps surviving across restores. + sidebar.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(Some(px(420.0)), cx); + }); + cx.run_until_parked(); + let resized = sidebar + .read_with(cx, |sidebar, cx| sidebar.serialized_state(cx)) + .expect("serialized_state should return Some"); + + let restored = + cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx))); + cx.run_until_parked(); + restored.update_in(cx, |sidebar, window, cx| { + sidebar.restore_serialized_state(&resized, window, cx); + }); + cx.run_until_parked(); + assert_eq!( + restored.read_with(cx, |sidebar, _| sidebar.width), + px(420.0), + "a user-chosen width should survive a restore" + ); + + // Resetting gives the width back to the setting, and stops persisting it. + restored.update_in(cx, |sidebar, _window, cx| { + sidebar.set_width(None, cx); + }); + cx.run_until_parked(); + let after_reset = restored + .read_with(cx, |sidebar, cx| sidebar.serialized_state(cx)) + .expect("serialized_state should return Some"); + assert!( + !after_reset.contains("420"), + "resetting should stop persisting the width, got {after_reset}" + ); +} + #[gpui::test] async fn test_restore_serialized_archive_view_does_not_panic(cx: &mut TestAppContext) { // A regression test to ensure that restoring a serialized archive view does not panic. @@ -884,6 +1118,7 @@ async fn test_restore_serialized_archive_view_does_not_panic(cx: &mut TestAppCon let serialized = serde_json::to_string(&SerializedSidebar { width: Some(400.0), + width_set_by_user: true, active_view: SerializedSidebarView::History, }) .expect("serialization should succeed"); diff --git a/docs/src/ai/parallel-agents.md b/docs/src/ai/parallel-agents.md index 70f0ff4f93..8775bf947d 100644 --- a/docs/src/ai/parallel-agents.md +++ b/docs/src/ai/parallel-agents.md @@ -19,6 +19,10 @@ Terminal Threads also appear as entries in the sidebar alongside agent threads, To focus the sidebar without toggling it, use {#kb multi_workspace::FocusWorkspaceSidebar}. To search your threads, press {#kb agents_sidebar::FocusSidebarFilter} while the sidebar is focused. +To change the sidebar’s default width, open the Settings Editor and search for “Threads Sidebar Default Width”. See [`agent.threads_sidebar_default_width`](../reference/all-settings.md#agent-threads-sidebar-default-width) for values and a configuration example. + +Drag the divider to override the configured width. Double-click it to return to the setting and follow subsequent settings changes. + ### Switching Threads {#switching-threads} Click any thread in the sidebar to switch to it. The Agent Panel updates to show that thread's conversation. diff --git a/docs/src/reference/all-settings.md b/docs/src/reference/all-settings.md index 4ac82c90cc..cf70ae1c14 100644 --- a/docs/src/reference/all-settings.md +++ b/docs/src/reference/all-settings.md @@ -121,6 +121,30 @@ To use a fixed reset width, disable flexible sizing in the Settings Editor. Or a See [Agent Panel visual customization](../visual-customization.md#agent-panel) for other panel appearance settings. +### Threads Sidebar Default Width {#agent-threads-sidebar-default-width} + +- Description: Default width in pixels of the [Threads Sidebar](../ai/parallel-agents.md#threads-sidebar). +- Setting: `agent.threads_sidebar_default_width` +- Default: `300` + +**Options** + +Numbers from `200` to `800` pixels (inclusive). Values outside this range are clamped to the nearest limit. + +Open the Settings Editor and search for “Threads Sidebar Default Width”. Or add this to your `settings.json`: + +```json [settings] +{ + "agent": { + "threads_sidebar_default_width": 360 + } +} +``` + +If you haven’t manually resized the sidebar, its width follows changes to this setting immediately. A manually resized width takes precedence until you double-click the divider to reset it. After resetting, the sidebar follows this setting again. + +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. + ## Agent UI Font Size - Description: The font size for text in the agent panel. Inherits the UI font size if unset.