Add a setting for the Threads Sidebar width (#62883)

## Objective

The Threads Sidebar opens at a hard-coded 300px and can be resized only
by dragging. `agent.default_width` controls the Agent Panel rather than
the sidebar, leaving no way to configure the sidebar’s preferred width.

## Solution

Add `agent.threads_sidebar_default_width` for the width used when no
user-chosen sidebar width is stored. The value is clamped to the
existing 200–800px drag range. Existing, unresized sidebars follow
settings changes immediately. Double-clicking the divider resets the
sidebar to the configured width and resumes following subsequent
settings changes.

The sidebar previously saved its width whenever any sidebar state
changed, not only when the divider was dragged. `SerializedSidebar` now
records `width_set_by_user`, so settings-driven widths are no longer
persisted. Manually resized widths take precedence until the divider is
reset.

## Testing

- `test_threads_sidebar_default_width` in
`crates/agent_settings/src/agent_settings.rs` covers the default, a user
override, fallback for `null` and an unset field, and preservation of
the Agent Panel width and sidebar position.
- Five width-focused tests in `crates/sidebar/src/sidebar_tests.rs`
cover reset behavior, live settings updates, clamping, legacy-width
restoration, and persistence. They also check that manually resized
widths resist settings changes and that resetting resumes following the
setting.
- To check by hand: set `"agent": { "threads_sidebar_default_width": 360
}`, reset the divider, and confirm the sidebar is 360px wide. Change the
setting and confirm the width updates immediately. Drag it wider,
restart, and confirm the dragged width is kept. Double-click the divider
and confirm it returns to the configured width. Set the value to `5000`
and confirm the untouched sidebar clamps to 800px.

## Self-Review Checklist:

- [x] I’ve reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed’s UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- Added `agent.threads_sidebar_default_width` for setting the width of
the Threads Sidebar.
This commit is contained in:
Dom Porada authored and GitHub committed 2026-09-15 14:53:12 +00:00
1 parent ee7af09145
commit aef893e276
10 files changed
+410 -17

No files matched your search

+4 -1
View File
@@ -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,
+1
View File
@@ -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(),
}
}
+68 -2
View File
@@ -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<DockPosition>,
@@ -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<Pixels>,
@@ -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);
+1
View File
@@ -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(),
};
+9 -1
View File
@@ -214,10 +214,18 @@ pub struct AgentSettingsContent {
///
/// Default: true
pub flexible: Option<bool>,
/// 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<SidebarDockPosition>,
/// 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<crate::PixelSetting>,
/// 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; 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.",
+39 -11
View File
@@ -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<f32>,
/// 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<MultiWorkspace>,
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<Editor>,
rename_editor: Entity<Editor>,
@@ -832,6 +840,15 @@ impl Sidebar {
AgentThreadWorktreeLabelFlag::watch(cx);
cx.observe_global::<SettingsStore>(|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<Pixels>, cx: &mut Context<Self>) {
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<String> {
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<Self>,
) {
if let Some(serialized) = serde_json::from_str::<SerializedSidebar>(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| {
+236 -1
View File
@@ -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<project::Project>,
@@ -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");
+4
View File
@@ -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.
+24
View File
@@ -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.