Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,31 @@ function GlobeSectionInner({ countryData, totalUsers, activeUsersByCountry, sate
};
}, [globeReady, mounted, liveAvatars]);

// When the pointer leaves the globe canvas and the button is released
// outside, OrbitControls may miss the pointerup (pointer capture can be
// lost intermittently). Listen on window and forward the event to the
// canvas so the controls properly end the drag.
useEffect(() => {
if (!globeReady) return;
const domElement = globeRef.current?.renderer().domElement;
if (!domElement) return;

const handleWindowPointerUp = (e: PointerEvent) => {
if (e.target !== domElement && !domElement.contains(e.target as Node)) {
domElement.dispatchEvent(new PointerEvent('pointerup', {
pointerId: e.pointerId,
pointerType: e.pointerType,
bubbles: true,
}));
Comment on lines +1038 to +1042
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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.

}
};

window.addEventListener('pointerup', handleWindowPointerUp);
return () => {
window.removeEventListener('pointerup', handleWindowPointerUp);
};
}, [globeReady]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 714f60d. Configure here.


// set globeReady to true after a bit in case onGlobeReady was not called
useEffect(() => {
const timeout = setTimeout(() => {
Expand Down
Loading