agent_ui: Add some general UI fixes (#53696)

Danilo Leal created

A note-worthy thing I'm adding in this PR to fix an agent panel problem
is a trait method allowing to set a min-width in panels. I think some of
them—if not all—could benefit from it, because there is a certain width
these panels can get in that we just can't ensure they will work; things
will break. So we either accept that and let the user's common sense
understand that, or we don't allow them to reach that state... Surely, a
small enough _window_ size can still break things, but then it's out of
the realms of solution...

Release Notes:

- Agent: Fixed multi-line queued messages getting cut-off when the agent
panel is in full screen.
- Agent: Fixed agent panel getting auto-closed after submitting a queued
message when the panel is in full screen.
- Agent: Added a min-width to the agent panel, preventing it from
reaching a small enough width where it would be essentially unusable.

Change summary

crates/agent_ui/src/agent_panel.rs                   |  8 ++++++++
crates/agent_ui/src/conversation_view/thread_view.rs | 11 ++++++++---
crates/workspace/src/dock.rs                         |  8 ++++++++
crates/workspace/src/workspace.rs                    |  4 ++++
4 files changed, 28 insertions(+), 3 deletions(-)

Detailed changes

crates/agent_ui/src/agent_panel.rs 🔗

@@ -90,6 +90,7 @@ use zed_actions::{
 };
 
 const AGENT_PANEL_KEY: &str = "agent_panel";
+const MIN_PANEL_WIDTH: Pixels = px(300.);
 const RECENTLY_UPDATED_MENU_LIMIT: usize = 6;
 const LAST_USED_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
 
@@ -3546,6 +3547,13 @@ impl Panel for AgentPanel {
         }
     }
 
+    fn min_size(&self, window: &Window, cx: &App) -> Option<Pixels> {
+        match self.position(window, cx) {
+            DockPosition::Left | DockPosition::Right => Some(MIN_PANEL_WIDTH),
+            DockPosition::Bottom => None,
+        }
+    }
+
     fn supports_flexible_size(&self) -> bool {
         true
     }

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

@@ -1510,6 +1510,9 @@ impl ThreadView {
         let Some(queued) = self.remove_from_queue(index, cx) else {
             return;
         };
+
+        self.message_editor.focus_handle(cx).focus(window, cx);
+
         let content = queued.content;
         let tracked_buffers = queued.tracked_buffers;
 
@@ -2257,12 +2260,14 @@ impl ThreadView {
 
         let max_content_width = AgentSettings::get_global(cx).max_content_width;
 
-        div()
+        h_flex()
             .w_full()
-            .max_w(max_content_width)
-            .mx_auto()
+            .justify_center()
             .child(
                 v_flex()
+                    .flex_basis(max_content_width)
+                    .flex_shrink()
+                    .flex_grow_0()
                     .mx_2()
                     .bg(self.activity_bar_bg(cx))
                     .border_1()

crates/workspace/src/dock.rs 🔗

@@ -39,6 +39,9 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
     fn position_is_valid(&self, position: DockPosition) -> bool;
     fn set_position(&mut self, position: DockPosition, window: &mut Window, cx: &mut Context<Self>);
     fn default_size(&self, window: &Window, cx: &App) -> Pixels;
+    fn min_size(&self, _window: &Window, _cx: &App) -> Option<Pixels> {
+        None
+    }
     fn initial_size_state(&self, _window: &Window, _cx: &App) -> PanelSizeState {
         PanelSizeState::default()
     }
@@ -98,6 +101,7 @@ pub trait PanelHandle: Send + Sync {
     fn remote_id(&self) -> Option<proto::PanelId>;
     fn pane(&self, cx: &App) -> Option<Entity<Pane>>;
     fn default_size(&self, window: &Window, cx: &App) -> Pixels;
+    fn min_size(&self, window: &Window, cx: &App) -> Option<Pixels>;
     fn initial_size_state(&self, window: &Window, cx: &App) -> PanelSizeState;
     fn size_state_changed(&self, window: &mut Window, cx: &mut App);
     fn supports_flexible_size(&self, cx: &App) -> bool;
@@ -181,6 +185,10 @@ where
         self.read(cx).default_size(window, cx)
     }
 
+    fn min_size(&self, window: &Window, cx: &App) -> Option<Pixels> {
+        self.read(cx).min_size(window, cx)
+    }
+
     fn initial_size_state(&self, window: &Window, cx: &App) -> PanelSizeState {
         self.read(cx).initial_size_state(window, cx)
     }

crates/workspace/src/workspace.rs 🔗

@@ -7457,6 +7457,7 @@ impl Workspace {
         let dock = dock.read(cx);
         if let Some(panel) = dock.visible_panel() {
             let size_state = dock.stored_panel_size_state(panel.as_ref());
+            let min_size = panel.min_size(window, cx);
             if position.axis() == Axis::Horizontal {
                 let use_flexible = panel.has_flexible_size(window, cx);
                 let flex_grow = if use_flexible {
@@ -7478,6 +7479,9 @@ impl Workspace {
                         .unwrap_or_else(|| panel.default_size(window, cx));
                     container = container.w(size);
                 }
+                if let Some(min) = min_size {
+                    container = container.min_w(min);
+                }
             } else {
                 let size = size_state
                     .and_then(|state| state.size)