agent_panel: Prevent content from overlapping with buttons in message editor (#53384)

Danilo Leal created

Follow up to https://github.com/zed-industries/zed/pull/53349 as I was
still seeing cases where content would overlap with buttons.

<img width="500" height="266" alt="Screenshot 2026-04-08 at 6  53@2x"
src="https://github.com/user-attachments/assets/e58742d4-a736-4509-9a29-35c135ddb980"
/>

I believe this PR makes the whole message editor more solid against
these cases because we're relying on `flex_basis` now to set the
max-width, which runs before we do any editor mode calculations. Tested
this a bunch and it seems to be pretty stable.

Release Notes:

- N/A

Change summary

crates/agent_ui/src/conversation_view/thread_view.rs | 102 +++++++------
crates/gpui/src/styled.rs                            |   7 
2 files changed, 59 insertions(+), 50 deletions(-)

Detailed changes

crates/agent_ui/src/conversation_view/thread_view.rs 🔗

@@ -1544,6 +1544,9 @@ impl ThreadView {
         _window: &mut Window,
         cx: &mut Context<Self>,
     ) {
+        if self.list_state.item_count() == 0 {
+            return;
+        }
         self.set_editor_is_expanded(!self.editor_expanded, cx);
         cx.stop_propagation();
         cx.notify();
@@ -3147,9 +3150,8 @@ impl ThreadView {
 
         let focus_handle = self.message_editor.focus_handle(cx);
         let editor_bg_color = cx.theme().colors().editor_background;
+
         let editor_expanded = self.editor_expanded;
-        let has_messages = self.list_state.item_count() > 0;
-        let v2_empty_state = !has_messages;
         let (expand_icon, expand_tooltip) = if editor_expanded {
             (IconName::Minimize, "Minimize Message Editor")
         } else {
@@ -3157,43 +3159,48 @@ impl ThreadView {
         };
 
         let max_content_width = AgentSettings::get_global(cx).max_content_width;
+        let has_messages = self.list_state.item_count() > 0;
+        let fills_container = !has_messages || editor_expanded;
 
-        v_flex()
-            .on_action(cx.listener(Self::expand_message_editor))
+        h_flex()
             .p_2()
-            .gap_2()
-            .when(!v2_empty_state, |this| {
-                this.border_t_1().border_color(cx.theme().colors().border)
-            })
             .bg(editor_bg_color)
-            .when(v2_empty_state, |this| this.flex_1().size_full())
-            .when(editor_expanded && !v2_empty_state, |this| {
-                this.h(vh(0.8, window)).size_full().justify_between()
+            .justify_center()
+            .map(|this| {
+                if has_messages {
+                    this.on_action(cx.listener(Self::expand_message_editor))
+                        .border_t_1()
+                        .border_color(cx.theme().colors().border)
+                        .when(editor_expanded, |this| this.h(vh(0.8, window)))
+                } else {
+                    this.flex_1().size_full()
+                }
             })
             .child(
                 v_flex()
-                    .flex_1()
-                    .min_h_0()
-                    .w_full()
-                    .max_w(max_content_width)
-                    .mx_auto()
+                    .flex_basis(max_content_width)
+                    .flex_shrink()
+                    .flex_grow_0()
+                    .when(fills_container, |this| this.h_full())
+                    .justify_between()
+                    .gap_2()
                     .child(
                         v_flex()
                             .relative()
+                            .w_full()
                             .min_h_0()
-                            .size_full()
-                            .when(v2_empty_state, |this| this.flex_1())
+                            .when(fills_container, |this| this.flex_1())
                             .pt_1()
                             .pr_2p5()
                             .child(self.message_editor.clone())
-                            .when(!v2_empty_state, |this| {
+                            .when(has_messages, |this| {
                                 this.child(
                                     h_flex()
                                         .absolute()
                                         .top_0()
                                         .right_0()
                                         .opacity(0.5)
-                                        .hover(|this| this.opacity(1.0))
+                                        .hover(|s| s.opacity(1.0))
                                         .child(
                                             IconButton::new("toggle-height", expand_icon)
                                                 .icon_size(IconSize::Small)
@@ -3218,39 +3225,34 @@ impl ThreadView {
                                         ),
                                 )
                             }),
-                    ),
-            )
-            .child(
-                h_flex()
-                    .w_full()
-                    .max_w(max_content_width)
-                    .mx_auto()
-                    .flex_none()
-                    .flex_wrap()
-                    .justify_between()
-                    .child(
-                        h_flex()
-                            .gap_0p5()
-                            .child(self.render_add_context_button(cx))
-                            .child(self.render_follow_toggle(cx))
-                            .children(self.render_fast_mode_control(cx))
-                            .children(self.render_thinking_control(cx)),
                     )
                     .child(
                         h_flex()
-                            .gap_1()
-                            .children(self.render_token_usage(cx))
-                            .children(self.profile_selector.clone())
-                            .map(|this| {
-                                // Either config_options_view OR (mode_selector + model_selector)
-                                match self.config_options_view.clone() {
-                                    Some(config_view) => this.child(config_view),
-                                    None => this
-                                        .children(self.mode_selector.clone())
-                                        .children(self.model_selector.clone()),
-                                }
-                            })
-                            .child(self.render_send_button(cx)),
+                            .w_full()
+                            .flex_none()
+                            .flex_wrap()
+                            .justify_between()
+                            .child(
+                                h_flex()
+                                    .gap_0p5()
+                                    .child(self.render_add_context_button(cx))
+                                    .child(self.render_follow_toggle(cx))
+                                    .children(self.render_fast_mode_control(cx))
+                                    .children(self.render_thinking_control(cx)),
+                            )
+                            .child(
+                                h_flex()
+                                    .gap_1()
+                                    .children(self.render_token_usage(cx))
+                                    .children(self.profile_selector.clone())
+                                    .map(|this| match self.config_options_view.clone() {
+                                        Some(config_view) => this.child(config_view),
+                                        None => this
+                                            .children(self.mode_selector.clone())
+                                            .children(self.model_selector.clone()),
+                                    })
+                                    .child(self.render_send_button(cx)),
+                            ),
                     ),
             )
             .into_any()

crates/gpui/src/styled.rs 🔗

@@ -208,6 +208,13 @@ pub trait Styled: Sized {
         self
     }
 
+    /// Sets the element to prevent a flex item from growing.
+    /// [Docs](https://tailwindcss.com/docs/flex-grow#dont-grow)
+    fn flex_grow_0(mut self) -> Self {
+        self.style().flex_grow = Some(0.);
+        self
+    }
+
     /// Sets the element to allow a flex item to shrink if needed.
     /// [Docs](https://tailwindcss.com/docs/flex-shrink)
     fn flex_shrink(mut self) -> Self {