Where does the bug appear (feature/product)?
Cursor IDE
Describe the Bug
Summary
In a .canvas.tsx using the cursor/canvas SDK, each canvas mount leaves a persistent click listener behind that survives tab close. Every subsequent mount accumulates another listener, so clicking a single eventually opens N tabs with the same URL (N = number of times that canvas file has been mounted in the current window).
Steps to Reproduce
Canvas SDK imports used: Link, Card, CardHeader, CardBody, TextArea, Select, Button, IconButton, useCanvasState, useCanvasAction
- Ctrl+Shift+P → “Developer: Reload Window” to start with no ghost mounts
- Open a .canvas.tsx that contains at least one
<Link href="https://example.com/test">click</Link>inside any<Card> - Click the link → 1 tab opens (expected)
- Close the canvas tab (not the IDE)
- Reopen the canvas via the chat button or by opening the file
- Click the same link → 2 tabs open with the same URL
- Close + reopen again → click → 3 tabs
- Each cycle adds exactly one more tab. Linear accumulation.
- Only Developer: Reload Window resets the count.
The leak is worse (more ghosts per open, or triggers more reliably) when the canvas contains:
<TextArea>with controlled value + onChange — especially when rendered inside a repeating .map() loop (e.g. one per item in a list)<Select>— similar pattern- Multiple
useCanvasStatecalls — consolidating all state into a single useCanvasState with a compound object drops the leak rate back to ~1 per mount - Hot reloads while canvas is open — every file rewrite triggers a remount that also leaves a ghost behind, compounding with normal close/reopen leak
Expected Behavior
Clicking a link just opens 1 window/tab
Operating System
Windows 10/11
Version Information
3.1.15 IDE
Additional Information
Either the cursor/canvas React runtime is adding listeners at the webview/document level without a useEffect cleanup, or the Cursor webview host isn’t tearing down the React root on tab close (keeping the DOM alive with all listeners attached). The SDK’s <Link> component appears to use an explicit click handler (since clicks on an anchor in isolation shouldn’t fire N times via event bubbling). useCanvasState’s subscription appears to be the primary leak source — each hook instance adds to the persistent count.
Does this stop you from using Cursor
No - Cursor works, but with this issue