From ebe92db8951f29cbde79fdaa917b01cc8795b43a Mon Sep 17 00:00:00 2001 From: Michael Atwood <70112949+doowta@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:56:08 -0500 Subject: [PATCH 1/6] Agent: inset sidebar row highlights Keep sidebar highlights equally inset without moving their text. Share six-pixel corners and one-pixel vertical margins across task, section, and computer rows. Co-Authored-By: Codex GPT-6.1-Sol --- apps/maple-agent/app/src/ui/chat/sidebar.rs | 45 ++++++++++++------- .../app/src/ui/chat/sidebar_computers.rs | 23 +++++++--- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/apps/maple-agent/app/src/ui/chat/sidebar.rs b/apps/maple-agent/app/src/ui/chat/sidebar.rs index e753d5508..8ee1e6012 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar.rs @@ -85,6 +85,9 @@ use crate::ui::widgets; const SEARCH_ROW_RADIUS: gpui::Pixels = px(6.); +/// Corner radius of a sidebar row's hover and selected highlight. +const ROW_RADIUS: gpui::Pixels = px(6.); + /// Geometry shared by the expanded wordmark row and the collapsed mark. pub(super) const WORDMARK_LEFT: gpui::Pixels = px(16.); pub(super) const WORDMARK_HEIGHT: gpui::Pixels = px(16.); @@ -2476,15 +2479,17 @@ impl Sidebar { let expanded = self.section_expanded(section); div() .id(gpui::SharedString::from(section.element_id())) + .w_auto() + .mx_2() + .my(px(1.)) .mt_4() - .w_full() .flex() .items_center() .gap_1p5() .mb_1() - .px_4() + .px_2() .py_1() - .rounded(theme::RADIUS_SM) + .rounded(ROW_RADIUS) .hover(|style| { style .bg(gpui::rgb(theme::bg_sidebar_row_hover())) @@ -2568,15 +2573,17 @@ impl Sidebar { .role(gpui::Role::Button) .aria_label(format!("Archived tasks {count_label}")) .aria_expanded(expanded) + .w_auto() + .mx_2() + .my(px(1.)) .mt_5() - .w_full() .flex() .items_center() .gap_1p5() .mb_1() - .px_4() + .px_2() .py_1() - .rounded(theme::RADIUS_SM) + .rounded(ROW_RADIUS) .hover(|style| { style .bg(gpui::rgb(theme::bg_sidebar_row_hover())) @@ -2657,12 +2664,14 @@ impl Sidebar { .flex() .items_center() .gap_1p5() - .w_full() - .pl_4() + .w_auto() + .mx_2() + .my(px(1.)) + .pl_2() // Room for the new-project button, which sits over this end. - .pr(px(46.)) + .pr(px(38.)) .py_1() - .rounded(theme::RADIUS_SM) + .rounded(ROW_RADIUS) .text_sm() .font_weight(gpui::FontWeight::MEDIUM) .text_color(gpui::rgb(theme::text_primary())) @@ -2939,7 +2948,9 @@ impl Sidebar { let renaming = rename_field.is_some(); div() .relative() - .w_full() + .w_auto() + .mx_2() + .my(px(1.)) .id(row.element_id.clone()) .role(gpui::Role::ListBoxOption) .accessibility_id(row.id.to_string()) @@ -2951,11 +2962,11 @@ impl Sidebar { .flex() .items_center() .gap_1p5() - .when(archived, |row| row.pl_6()) - .when(!archived, |row| row.pl_8()) - .pr_6() + .when(archived, |row| row.pl_4()) + .when(!archived, |row| row.pl_6()) + .pr_4() .py_1() - .rounded(theme::RADIUS_MD) + .rounded(ROW_RADIUS) .text_sm() .when(is_selected, |row| { row.bg(gpui::rgb(theme::bg_sidebar_row_selected())) @@ -3035,13 +3046,13 @@ impl Sidebar { .bottom_0() .when(is_pinned, |overlay| overlay.right(px(26.))) .when(!is_pinned, |overlay| { - overlay.right_0().rounded_r(theme::RADIUS_MD) + overlay.right_0().rounded_r(ROW_RADIUS) }) .flex() .items_center() .gap_1p5() .pl_2() - .pr_6() + .pr_4() .group_hover(row.group.clone(), |style| { style.bg(gpui::rgb(theme::bg_sidebar_row_hover())) }) diff --git a/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs b/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs index 26e7c5cb0..5210f6cac 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs @@ -1192,8 +1192,11 @@ impl Sidebar { .id(SharedString::from(format!("computer-page-{index}"))) .role(gpui::Role::Button) .aria_label(label.clone()) - .px_8() + .mx_2() + .my(px(1.)) + .px_6() .py_2() + .rounded(ROW_RADIUS) .text_xs() .text_color(gpui::rgb(theme::text_secondary())) .when(selected, |row| { @@ -1221,9 +1224,12 @@ impl Sidebar { .id(project.id.clone()) .role(gpui::Role::Button) .aria_label(project.label.clone()) + .mx_2() + .my(px(1.)) .mt_3() - .px_4() + .px_2() .py_1p5() + .rounded(ROW_RADIUS) .flex() .gap_2() .items_center() @@ -1262,13 +1268,15 @@ impl Sidebar { .role(gpui::Role::ListBoxOption) .aria_label(task.title.clone()) .aria_selected(false) - .pl_8() - .pr_6() + .mx_2() + .my(px(1.)) + .pl_6() + .pr_4() .py_1() .flex() .gap_2() .items_center() - .rounded(theme::RADIUS_MD) + .rounded(ROW_RADIUS) .text_sm() .text_color(gpui::rgb(theme::text_primary())) .when(selected, |row| { @@ -1308,9 +1316,12 @@ impl Sidebar { .id(SharedString::from(format!("computer-{index}"))) .role(gpui::Role::Button) .aria_label(computer.label.clone()) + .mx_2() + .my(px(1.)) .mt_3() - .px_4() + .px_2() .py_2() + .rounded(ROW_RADIUS) .flex() .flex_col() .gap_1() From c7795143e3adcdf9c8b468119ee9f4761750ac98 Mon Sep 17 00:00:00 2001 From: Michael Atwood <70112949+doowta@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:56:08 -0500 Subject: [PATCH 2/6] Agent: brighten a hovered selected row Distinguish hovering a selected task from its resting selection. Paint hover actions and other computers' selected rows with the same theme token. Co-Authored-By: Codex GPT-6.1-Sol --- apps/maple-agent/app/src/ui/chat/sidebar.rs | 15 ++++++----- .../app/src/ui/chat/sidebar_computers.rs | 25 +++++++------------ apps/maple-agent/app/src/ui/theme.rs | 1 + 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/apps/maple-agent/app/src/ui/chat/sidebar.rs b/apps/maple-agent/app/src/ui/chat/sidebar.rs index 8ee1e6012..d6593b3d5 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar.rs @@ -2946,6 +2946,11 @@ impl Sidebar { let pin_id = row.pin_id.clone(); let rename_field = self.task_rename_field(&row.id, cx); let renaming = rename_field.is_some(); + let hover_bg = if is_selected { + theme::bg_sidebar_row_selected_hover() + } else { + theme::bg_sidebar_row_hover() + }; div() .relative() .w_auto() @@ -2976,11 +2981,7 @@ impl Sidebar { // A row being renamed is a text field: no hover highlight and // no hover actions over it, so the edit is plain to see. .when(!renaming, |el| { - el.hover(|style| { - style - .bg(gpui::rgb(theme::bg_sidebar_row_hover())) - .cursor_pointer() - }) + el.hover(|style| style.bg(gpui::rgb(hover_bg)).cursor_pointer()) }) .on_click(cx.listener(move |_this, _event, _window, cx| { cx.emit(SidebarEvent::Select(session_id.to_string())); @@ -3053,9 +3054,7 @@ impl Sidebar { .gap_1p5() .pl_2() .pr_4() - .group_hover(row.group.clone(), |style| { - style.bg(gpui::rgb(theme::bg_sidebar_row_hover())) - }) + .group_hover(row.group.clone(), |style| style.bg(gpui::rgb(hover_bg))) .child(self.task_menu_trigger(row, task, window, cx)) .child(row_action( row.rename_id.clone(), diff --git a/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs b/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs index 5210f6cac..a33f81b76 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs @@ -1183,6 +1183,11 @@ impl Sidebar { selected: bool, cx: &mut Context, ) -> AnyElement { + let hover_bg = if selected { + theme::bg_sidebar_row_selected_hover() + } else { + theme::bg_sidebar_row_hover() + }; match entry { SidebarEntry::ComputerMore(index) => { let action = self.group_pages.more[index].clone(); @@ -1204,10 +1209,7 @@ impl Sidebar { .bg(gpui::rgb(theme::bg_sidebar_row_hover())) }) .when(!loading, |row| { - row.hover(|s| { - s.cursor_pointer() - .bg(gpui::rgb(theme::bg_sidebar_row_hover())) - }) + row.hover(|s| s.cursor_pointer().bg(gpui::rgb(hover_bg))) }) .on_click(cx.listener(move |this, _, _, cx| { if !loading { @@ -1239,10 +1241,7 @@ impl Sidebar { row.aria_active_descendant() .bg(gpui::rgb(theme::bg_sidebar_row_hover())) }) - .hover(|s| { - s.bg(gpui::rgb(theme::bg_sidebar_row_hover())) - .cursor_pointer() - }) + .hover(|s| s.bg(gpui::rgb(hover_bg)).cursor_pointer()) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(SidebarEvent::Navigate(request.clone())) })) @@ -1283,10 +1282,7 @@ impl Sidebar { row.aria_active_descendant() .bg(gpui::rgb(theme::bg_sidebar_row_hover())) }) - .hover(|s| { - s.bg(gpui::rgb(theme::bg_sidebar_row_hover())) - .cursor_pointer() - }) + .hover(|s| s.bg(gpui::rgb(hover_bg)).cursor_pointer()) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(SidebarEvent::Navigate(request.clone())) })) @@ -1327,10 +1323,7 @@ impl Sidebar { .gap_1() .text_xs() .text_color(gpui::rgb(theme::text_secondary())) - .hover(|s| { - s.bg(gpui::rgb(theme::bg_sidebar_row_hover())) - .cursor_pointer() - }) + .hover(|s| s.bg(gpui::rgb(hover_bg)).cursor_pointer()) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(SidebarEvent::Navigate(request.clone())) })) diff --git a/apps/maple-agent/app/src/ui/theme.rs b/apps/maple-agent/app/src/ui/theme.rs index 0551fef9f..d54f60694 100644 --- a/apps/maple-agent/app/src/ui/theme.rs +++ b/apps/maple-agent/app/src/ui/theme.rs @@ -242,6 +242,7 @@ tokens! { bg_sidebar_chrome: 0x1e1e24, 0xffffff; bg_sidebar_row_hover: 0x32323a, 0xe8e8ed; bg_sidebar_row_selected: 0x3a3a44, 0xd1d2dc; + bg_sidebar_row_selected_hover: 0x43434e, 0xc5c6d1; /// Display headings (Array face): pebble-300 on dark, pebble-800 on /// light, as the brand kit sets its section titles. From 94ae0b17665f39e39a56a21bedb4165e70330ac6 Mon Sep 17 00:00:00 2001 From: Michael Atwood <70112949+doowta@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:56:08 -0500 Subject: [PATCH 3/6] Agent: soften sidebar text Use a dedicated sidebar text color in both themes. Keep New Task in the same plain color as task rows, and let selection and project headings use regular weight. Co-Authored-By: Codex GPT-6.1-Sol --- apps/maple-agent/app/src/ui/chat/sidebar.rs | 10 ++++------ apps/maple-agent/app/src/ui/chat/sidebar_computers.rs | 2 +- apps/maple-agent/app/src/ui/theme.rs | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/maple-agent/app/src/ui/chat/sidebar.rs b/apps/maple-agent/app/src/ui/chat/sidebar.rs index d6593b3d5..08d821d84 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar.rs @@ -2419,7 +2419,7 @@ impl Sidebar { "New Task", ChatCommand::NewTask, &super::NewTask, - theme::accent(), + theme::text_sidebar(), ) }; div() @@ -2673,8 +2673,7 @@ impl Sidebar { .py_1() .rounded(ROW_RADIUS) .text_sm() - .font_weight(gpui::FontWeight::MEDIUM) - .text_color(gpui::rgb(theme::text_primary())) + .text_color(gpui::rgb(theme::text_sidebar())) .hover(|style| { style .bg(gpui::rgb(theme::bg_sidebar_row_hover())) @@ -2975,9 +2974,8 @@ impl Sidebar { .text_sm() .when(is_selected, |row| { row.bg(gpui::rgb(theme::bg_sidebar_row_selected())) - .font_weight(gpui::FontWeight::MEDIUM) }) - .text_color(gpui::rgb(theme::text_primary())) + .text_color(gpui::rgb(theme::text_sidebar())) // A row being renamed is a text field: no hover highlight and // no hover actions over it, so the edit is plain to see. .when(!renaming, |el| { @@ -3149,7 +3147,7 @@ impl Render for Sidebar { .flex() .items_center() .child( - wordmark(WORDMARK_HEIGHT, theme::text_primary()) + wordmark(WORDMARK_HEIGHT, theme::text_sidebar()) .debug_selector(|| "sidebar-wordmark".to_string()), ), ), diff --git a/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs b/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs index a33f81b76..7e0077475 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar_computers.rs @@ -1277,7 +1277,7 @@ impl Sidebar { .items_center() .rounded(ROW_RADIUS) .text_sm() - .text_color(gpui::rgb(theme::text_primary())) + .text_color(gpui::rgb(theme::text_sidebar())) .when(selected, |row| { row.aria_active_descendant() .bg(gpui::rgb(theme::bg_sidebar_row_hover())) diff --git a/apps/maple-agent/app/src/ui/theme.rs b/apps/maple-agent/app/src/ui/theme.rs index d54f60694..81f367c7c 100644 --- a/apps/maple-agent/app/src/ui/theme.rs +++ b/apps/maple-agent/app/src/ui/theme.rs @@ -203,6 +203,8 @@ tokens! { // Dark body is Pebble 200 (~12:1 on the well), not Pebble 50, so // Regular and SemiBold stay distinct. Headings sit one step brighter. text_primary: 0xd1d2dc, 0x171717; + /// Sidebar text stays softer than body text in both themes. + text_sidebar: 0xbcbdc8, 0x45454b; text_heading: 0xe8e8ed, 0x171717; text_secondary: 0xbabccb, 0x525252; /// Captions. Light sits between neutral-500 and 600: neutral-400 was From ee9da8be678cbf3992d6b6b3571dd8d720957862 Mon Sep 17 00:00:00 2001 From: Michael Atwood <70112949+doowta@users.noreply.github.com> Date: Sat, 24 Oct 2026 10:56:55 -0500 Subject: [PATCH 4/6] Agent: calmer sidebar section headings Use sentence case and regular muted text for section labels, with chevrons after them. Put top spacing on heading rows, and remove it between adjacent folded sections while retaining the gap after expanded task rows. Co-Authored-By: Codex GPT-6.1-Sol --- apps/maple-agent/app/src/ui/chat/mod.rs | 6 +- apps/maple-agent/app/src/ui/chat/sidebar.rs | 156 +++++++++++++++++--- 2 files changed, 141 insertions(+), 21 deletions(-) diff --git a/apps/maple-agent/app/src/ui/chat/mod.rs b/apps/maple-agent/app/src/ui/chat/mod.rs index 1072395c8..e7512d574 100644 --- a/apps/maple-agent/app/src/ui/chat/mod.rs +++ b/apps/maple-agent/app/src/ui/chat/mod.rs @@ -7263,12 +7263,10 @@ fn head_change_event(event: ¬ify::Event) -> bool { .any(|path| path.file_name().is_some_and(|name| name == "HEAD")) } -/// Last path component of a project root, for chips and the sidebar. -/// Upper-case section heading in the sidebar. +/// Section heading in the sidebar. fn section_label(text: &'static str) -> Div { div() - .text_xs() - .font_weight(gpui::FontWeight::SEMIBOLD) + .text_sm() .text_color(gpui::rgb(theme::text_muted())) .child(text) } diff --git a/apps/maple-agent/app/src/ui/chat/sidebar.rs b/apps/maple-agent/app/src/ui/chat/sidebar.rs index 08d821d84..2e620052a 100644 --- a/apps/maple-agent/app/src/ui/chat/sidebar.rs +++ b/apps/maple-agent/app/src/ui/chat/sidebar.rs @@ -360,9 +360,9 @@ pub(super) enum SidebarSection { impl SidebarSection { pub(super) fn label(self) -> &'static str { match self { - Self::Pinned => "PINNED", - Self::Active => "ACTIVE", - Self::Settled => "SETTLED", + Self::Pinned => "Pinned", + Self::Active => "Active", + Self::Settled => "Settled", } } @@ -423,6 +423,7 @@ pub(super) struct Sidebar { // The list and its sections. list: gpui::ListState, entries: Vec, + heading_gaps: Vec, rows: Vec, pinned_rows: Vec, active_rows: Vec, @@ -687,6 +688,7 @@ impl Sidebar { vim_active: false, list: gpui::ListState::new(0, gpui::ListAlignment::Top, px(200.)), entries: Vec::new(), + heading_gaps: Vec::new(), rows: Vec::new(), pinned_rows: Vec::new(), active_rows: Vec::new(), @@ -1453,6 +1455,20 @@ impl Sidebar { if self.computers.len() > 1 && self.project_filter.is_none() { entries = self.computer_entries(); } + let heading_gaps = entries + .iter() + .enumerate() + .map(|(ix, entry)| { + matches!( + entry, + SidebarEntry::SectionLabel(_) | SidebarEntry::ArchivedHeader + ) && !matches!( + ix.checked_sub(1).and_then(|ix| entries.get(ix)), + Some(SidebarEntry::SectionLabel(_) | SidebarEntry::ArchivedHeader) + ) + }) + .collect(); + let old_gaps = std::mem::replace(&mut self.heading_gaps, heading_gaps); let old = std::mem::replace(&mut self.entries, entries); if self.application_vim_enabled { self.rebuild_vim_targets(); @@ -1479,6 +1495,14 @@ impl Sidebar { self.entries.len() - prefix - suffix, ); } + // A retained heading can gain or lose its gap when its predecessor + // changes. Invalidate its cached height even when it is off screen. + if suffix > 0 { + let row = self.entries.len() - suffix; + if self.heading_gaps[row] != old_gaps[old.len() - suffix] { + self.list.remeasure_items(row..row + 1); + } + } if self.application_vim_enabled { self.vim_reconcile(); } @@ -2391,6 +2415,7 @@ impl Sidebar { ) -> AnyElement { let selected = self.selected.as_deref(); let chat = self.chat.clone(); + let heading_gap = self.heading_gaps.get(ix).copied().unwrap_or(false); let entry = match self.entries.get(ix).copied() { Some( entry @ (SidebarEntry::ComputerProject(_) @@ -2482,11 +2507,10 @@ impl Sidebar { .w_auto() .mx_2() .my(px(1.)) - .mt_4() + .when(heading_gap, |row| row.mt_3()) .flex() .items_center() - .gap_1p5() - .mb_1() + .gap_1() .px_2() .py_1() .rounded(ROW_RADIUS) @@ -2498,6 +2522,7 @@ impl Sidebar { .on_click(cx.listener(move |this, _event, _window, cx| { this.toggle_section_expanded(section, cx); })) + .child(section_label(section.label())) .child(icon( if expanded { "chevron-down" @@ -2507,7 +2532,6 @@ impl Sidebar { px(14.), theme::text_secondary(), )) - .child(section_label(section.label())) .into_any_element() } Some(SidebarEntry::Empty(reason)) => { @@ -2576,11 +2600,10 @@ impl Sidebar { .w_auto() .mx_2() .my(px(1.)) - .mt_5() + .when(heading_gap, |row| row.mt_3()) .flex() .items_center() - .gap_1p5() - .mb_1() + .gap_1() .px_2() .py_1() .rounded(ROW_RADIUS) @@ -2592,6 +2615,13 @@ impl Sidebar { .on_click(cx.listener(|this, _event, _window, cx| { this.toggle_archived_visibility(cx); })) + .child(section_label("Archived")) + .child( + div() + .text_xs() + .text_color(gpui::rgb(theme::text_muted())) + .child(count_label), + ) .child(icon( if expanded { "chevron-down" @@ -2601,13 +2631,6 @@ impl Sidebar { px(14.), theme::text_secondary(), )) - .child(section_label("ARCHIVED")) - .child( - div() - .text_xs() - .text_color(gpui::rgb(theme::text_muted())) - .child(count_label), - ) .into_any_element() } None => div().into_any_element(), @@ -3310,6 +3333,105 @@ fn remote_root_display_name(root: &str) -> String { mod tests { use super::{remote_root_display_name, root_display_name}; + #[gpui::test] + fn sidebar_remeasures_an_offscreen_heading_when_its_gap_changes(cx: &mut gpui::TestAppContext) { + use super::*; + cx.executor().allow_parking(); + let backend = + Arc::new(AgentBackend::for_test("http://127.0.0.1:9".into(), String::new()).unwrap()); + let chat = cx.new(|cx| ChatScreen::new_without_start(backend, "user".into(), cx)); + let sidebar = chat.update(cx, |chat, _| chat.sidebar.clone()); + sidebar.update(cx, |sidebar, cx| { + sidebar.application_vim_enabled = true; + sidebar.list = gpui::ListState::new(0, gpui::ListAlignment::Top, px(0.)); + let sessions = (0..102) + .map(|ix| AgentSessionSummary { + id: format!("s{ix}"), + title: format!("Task {ix}"), + project_root: "/tmp/proj".into(), + created_ms: 0, + updated_ms: 0, + message_count: 1, + model: None, + web_enabled: true, + state: if ix == 101 { + AgentTaskState::Settled + } else { + AgentTaskState::Active + }, + acp: false, + }) + .collect(); + sidebar.pinned_tasks = (0..100).map(|ix| format!("s{ix}")).collect(); + sidebar.set_sessions(sessions, cx); + }); + let list = sidebar.update(cx, |sidebar, _| sidebar.list.clone()); + struct Host(Entity); + impl Render for Host { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + div() + .w(px(300.)) + .h(px(400.)) + .flex() + .flex_col() + .child(self.0.clone()) + } + } + let (_host, cx) = cx.add_window_view(|_, _| Host(sidebar.clone())); + cx.simulate_resize(gpui::size(px(300.), px(400.))); + let settled = |cx: &mut gpui::VisualTestContext| { + sidebar.update(cx, |sidebar, _| { + sidebar + .entries + .iter() + .position(|entry| *entry == SidebarEntry::SectionLabel(SidebarSection::Settled)) + .unwrap() + }) + }; + let row = settled(cx); + sidebar.update(cx, |sidebar, cx| { + sidebar.list.scroll_to(gpui::ListOffset { + item_ix: row, + offset_in_item: px(0.), + }); + cx.notify(); + }); + cx.run_until_parked(); + let height = list.bounds_for_item(row).unwrap().size.height; + for expanded in [false, true] { + sidebar.update(cx, |sidebar, cx| { + sidebar.list.scroll_to(gpui::ListOffset::default()); + cx.notify(); + }); + cx.run_until_parked(); + let row = settled(cx); + assert!(list.item_is_below_viewport(row).unwrap()); + assert!(list.bounds_for_item(row).is_some()); + sidebar.update(cx, |sidebar, cx| { + sidebar.toggle_section_expanded(SidebarSection::Active, cx); + }); + let row = settled(cx); + assert!( + list.bounds_for_item(row).is_none(), + "discard the off-screen heading's old measurement" + ); + sidebar.update(cx, |sidebar, cx| { + sidebar.list.scroll_to(gpui::ListOffset { + item_ix: row, + offset_in_item: px(0.), + }); + cx.notify(); + }); + cx.run_until_parked(); + let measured = list.bounds_for_item(row).unwrap().size.height; + if expanded { + assert_eq!(measured, height); + } else { + assert!(measured < height, "adjacent headings lose the section gap"); + } + } + } + #[test] fn remote_project_labels_use_the_owners_path_syntax() { assert_eq!(remote_root_display_name(r"C:\Users\Alex\Maple"), "Maple"); From 2b7165536eeab8a1f0f05e848da7ad7cc17c96d7 Mon Sep 17 00:00:00 2001 From: marks Date: Mon, 26 Oct 2026 14:37:21 -0500 Subject: [PATCH 5/6] Agent: keep sidebar mark color consistent when collapsed --- apps/maple-agent/app/src/ui/chat/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/maple-agent/app/src/ui/chat/mod.rs b/apps/maple-agent/app/src/ui/chat/mod.rs index e7512d574..bece3c0f8 100644 --- a/apps/maple-agent/app/src/ui/chat/mod.rs +++ b/apps/maple-agent/app/src/ui/chat/mod.rs @@ -7023,7 +7023,7 @@ impl ChatScreen { this.execute_command(ChatCommand::ToggleSidebar, window, cx); })) .child( - crate::ui::icons::wordmark(sidebar::WORDMARK_HEIGHT, theme::text_primary()) + crate::ui::icons::wordmark(sidebar::WORDMARK_HEIGHT, theme::text_sidebar()) .path("icons/maple-mark.svg") .debug_selector(|| "sidebar-rail-mark".to_string()), ), From 9bccfe8a1e4b72c905a1f8c779ce0aa24253252f Mon Sep 17 00:00:00 2001 From: marks Date: Tue, 27 Oct 2026 09:07:15 -0500 Subject: [PATCH 6/6] Agent: give sidebar toggle more space beside window controls --- apps/maple-agent/app/src/ui/chat/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/maple-agent/app/src/ui/chat/mod.rs b/apps/maple-agent/app/src/ui/chat/mod.rs index bece3c0f8..da5e5eba7 100644 --- a/apps/maple-agent/app/src/ui/chat/mod.rs +++ b/apps/maple-agent/app/src/ui/chat/mod.rs @@ -132,9 +132,9 @@ impl SidebarTransition { /// Height of the task header and the clear strip above the collapsed rail. const CHAT_HEADER_HEIGHT: gpui::Pixels = px(40.); /// Left inset for the header beside the rail: the traffic lights reach -/// 30 px past it when the bar is transparent. +/// 30 px past it, followed by an 8 px gap when the bar is transparent. const SIDEBAR_COLLAPSED_INSET: gpui::Pixels = if titlebar::TRANSPARENT_TITLEBAR { - px(30.) + px(38.) } else { px(16.) };