Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix: canvas toolbar toggles getting stuck open on rapid hover#679

Merged
breaking-brake merged 1 commit intomainbreaking-brake/cc-wf-studio:mainfrom
fix/toolbar-toggle-stuck-openbreaking-brake/cc-wf-studio:fix/toolbar-toggle-stuck-openCopy head branch name to clipboard
Mar 23, 2026
Merged

fix: canvas toolbar toggles getting stuck open on rapid hover#679
breaking-brake merged 1 commit intomainbreaking-brake/cc-wf-studio:mainfrom
fix/toolbar-toggle-stuck-openbreaking-brake/cc-wf-studio:fix/toolbar-toggle-stuck-openCopy head branch name to clipboard

Conversation

@breaking-brake
Copy link
Copy Markdown
Owner

@breaking-brake breaking-brake commented Mar 23, 2026

Problem

Canvas toolbar toggles (ScrollMode, InteractionMode, EdgeAnimation, Highlight, Minimap) use a hover-expand UX where the toggle expands from a compact 28px icon to a full 100px control on hover. When rapidly moving the mouse in and out, the CSS width transition (200ms) causes the element boundary to shift under the cursor, preventing the browser from firing mouseleave. This leaves isHovered stuck as true, keeping the toggle permanently expanded.

Current Behavior

  1. Hover over a canvas toolbar toggle → it expands
  2. Rapidly move mouse in and out during the CSS transition
  3. ❌ Toggle stays expanded (stuck open) because mouseleave never fires

Expected Behavior

  1. Hover over a canvas toolbar toggle → it expands
  2. Move mouse away
  3. ✅ Toggle always collapses back to compact state

Solution

Introduced useStableHover hook that does not rely solely on mouseleave. Instead, it attaches a document-level pointermove listener while hovered and hit-tests the cursor against the element's current bounding rect. If the cursor is outside (with a 4px margin), it forces isHovered=false. It also listens for transitionend to catch the case where the cursor is stationary while the element shrinks away.

Changes

New file: src/webview/src/hooks/useStableHover.ts

  • Document-level pointermove listener (only active while hovered)
  • transitionend listener for stationary cursor during CSS transitions
  • rAF-throttled bounds checking to avoid layout thrash
  • Proper cleanup on unhover and unmount

Modified files: All 5 canvas toolbar toggles

  • ScrollModeToggle.tsx
  • InteractionModeToggle.tsx
  • EdgeAnimationToggle.tsx
  • HighlightToggle.tsx
  • MinimapToggle.tsx
  • Replaced useState(false) with useStableHover() hook
  • Added ref to hoverable div for bounds checking

Impact

  • Zero overhead when toggles are collapsed (no listeners registered)
  • No memory leak risk — all listeners cleaned up on unhover/unmount
  • No behavior change for normal hover interactions (mouseleave still works as fast path)

Testing

  • Manual E2E testing — rapid hover toggle no longer gets stuck
  • Build passes (npm run format && npm run lint && npm run build)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced improved hover detection for animated controls with enhanced boundary tracking during expand/collapse transitions.
  • Refactor

    • Unified hover state management across multiple toggle components for consistent user experience.

- Add useStableHover hook with document-level pointermove detection
- Detect cursor outside element bounds even when mouseleave fails
- Handle transitionend to catch stationary cursor during CSS transitions
- Apply to all 5 canvas toolbar toggles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1396617d-5eea-42dc-ad40-c4140da423f8

📥 Commits

Reviewing files that changed from the base of the PR and between e4318db and 18c733c.

📒 Files selected for processing (6)
  • src/webview/src/components/EdgeAnimationToggle.tsx
  • src/webview/src/components/HighlightToggle.tsx
  • src/webview/src/components/InteractionModeToggle.tsx
  • src/webview/src/components/MinimapToggle.tsx
  • src/webview/src/components/ScrollModeToggle.tsx
  • src/webview/src/hooks/useStableHover.ts

📝 Walkthrough

Walkthrough

Introduced a new useStableHover hook that provides robust hover state management for elements with animated expand/collapse transitions. Replaced local hover state in five toggle components (EdgeAnimationToggle, HighlightToggle, InteractionModeToggle, MinimapToggle, ScrollModeToggle) with this shared hook to centralize and improve hover tracking reliability.

Changes

Cohort / File(s) Summary
Toggle Components with Hover Hook Integration
src/webview/src/components/EdgeAnimationToggle.tsx, src/webview/src/components/HighlightToggle.tsx, src/webview/src/components/InteractionModeToggle.tsx, src/webview/src/components/MinimapToggle.tsx, src/webview/src/components/ScrollModeToggle.tsx
Replaced local useState hover state management with useStableHover hook. Each component now derives isHovered, ref, and event handlers from the hook, attaching ref to the root container and using hook-provided onMouseEnter/onMouseLeave handlers. Hover-driven logic (tooltips, rendering, click/keyboard gating) remains unchanged but sources state from hook.
New Stable Hover Hook
src/webview/src/hooks/useStableHover.ts
Added new hook providing robust hover state for animated elements. Exposes ref, isHovered boolean, and mouse event handlers. Uses document-level pointermove listener with requestAnimationFrame throttling to hit-test cursor coordinates against element bounds (4px margin). Registers transitionend listener for bounds re-checks during animations. Implements full cleanup of listeners and animation frames on unmount or hover end.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

released

Poem

🐰 A rabbit hops 'cross hovering states,
No longer lost when toggles animate,
With stable bounds and margins true,
The pointer dance finds what to do!
Animations blur, but hover stays clear—
Four pixels of grace, we hold it dear. ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/toolbar-toggle-stuck-open

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@breaking-brake breaking-brake merged commit 7b42494 into main Mar 23, 2026
2 of 4 checks passed
@breaking-brake breaking-brake deleted the fix/toolbar-toggle-stuck-open branch March 23, 2026 03:06
@breaking-brake breaking-brake mentioned this pull request Mar 23, 2026
braking-brake-semantic-release Bot pushed a commit that referenced this pull request Mar 23, 2026
## [3.30.3](v3.30.2...v3.30.3) (2026-03-23)

### Bug Fixes

* canvas toolbar toggles getting stuck open on rapid hover ([#679](#679)) ([7b42494](7b42494))

### Improvements

* add minimap 3-state display mode with scroll auto-show ([#677](#677)) ([fb26d73](fb26d73))
* add PulseMCP discovery link to MCP server selection ([#674](#674)) ([e6f2b6c](e6f2b6c))
* add SkillsMP discovery link to Skill Browser dialog ([#675](#675)) ([7638fdf](7638fdf))
* add sub-agent discovery link to Sub-Agent Creation dialog ([#676](#676)) ([3523da8](3523da8))
* increase max node limit to 100 with user-configurable setting ([#678](#678)) ([e4318db](e4318db))
@braking-brake-semantic-release
Copy link
Copy Markdown

🎉 This PR is included in version 3.30.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.