The previous slice tested its drag with synthetic touch events, and they passed. The owner’s phone disagreed. Sliders, install, and offline all worked, but picking up a note did nothing, and swiping down the pad grid previewed a note instead of scrolling.
What was wrong
A browser gives a touch to the element under the finger automatically. The drag code then claimed the pointer for the whole grid, and the browser reported that the pad had lost it. The code took that report as the end of the drag, so every touch drag ended on its first movement. A mouse never triggers that handoff, and neither did the synthetic events, which is why nobody had seen it. The builder reproduced the sequence event by event in a real Chromium touch session before changing anything.
The rule
The owner’s requirement was simple: a vertical swipe that starts on a lit pad must scroll the page. So touch and mouse now behave differently on purpose.
- Touch or pen. Press and hold a lit pad for about a quarter of a second and it lifts, with a short preview and a visible sink. Move before the hold completes and it is a swipe: the page scrolls and nothing plays or moves.
- Mouse. A drag lifts the pad at once, as before.
- After a drop, the stray click that browsers send after a long press is swallowed, so the pad does not toggle itself back.
Scrolling is blocked only once a pad is actually lifted, and only on the grid itself, because browsers decide at the start of a touch whether a scroll can be cancelled. A Playwright test with real touch input now guards the behaviour.
The owner merged it, deployed, and ran the three checks on their phone: hold and drag with one undo, swipe scrolls, tap selects and a second tap clears. All passed.