Skip to content

Commit 57f01f2

Browse files
advait-moz-agent
andcommitted
Hide orchestration hover card as soon as cursor leaves the pill
Exiting the hover card in certain ways (cursor leaving the app window, focus changes, layout drops mid-hover, the cursor passing through a stale Hoverable that suppresses the synthetic hover-out) could leave `hovered_pill` stuck on a stale id and the card visible until something else triggered a re-render. The pill's `on_hover(false)` callback didn't fire in those cases, so the `SetHoveredPill(None)` action was never dispatched. Add a defensive render-time check: only render the hover card if the underlying `MouseState::is_mouse_over_element` for the hovered pill still reports the cursor is over it. This makes the overlay strictly track the cursor — as soon as the pointer moves off the pill, the next render hides the card, regardless of whether the typed-action callback fired. Trade-off: the existing 80ms hover-out smoothing (intended to bridge the 6px gap between pill and card) becomes a no-op at the render layer because `is_mouse_over_element` is not delayed. The card was never meant to be interactive (its docstring already notes the card disappears as soon as the pointer leaves the pill body), so losing the smoothing is acceptable in exchange for guaranteed dismissal. Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent 69b6343 commit 57f01f2

1 file changed

Lines changed: 22 additions & 0 deletions

File tree

app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,10 +851,32 @@ impl View for OrchestrationPillBar {
851851
// the hover details card under that pill instead. The two overlays
852852
// are mutually exclusive by design: opening the menu clears
853853
// `hovered_pill` (see `open_menu_for`).
854+
//
855+
// Defensive: only render the hover card if the cursor is still
856+
// genuinely over the pill. The `SetHoveredPill(None)` action that
857+
// the pill's `on_hover` callback dispatches when the cursor leaves
858+
// can be missed in edge cases (window-focus changes, layout drops
859+
// mid-hover, the cursor exiting the app entirely, or a re-entry
860+
// into a stale Hoverable that suppresses the synthetic
861+
// hover-out), leaving `hovered_pill` stuck on a stale id and the
862+
// card visible until something else triggers a re-render. Reading
863+
// `MouseState::is_mouse_over_element` directly at render time
864+
// makes the overlay strictly track the cursor: as soon as the
865+
// pointer moves off the pill, the next render hides the card,
866+
// regardless of whether the typed-action callback fired.
854867
let overlay = if let Some(target_id) = self.menu_open_for {
855868
Some(MenuOrCard::Menu(target_id))
856869
} else {
857870
self.hovered_pill.and_then(|id| {
871+
let mouse_states = self.mouse_states.borrow();
872+
let still_over_pill = mouse_states
873+
.get(&id)
874+
.and_then(|handle| handle.lock().ok().map(|s| s.is_mouse_over_element()))
875+
.unwrap_or(false);
876+
drop(mouse_states);
877+
if !still_over_pill {
878+
return None;
879+
}
858880
render_hover_card(id, self.agent_view_controller.as_ref(app), app)
859881
.map(|card| MenuOrCard::Card { id, card })
860882
})

0 commit comments

Comments
 (0)