From d2bb6502bb071086f8558da3249f580d1398e310 Mon Sep 17 00:00:00 2001 From: Oleksandr Kholiavko <43780952+HalavicH@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:14:01 +0200 Subject: [PATCH] agent_ui: Adjust edited files and plan lists to fit content height (#55189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a plan contains many tasks (or the edited-files list is long), items were being visually compressed into the fixed-height container rather than scrolling. image_2026-04-28_20-17-43 Asking an agent to "create a plan with 20 tasks" is an easy repro. **Root cause** Both lists used a `v_flex()` as their scroll container, and flexbox children have `flex-shrink: 1` by default. When the container hits `max_h`, the flex algorithm compresses all children to fit instead of overflowing into the scroll region — resulting in 20 items crammed into 160px. **Fix** Separate the scroll boundary from the flex layout. A plain `div` (non-flex) as the outer scroll container lets its inner `v_flex` size naturally — content then overflows the bounded `div` and scrolling works correctly. Result: image_2026-04-29_10-09-24 Closes issue #54633. > What's missing: vertical scrollbar, as it requires large refactoring due to double mutable borrowing of `cx` (the second introduced by scroll handle), which will slowdown review of this pr Release Notes: - Fixed plan and edited-files lists in the agent panel being squashed when they contain many items --- .../src/conversation_view/thread_view.rs | 132 +++++++++--------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index ed42c576a3..44b51bb491 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -2295,11 +2295,9 @@ impl ThreadView { .id("edited_files_list") .max_h_40() .overflow_y_scroll() - .children( - sorted_buffers - .into_iter() - .enumerate() - .flat_map(|(index, (buffer, diff))| { + .child( + v_flex().children(sorted_buffers.into_iter().enumerate().flat_map( + |(index, (buffer, diff))| { let file = buffer.read(cx).file()?; let path = file.path(); let path_style = file.path_style(cx); @@ -2402,7 +2400,8 @@ impl ThreadView { .child(buttons); Some(element) - }), + }, + )), ) .into_any_element() } @@ -2786,66 +2785,69 @@ impl ThreadView { .id("plan_items_list") .max_h_40() .overflow_y_scroll() - .children(plan.entries.iter().enumerate().flat_map(|(index, entry)| { - let entry_bg = cx.theme().colors().editor_background; - let tooltip_text: SharedString = entry.content.read(cx).source().to_string().into(); + .child( + v_flex().children(plan.entries.iter().enumerate().flat_map(|(index, entry)| { + let entry_bg = cx.theme().colors().editor_background; + let tooltip_text: SharedString = + entry.content.read(cx).source().to_string().into(); - Some( - h_flex() - .id(("plan_entry_row", index)) - .py_1() - .px_2() - .gap_2() - .justify_between() - .relative() - .bg(entry_bg) - .when(index < plan.entries.len() - 1, |parent| { - parent.border_color(cx.theme().colors().border).border_b_1() - }) - .overflow_hidden() - .child( - h_flex() - .id(("plan_entry", index)) - .gap_1p5() - .min_w_0() - .text_xs() - .text_color(cx.theme().colors().text_muted) - .child(match entry.status { - acp::PlanEntryStatus::InProgress => { - Icon::new(IconName::TodoProgress) - .size(IconSize::Small) - .color(Color::Accent) - .with_rotate_animation(2) - .into_any_element() - } - acp::PlanEntryStatus::Completed => { - Icon::new(IconName::TodoComplete) - .size(IconSize::Small) - .color(Color::Success) - .into_any_element() - } - acp::PlanEntryStatus::Pending | _ => { - Icon::new(IconName::TodoPending) - .size(IconSize::Small) - .color(Color::Muted) - .into_any_element() - } - }) - .child(MarkdownElement::new( - entry.content.clone(), - plan_label_markdown_style(&entry.status, window, cx), - )), - ) - .child(div().absolute().top_0().right_0().h_full().w_8().bg( - linear_gradient( - 90., - linear_color_stop(entry_bg, 1.), - linear_color_stop(entry_bg.opacity(0.), 0.), - ), - )) - .tooltip(Tooltip::text(tooltip_text)), - ) - })) + Some( + h_flex() + .id(("plan_entry_row", index)) + .py_1() + .px_2() + .gap_2() + .justify_between() + .relative() + .bg(entry_bg) + .when(index < plan.entries.len() - 1, |parent| { + parent.border_color(cx.theme().colors().border).border_b_1() + }) + .overflow_hidden() + .child( + h_flex() + .id(("plan_entry", index)) + .gap_1p5() + .min_w_0() + .text_xs() + .text_color(cx.theme().colors().text_muted) + .child(match entry.status { + acp::PlanEntryStatus::InProgress => { + Icon::new(IconName::TodoProgress) + .size(IconSize::Small) + .color(Color::Accent) + .with_rotate_animation(2) + .into_any_element() + } + acp::PlanEntryStatus::Completed => { + Icon::new(IconName::TodoComplete) + .size(IconSize::Small) + .color(Color::Success) + .into_any_element() + } + acp::PlanEntryStatus::Pending | _ => { + Icon::new(IconName::TodoPending) + .size(IconSize::Small) + .color(Color::Muted) + .into_any_element() + } + }) + .child(MarkdownElement::new( + entry.content.clone(), + plan_label_markdown_style(&entry.status, window, cx), + )), + ) + .child(div().absolute().top_0().right_0().h_full().w_8().bg( + linear_gradient( + 90., + linear_color_stop(entry_bg, 1.), + linear_color_stop(entry_bg.opacity(0.), 0.), + ), + )) + .tooltip(Tooltip::text(tooltip_text)), + ) + })), + ) .into_any_element() }