Fix code lens tooltip jitter - #10591
Conversation
CodeMirror measured the popover before React rendered its content. In an unfocused editor, the empty measurement placed the popover over the icon. The completed render then moved it and triggered extra pointer events. Render the popover content before CodeMirror measures it. This keeps the initial placement stable and prevents the hover loop.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
All contributors have signed the CLA ✍️ ✅ |
There was a problem hiding this comment.
Pull request overview
Fixes a UI hover-loop/jitter issue in the CodeLens popover by ensuring the popover content is rendered before CodeMirror performs its initial tooltip measurement, keeping the initial placement stable.
Changes:
- Wrap initial
root.render(...)influshSync(...)so the tooltip is fully rendered before CodeMirror measures/positions it. - Add a unit test asserting the popover content is present immediately after mounting (to match CodeMirror’s measurement timing).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| frontend/src/core/codemirror/code-lens/popover.tsx | Forces synchronous initial render of the popover to stabilize CodeMirror tooltip measurement/positioning. |
| frontend/src/core/codemirror/code-lens/tests/popover.test.ts | Adds a regression test for synchronous initial popover content rendering. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| it("renders content synchronously for CodeMirror's initial measurement", () => { | ||
| const dom = document.createElement("div"); | ||
|
|
||
| const dispose = mountLensPopover(dom, CACHE_SPEC); | ||
|
|
||
| expect(dom.textContent).toContain("my_cache"); | ||
| dispose(); | ||
| }); |
Coverage Report for ./frontend
File Coverage
|
||||||||||||||||||||||||||||||||||||||
`act()` flushes pending React renders. Wrapping the test mount in `act()` would hide a regression that removes the required `flushSync`. Keep the mount unwrapped and run only the deferred cleanup inside `act()`. This prevents unmount warnings without weakening the assertion.
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Architecture diagram
sequenceDiagram
participant CM as CodeMirror Editor
participant Lens as CodeLens Plugin
participant Popover as mountLensPopover()
participant React as React Root
participant DOM as Tooltip DOM Element
Note over CM,DOM: Code Lens Popover Positioning Flow
CM->>Lens: User hovers over code lens icon
Lens->>Popover: Create tooltip DOM element
Popover->>React: createRoot(dom)
Popover->>React: flushSync(render(<LensPopover />))
Note over React: Synchronous render completes<br/>before function returns
React->>DOM: Render popover content
React-->>Popover: Render complete
Popover-->>Lens: Return dispose function
Lens->>CM: Provide tooltip position callback
CM->>DOM: Measure popover dimensions
CM->>CM: Calculate stable position
CM->>DOM: Position tooltip at calculated location
Note over CM,DOM: Key difference: Content is rendered<br/>before measurement, preventing<br/>empty container sizing
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
CodeMirror measured the popover before React rendered its content. In an unfocused editor, the empty measurement placed the popover over the icon. The completed render then moved it and triggered extra pointer events.
Render the popover content before CodeMirror measures it. This keeps the initial placement stable and prevents the hover loop.