Fix globe drag not ending when pointer released outside element#1447
Conversation
When dragging the globe to rotate it and releasing the mouse button outside the globe element, OrbitControls could miss the pointerup event (pointer capture can be lost intermittently). This caused the globe to continue rotating on re-entry even without the mouse button held. Fix: listen for pointerup on window and forward it to the canvas when the event originated outside the globe, so OrbitControls properly ends the drag. Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughA ChangesGlobe Pointer Release Forwarding
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR fixes the overview globe continuing to rotate after the mouse button is released outside the canvas element. It adds a
Confidence Score: 4/5Safe to merge; the change is well-scoped and its cleanup path is correct. The fix correctly addresses the drag-not-ending bug: the target check prevents duplicate dispatches when pointer capture is still active, Only Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Window
participant Handler as handleWindowPointerUp
participant Canvas as domElement (canvas)
participant OC as OrbitControls
User->>Window: pointerup (outside canvas)
Window->>Handler: fires listener
Handler->>Handler: "check e.target !== domElement?"
alt pointer released outside canvas
Handler->>Canvas: dispatchEvent(PointerEvent 'pointerup')
Canvas->>OC: onPointerUp fires
OC->>OC: removePointer / reset drag state
else pointer released on canvas (normal path)
Handler-->>Handler: no-op (target check fails)
end
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx:1038-1042
`bubbles: true` is not needed here and has a subtle downside. OrbitControls attaches its `pointerup` listener directly on `domElement` (not a parent), so bubbling isn't required for it to fire. With `bubbles: true`, the synthetic event propagates up through the entire DOM after OrbitControls handles it, triggering any intermediate `pointerup` listeners (e.g. React synthetic event delegation at `document`) and re-entering `handleWindowPointerUp` on `window` — where it's a no-op due to the target check, but still an unnecessary invocation. Using `bubbles: false` keeps the dispatch scoped to the canvas and avoids unexpected side-effects.
```suggestion
domElement.dispatchEvent(new PointerEvent('pointerup', {
pointerId: e.pointerId,
pointerType: e.pointerType,
bubbles: false,
}));
```
Reviews (1): Last reviewed commit: "Fix globe drag not ending when pointer r..." | Re-trigger Greptile |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 714f60d. Configure here.
| return () => { | ||
| window.removeEventListener('pointerup', handleWindowPointerUp); | ||
| }; | ||
| }, [globeReady]); |
There was a problem hiding this comment.
Stale canvas in pointerup handler
Medium Severity
The window pointerup listener keeps the renderer canvas from when the effect first ran. globeReady stays true when Globe remounts (theme or error refresh), so synthetic pointerup events go to a detached canvas and the live controls may still miss an outside release.
Reviewed by Cursor Bugbot for commit 714f60d. Configure here.
| domElement.dispatchEvent(new PointerEvent('pointerup', { | ||
| pointerId: e.pointerId, | ||
| pointerType: e.pointerType, | ||
| bubbles: true, | ||
| })); |
There was a problem hiding this comment.
bubbles: true is not needed here and has a subtle downside. OrbitControls attaches its pointerup listener directly on domElement (not a parent), so bubbling isn't required for it to fire. With bubbles: true, the synthetic event propagates up through the entire DOM after OrbitControls handles it, triggering any intermediate pointerup listeners (e.g. React synthetic event delegation at document) and re-entering handleWindowPointerUp on window — where it's a no-op due to the target check, but still an unnecessary invocation. Using bubbles: false keeps the dispatch scoped to the canvas and avoids unexpected side-effects.
| domElement.dispatchEvent(new PointerEvent('pointerup', { | |
| pointerId: e.pointerId, | |
| pointerType: e.pointerType, | |
| bubbles: true, | |
| })); | |
| domElement.dispatchEvent(new PointerEvent('pointerup', { | |
| pointerId: e.pointerId, | |
| pointerType: e.pointerType, | |
| bubbles: false, | |
| })); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
Line: 1038-1042
Comment:
`bubbles: true` is not needed here and has a subtle downside. OrbitControls attaches its `pointerup` listener directly on `domElement` (not a parent), so bubbling isn't required for it to fire. With `bubbles: true`, the synthetic event propagates up through the entire DOM after OrbitControls handles it, triggering any intermediate `pointerup` listeners (e.g. React synthetic event delegation at `document`) and re-entering `handleWindowPointerUp` on `window` — where it's a no-op due to the target check, but still an unnecessary invocation. Using `bubbles: false` keeps the dispatch scoped to the canvas and avoids unexpected side-effects.
```suggestion
domElement.dispatchEvent(new PointerEvent('pointerup', {
pointerId: e.pointerId,
pointerType: e.pointerType,
bubbles: false,
}));
```
How can I resolve this? If you propose a fix, please make it concise.

Fixes the overview globe continuing to rotate when re-entering it after releasing the mouse button outside. Listens for
pointeruponwindowand forwards it to the canvas when it originated outside the globe element.Link to Devin session: https://app.devin.ai/sessions/a1b15005ef104bbe8d77b54ace75f66f
Requested by: @N2D4
Note
Low Risk
Low risk UI interaction fix that only adds a
window-levelpointeruplistener to ensure OrbitControls ends drags correctly; main concern is unintended event forwarding if the target detection is wrong.Overview
Fixes the overview globe getting “stuck” rotating after a drag when the pointer is released outside the globe canvas.
Adds a
useEffectthat listens forpointeruponwindowand, when the event originated outside the canvas, dispatches a syntheticpointerupto the globe renderer element so OrbitControls reliably terminates the drag state.Reviewed by Cursor Bugbot for commit 714f60d. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
Bug Fixes