From 132a5c2474f002c6f5d7ee21940514cc012c6771 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Fri, 29 May 2026 15:47:43 +0530 Subject: [PATCH 1/2] show perm popover even when perm dailog is hidden above viewport --- crates/agent_ui/src/conversation_view.rs | 58 ++++++++++++++++++- .../src/conversation_view/thread_view.rs | 19 +++--- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs index 568b94c1240b01..eb1759efd8f6ae 100644 --- a/crates/agent_ui/src/conversation_view.rs +++ b/crates/agent_ui/src/conversation_view.rs @@ -8113,9 +8113,15 @@ pub(crate) mod tests { async fn test_permission_row_hidden_when_inline_bounds_unavailable(cx: &mut TestAppContext) { init_test(cx); - let (_view, thread_view, _entry_ix, cx) = + let (_view, thread_view, entry_ix, cx) = setup_pending_permission_thread("perm-no-bounds", cx).await; + thread_view.read_with(cx, |view, _cx| { + view.list_state.scroll_to(ListOffset { + item_ix: entry_ix, + offset_in_item: px(0.0), + }); + }); thread_view.update_in(cx, |view, window, cx| { assert!( view.render_main_agent_awaiting_permission(window, cx) @@ -8176,8 +8182,8 @@ pub(crate) mod tests { let (_view, thread_view, entry_ix, cx) = setup_pending_permission_thread("perm-scroll", cx).await; - // Start off-screen below the viewport — row visible because the item - // has bounds that do not intersect the viewport. + // Start off-screen below the viewport. The row is visible because the + // item has bounds that do not intersect the viewport. draw_thread_list_at( &thread_view, ListOffset { @@ -8221,6 +8227,52 @@ pub(crate) mod tests { }); } + #[gpui::test] + async fn test_permission_row_shown_when_inline_prompt_is_above_viewport( + cx: &mut TestAppContext, + ) { + init_test(cx); + + let (_view, thread_view, entry_ix, cx) = + setup_pending_permission_thread("perm-above", cx).await; + + let thread = thread_view.read_with(cx, |view, _cx| view.thread.clone()); + thread.update(cx, |thread, cx| { + let result = thread.handle_session_update( + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new( + "More content".into(), + )), + cx, + ); + assert!( + result.is_ok(), + "following assistant message should be accepted" + ); + }); + + draw_thread_list_at( + &thread_view, + ListOffset { + item_ix: entry_ix + 1, + offset_in_item: px(0.0), + }, + cx, + ); + thread_view.read_with(cx, |view, _cx| { + assert!( + entry_ix < view.list_state.logical_scroll_top().item_ix, + "The tool call entry should be above the logical scroll top" + ); + }); + thread_view.update_in(cx, |view, window, cx| { + assert!( + view.render_main_agent_awaiting_permission(window, cx) + .is_some(), + "Floating row should be visible when the inline prompt is above the viewport" + ); + }); + } + #[gpui::test] async fn test_permission_row_disappears_when_authorized(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index a90fef49bd9915..32969d76d3de45 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -3047,15 +3047,6 @@ impl ThreadView { ) } - /// Returns true when the entry has been measured and sits entirely below - /// the current viewport. - fn entry_is_below_viewport(&self, entry_ix: usize) -> bool { - let viewport_bounds = self.list_state.viewport_bounds(); - self.list_state - .bounds_for_item(entry_ix) - .is_some_and(|entry_bounds| entry_bounds.top() >= viewport_bounds.bottom()) - } - pub(crate) fn render_main_agent_awaiting_permission( &self, window: &Window, @@ -3073,9 +3064,13 @@ impl ThreadView { let thread = self.thread.read(cx); let (entry_ix, tool_call) = thread.tool_call(&tool_call_id)?; - if !self.entry_is_below_viewport(entry_ix) { + let scroll_icon = if self.list_state.item_is_above_viewport(entry_ix)? { + IconName::ArrowUp + } else if self.list_state.item_is_below_viewport(entry_ix)? { + IconName::ArrowDown + } else { return None; - } + }; let focus_handle = self.focus_handle(cx); @@ -3118,7 +3113,7 @@ impl ThreadView { Button::new("main-agent-permission-scroll-to", "Scroll") .label_size(LabelSize::Small) .end_icon( - Icon::new(IconName::ArrowDown) + Icon::new(scroll_icon) .size(IconSize::XSmall) .color(Color::Default), ) From bed62293ee1c5a77452753cc6483db1f7376d004 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Fri, 29 May 2026 19:39:24 +0530 Subject: [PATCH 2/2] clean up --- crates/agent_ui/src/conversation_view.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs index eb1759efd8f6ae..c707fc9f624bd4 100644 --- a/crates/agent_ui/src/conversation_view.rs +++ b/crates/agent_ui/src/conversation_view.rs @@ -8116,6 +8116,8 @@ pub(crate) mod tests { let (_view, thread_view, entry_ix, cx) = setup_pending_permission_thread("perm-no-bounds", cx).await; + // Pin the scroll top to the entry so it isn't treated as above the + // viewport, forcing the unmeasured-bounds path we want to exercise. thread_view.read_with(cx, |view, _cx| { view.list_state.scroll_to(ListOffset { item_ix: entry_ix, @@ -8271,6 +8273,23 @@ pub(crate) mod tests { "Floating row should be visible when the inline prompt is above the viewport" ); }); + + // Scrolling up to the entry brings it back into view. + draw_thread_list_at( + &thread_view, + ListOffset { + item_ix: entry_ix, + offset_in_item: px(0.0), + }, + cx, + ); + thread_view.update_in(cx, |view, window, cx| { + assert!( + view.render_main_agent_awaiting_permission(window, cx) + .is_none(), + "Floating row should disappear after scrolling brings the inline prompt into view" + ); + }); } #[gpui::test]