On a CSS-scaled dashboard panel uPlot reads the pointer in visual pixels
and measures it against its own unscaled plot width. Refining the result
in cursor.move left uPlot's earlier arithmetic wrong: cacheMouse snaps an
offset within 1px of the plot edge to the plot width, and on a panel
scaled up the raw visual offset passes that edge at 1/drawn of the way
across, so the readout stopped advancing partway over the chart and stuck
to the last point.
cursor.bind wraps the three events that carry a position and hands uPlot
a corrected clientX/clientY, so every step after it — the snap, the drag
detection, the redraw path — comes out right by construction. It is also
stateless, which removes the cursor.move memo and the drag.click
workaround that the mousedown/mouseup mismatch needed.
cursor.check.ts now replays uPlot 1.6.32's own pipeline against the
shipped cursor config, for panels scaled both down and up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZeGnqVsf5VHQqvz4HdUhN
uPlot writes what `cursor.move` returns back into the value it hands in next
time and calls it again on every redraw — and a chart sets its data on every
render. The correction was therefore applied repeatedly: the cursor walked
left while the pointer stood still, and the position taken at mousedown no
longer matched the one held at mouseup, which uPlot reads as a drag and
answers by swallowing the click. That is what stopped a chart tile being
selectable in the dashboard editor.
The refiner now returns its previous answer unchanged when handed it back, so
a redraw is a no-op. Lifted out of the config as `cursorRefiner` so the branch
can be checked without a browser; `cursor.check.ts` replays uPlot's own
sequences, including the press-is-not-a-drag one that regressed.
The Playwright hover test now also asserts the cursor holds its place across a
redraw, which is what the first version missed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZeGnqVsf5VHQqvz4HdUhN