Skip to content

fix: notification stays visible when mounted under cursor#4

Open
athulchandroth wants to merge 1 commit into
Zackriya-Solutions:mainfrom
athulchandroth:fix/auto-dismiss-stuck-on-mount
Open

fix: notification stays visible when mounted under cursor#4
athulchandroth wants to merge 1 commit into
Zackriya-Solutions:mainfrom
athulchandroth:fix/auto-dismiss-stuck-on-mount

Conversation

@athulchandroth
Copy link
Copy Markdown

Summary

  • Notifications that mount with the cursor already over them (very common with overlay notifications) never auto-dismiss
  • Root cause: mouseenter doesn't fire when an element appears under an already-present cursor, but the CSS :hover rule pauses the countdown animation immediately. JS hover state and CSS hover state desync, leaving the notification stuck.
  • Fix: drop the CSS :hover pause and replace mouseenter/mouseleave with global mousemove tracking + bounding-rect check.

Repro (before this PR)

  1. Have your cursor anywhere in the area where notifications appear (top-right by default).
  2. Send a notification: syncfu send -t "Test" "should auto-close in 8s".
  3. Don't move the mouse.
  4. Notification stays on screen indefinitely.

The same happens with the last remaining notification in a stack — once siblings dismiss and the bottom card slides into the cursor area, it gets stuck.

Root cause

Two independent hover-pause mechanisms that don't sync:

  1. CSS :hover pseudo-class in overlay.css paused the countdown animation via animation-play-state: paused. This activates on mount the instant the cursor is inside the element's box.
  2. JS mouseenter handler in NotificationCard.tsx set hovering.current = true and cleared the dismiss timer. But mouseenter only fires when the cursor moves into an element — if the element appears under an already-present cursor, no event fires.

Combined effect: CSS pauses the bar visually, JS may or may not pause the dismiss timer (depending on whether mouseenter fires under the Tauri NSPanel), and once stuck there's no event to recover from.

Fix

  • src/styles/overlay.css — remove :hover rule that paused animation-play-state on the countdown bar. JS now owns hover-pause exclusively.
  • src/components/overlay/NotificationCard.tsx — replace mouseenter/mouseleave ref tracking with a global mousemove listener that records cursor position. The dismiss tick checks the cursor against the card's bounding rect via getBoundingClientRect(). Initial cursor is (-1, -1) (treated as "not hovered"), so a card under a stationary cursor still dismisses on schedule.

Test plan

  • All 13 existing NotificationCard.test.tsx tests pass
  • Type-check passes (pnpm tsc --noEmit)
  • Manual: cursor parked over the notification region, send a notification — auto-dismisses in autoDismissMs
  • Manual: cursor moved onto the notification mid-countdown — countdown pauses ✅
  • Manual: cursor moved off — dismisses within ~200ms (poll interval) ✅
  • Manual: stack of 3 notifications, cursor parked at top — all three dismiss in sequence, none get stuck ✅

Notes

  • The visual countdown bar will keep shrinking while you hover (since the CSS animation no longer pauses). The dismiss timer is correctly paused, so the notification won't disappear — but the bar reaching zero is now decorative rather than load-bearing. If preserving the visual pause is important, JS could toggle a .paused class on hover and the CSS could pause via that class instead of :hover.

Auto-dismiss relied on `mouseenter`/`mouseleave` to track hover state and
on a CSS `:hover` rule to pause the countdown bar. When a card mounts
under the cursor (common with overlay notifications):

- CSS `:hover` activates immediately and pauses the countdown animation,
  but `mouseenter` does NOT fire (the cursor was already inside, never
  "entered"). The bar stays paused indefinitely.
- The JS `hovering` ref also stays false in some Tauri NSPanel cases,
  so the timer fires but visual state is stuck — or the ref incorrectly
  becomes true and the timer is cleared with no `mouseleave` to restart it.

Net effect: the first-ever notification in a stack (and often the last
remaining one) never auto-dismisses.

Fix: drop the CSS `:hover` pause and replace `mouseenter`/`mouseleave`
with global `mousemove` tracking + bounding-rect check. The dismiss tick
checks the actual cursor position against the card's rect, so a card
mounted under a stationary cursor still dismisses correctly once the
cursor moves away (or immediately if it never moved).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant