Defer a map click to fix multi-tap gestures (task 228) #161
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/task-228-defer-map-click-to-add"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes Vikunja task 228 (#71). A single tap or click on the map opened the add-place panel immediately, so the first tap of any multi-tap gesture swallowed the second before it could arrive — task 227's own touch drag-zoom and one-step double-tap zoom, and desktop's own
doubleClickZoom(click,click,dblclick), were all unreachable for the same reason.Both map-click callbacks (
OnMapClickedand the picker'sOnPick) are now deferred by the double-tap window and cancelled by a second click, adblclick, a drag/pinch (movestart/zoomstart), or the qualifying second touchstart of task 227's own gesture taking the click over directly.Design record: ADR-0144, partially superseding ADR-0105 and ADR-0143 (metadata only — both records' frozen bodies are untouched).
Verification, with trusted input, not reasoning alone —
MapClickDeferralJourneyTests(new): a single deliberate tap still opens the add-place panel; a genuine double-tap (two real taps) does not, and zooms instead; a genuine mouse double-click has the identical outcome. The pre-fix version of this test file was pushed on its own first and confirmedfailurein CI before the fix went in (run #674, 5m16s) — Forgejo's own run-status API then reported that finished run asrunningwith a nonsense negative duration for over an hour before correcting itself; the fix's own run (#676) hit the identical stale-status symptom before settling atsuccess(5m28s), both recorded in ADR-0144 as one small lesson in trusting a terminal result over a status string.What remains reasoned, not reproduced, named plainly in ADR-0144: the held-and-dragged second tap's own cancellation path (inside
DoubleTapDragZoom._onTouchStart) is reached only by a real DOMtouchstart, which this environment's CDP-driven trusted input does not reliably dispatch to a page's own listener (ADR-0143's own established finding, not re-tested here).CI: run #676 —
success, 5m28s,e2eincluded.0f71b45e1501f5f7d1ceWIP: defer map click to fix task 228 (do not review yet)to Defer a map click to fix multi-tap gestures (task 228)Verdict: mergeable
Verified independently, not taken on trust:
f3c53d8, "Add trusted-input E2E tests for map click suppression (WIP, expected red)") is a real, test-only commit on this branch —git diff 239f1e0 f3c53d8touches only the new test file, no fix code. Itse2ejob genuinely failed on the new tests, for the claimed reason:DoubleTouchTap_...failed with a Playwright strict-mode violation becausediv.leaflet-map-hostresolved to two elements — the home map plus the picker map insidePlaceFormPanel, which had already opened after the first tap, exactly the pre-fix defect.MouseDoubleClick_...failed because the zoom-out button stayedleaflet-disabled— the double-click never reacheddoubleClickZoom. Both are the right reason, not an unrelated flake. Run #676 (01f5f7d, the PR head) issuccesswithe2eincluded, and01f5f7dis confirmed as this PR's actual head SHA.deferSingleClick/cancelPendingClickcorrectly key state per-mapinstance, so Home's map and the picker map never interfere with each other.dispose()against the vendored bundle:movestart/zoomstartgenuinely coversetView/fitBounds(task 216, ADR-0101) via Leaflet's own_moveStart, andDoubleTapDragZoom._onTouchStartis the one case they can't reach, handled by the direct call.LeafletMap.DisposeAsynccalls JSdispose()(which synchronously clears both timeouts) before disposing theDotNetObjectReferences, so no stale-callback path into a torn-down component.pendingKeyclears itself once the timer fires, so a click landing after that is treated as an independent new single click, not swallowed.docs/adr/README.mddiff is exactly the two status-column edits plus the one added row. No AI attribution in the branch's commits. British spellings throughout (cancelled,recognised,colour, etc.).Two non-blocking findings from tracing the cancellation list further than the ADR does:
map.js:99— the "second click while one is pending" cancellation is purely temporal, with no positional check (unlikeDoubleTapDragZoom's ownDOUBLE_TAP_SLOP_PIXELS). Two genuine, deliberate single clicks at different points, both inside the 300ms window, are both silently discarded rather than either one winning. ADR-0144's own reasoning ("two clicks close together are one gesture") doesn't address position at all. Given human click timing this is unlikely to bite in practice, but it's worth naming in the ADR alongside the other alternatives considered, since the record already goes to the trouble of enumerating what does and doesn't get treated as "the same gesture."map.js:607— a marker's owndragendisn't among the events that cancel a pending click on the same map. On the picker map specifically: click an empty spot (defersreportPick), then within the 300ms window drag the existing marker to reposition it (dragendfiresreportPickimmediately) — the deferred callback from the first click still fires afterwards and overwrites the just-completed drag with the stale coordinate. Narrow (needs an existing marker elsewhere on the map and a fast click-then-grab), non-destructive (nothing is persisted until the form is submitted, and the pin is still draggable afterwards), but it's a case "the cancellation list is complete" doesn't quite cover.Neither is a regression versus the shipped defect this PR fixes, and neither is severe enough to block.
A marker's own
dragenddoesn't cancel a pending click on the same map. On the picker: click empty space (defersreportPick), then within 300ms drag the existing marker elsewhere (dragendfires immediately) — the stale deferred callback from the first click still fires afterwards and overwrites the just-completed drag. Narrow and non-destructive (nothing persists until submit), but worth a mention alongside the doc comment's "has no ambiguity" claim above, which is true for the drag itself but not for a click still pending from before it.Purely temporal — no positional check like
DoubleTapDragZoom's ownDOUBLE_TAP_SLOP_PIXELS. Two deliberate single clicks at different points, both within 300ms, are both discarded rather than either succeeding. Worth naming in ADR-0144 alongside its other enumerated cases; not a blocker.