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.
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:
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()
}