Summary
The context popup measures its anchor live but nothing re-measures it when the selection changes, so a popup stays where it opened while the selection it acts on grows, shrinks, or moves under it. Scrolling still moves it, because scroll is what triggers a recompute.
Steps to reproduce
- Select a word with the pointer so the popup opens below it.
- Extend the selection to the lines below with
Shift+Down, or sideways with Shift+Right.
- For contrast, scroll the document surface with the popup open.
Expected behavior
The popup follows the selection it acts on, staying against the visible part of it as the selection changes.
Must remain stable: a popup holding focus stays pinned where it was, a popup whose selection scrolls out of view is hidden and returns, and the dismissal rules are unaffected.
Actual behavior
Steps 1 and 2 leave the popup where it opened, pointing at text the selection has moved away from. Step 3 moves it correctly.
Related context
Done when
Notes, logs, screenshots
Diagnosis
PopperAnchor registers the reference only when the object inside virtualRef changes identity — its effect compares previousAnchor !== anchorRef.current — and EditorContextPopup.tsx creates that object once, deliberately, so registration happens once. From then on floating-ui's autoUpdate recomputes only on ancestor scroll and resize, and a selection change is neither.
The plugin already issues a fresh request on every selection change and the anchor's getRect is live, so the measurement is current; nothing asks for it.
Implementation direction
Two routes the installed Radix supports:
- Give the virtual anchor a new object identity whenever the request changes. One recompute per selection change, nothing while idle, and it keeps the no-per-frame-work property the virtual anchor was chosen for. The existing "created once" constraint guards against a new object per render, which is not the same thing.
- Set
updatePositionStrategy="always" on PopoverContent, which passes animationFrame: true to autoUpdate. One line, at the cost of re-measuring the selection every frame while the popup is open.
Either way the focus-holding pin must keep winning, so a popup the user is working in does not move.
Out of scope
Summary
The context popup measures its anchor live but nothing re-measures it when the selection changes, so a popup stays where it opened while the selection it acts on grows, shrinks, or moves under it. Scrolling still moves it, because scroll is what triggers a recompute.
Steps to reproduce
Shift+Down, or sideways withShift+Right.Expected behavior
The popup follows the selection it acts on, staying against the visible part of it as the selection changes.
Must remain stable: a popup holding focus stays pinned where it was, a popup whose selection scrolls out of view is hidden and returns, and the dismissal rules are unaffected.
Actual behavior
Steps 1 and 2 leave the popup where it opened, pointing at text the selection has moved away from. Step 3 moves it correctly.
Related context
specification.mdstates the scroll-following rule but says nothing about the popup following its selection, which is the line this issue would add.Done when
docs/specification.md— the popup follows the selection it acts on, not only the scroll.Notes, logs, screenshots
Diagnosis
PopperAnchorregisters the reference only when the object insidevirtualRefchanges identity — its effect comparespreviousAnchor !== anchorRef.current— andEditorContextPopup.tsxcreates that object once, deliberately, so registration happens once. From then on floating-ui'sautoUpdaterecomputes only on ancestor scroll and resize, and a selection change is neither.The plugin already issues a fresh request on every selection change and the anchor's
getRectis live, so the measurement is current; nothing asks for it.Implementation direction
Two routes the installed Radix supports:
updatePositionStrategy="always"onPopoverContent, which passesanimationFrame: truetoautoUpdate. One line, at the cost of re-measuring the selection every frame while the popup is open.Either way the focus-holding pin must keep winning, so a popup the user is working in does not move.
Out of scope