iD Editor Zoom Permanently Stuck After Rapidly Double-Tapping to Close a Touch-Drawn Area
When a user closes a drawn area in the iD OpenStreetMap editor by rapidly double-tapping the final node using touch input on a Windows convertible device, the editor's internal pointer…
At a Glance
When a user closes a drawn area in the iD OpenStreetMap editor by rapidly double-tapping the final node using touch input on a Windows convertible device, the editor's internal pointer state becomes desynchronized.
Summary
When a user closes a drawn area in the iD OpenStreetMap editor by rapidly double-tapping the final node using touch input on a Windows convertible device, the editor's internal pointer state becomes desynchronized. The application behaves as though the primary pointer button remains held down, locking zoom into single-finger scroll-to-zoom mode. The broken state persists until the page is refreshed.
Root-Cause Analysis
Confirmed evidence:
- The failure is reproducible on a touch-enabled Windows convertible (BMAX Y13) running Windows 10 and Microsoft Edge 89.
- The symptom — zoom behaving as if the left mouse button remains pressed — indicates that iD's internal drag or button-state flag is never cleared after the double-tap sequence completes.
- Normal zoom and pan functionality returns only on full page reload, confirming the corruption is in ephemeral JavaScript runtime state, not persisted data.
Reasonable inference:
iD uses a combination of pointer events (pointerdown, pointermove, pointerup) and legacy mouse events (mousedown, mouseup) alongside D3's drag behavior to manage map interactions. When a touch-generated double-tap closes an active drawing, the event sequence compresses: a rapid succession of pointerdown → pointerup → pointerdown → pointerup (and the synthesized dblclick) arrives within a very short window. It is likely that:
- The double-tap completion triggers the area-close logic while a
pointerdownfrom the second tap is still considered "active" by D3's drag handler or iD's own mouse-state tracker. - The corresponding
pointerupfor the closing tap is either consumed by the area-close handler before the map's drag/zoom listener can process it, or the event'spreventDefault()call suppresses the synthetic mouse event that would normally reset the drag state. - As a result, the zoom/pan layer retains a
mousedown-equivalent state indefinitely — no matchingmouseupever resets it.
Alternative causes (cannot confirm without source inspection):
- A
pointerIdcapture (setPointerCapture) that is never released on the closing element. - A race condition in iD's
modestate machine where the drawing mode exits before the map behavior'sdragendhandler fires, orphaning the drag state. - Edge-specific touch-action or pointer-coalescing behavior that reorders or drops events compared to other browsers.
What additional evidence would confirm the diagnosis:
- Browser DevTools event listener breakpoints on
pointerupandmouseupduring a failing double-tap sequence, to confirm whether those events fire and whether iD's handlers process them. - Inspection of iD's internal
context._mouseor equivalent state object immediately after the failure. - Reproducing in Chrome or Firefox on the same hardware to isolate browser-specific behavior.
Resolution Steps
For end users experiencing the issue:
- If zoom breaks during a session, press F5 (or the browser's reload shortcut) to restore normal behavior. Unsaved edits will be lost unless already uploaded.
- As a temporary workaround, close the drawn area by single-tapping the endpoint to place the final node, then single-tapping the endpoint a second time with a deliberate pause between taps rather than using a rapid double-tap.
- Alternatively, use the keyboard shortcut to finish the area (typically Enter or Escape depending on context) instead of a touch double-tap.
For maintainers investigating the defect:
- Instrument iD's pointer/mouse event handlers to log the full event sequence —
pointerdown,pointerup,mousedown,mouseup,click,dblclick— captured during a rapid touch double-tap on the closing node. - Audit every code path that terminates drawing mode (
iD.modes.DrawArea,iD.behavior.Draw, or equivalents in the current codebase) and verify that each path explicitly resets any D3 drag state and releases pointer capture before exiting. - Add a defensive reset of the map's drag/zoom button state whenever drawing mode exits, regardless of which event triggered the exit. This prevents orphaned state if an event is consumed before the map layer sees it.
- Evaluate adding a
pointercancelhandler to the drawing behavior so that ambiguous pointer sequences always produce a clean state reset. - Test the fix against Edge and Chrome on a Windows touch device to cover both pointer-event stacks.
CLI Commands
Not directly applicable to this browser-based JavaScript issue. Developers cloning and building iD locally can set up the development environment as follows:
git clone https://github.com/openstreetmap/iD.git
cd iD
npm install
npm run all # build and run tests
npm start # start local dev serverTo enable verbose event logging during debugging in DevTools:
# Open Edge DevTools console and monitor pointer events on the map container
# Paste into the DevTools console after page load:
(function () {
const target = document.querySelector('.main-map'); // adjust selector as needed
['pointerdown','pointerup','pointermove','pointercancel',
'mousedown','mouseup','click','dblclick'].forEach(type => {
target.addEventListener(type, e => {
console.log(`[${type}] pointerId=${e.pointerId} buttons=${e.buttons} target=${e.target.className}`);
}, { capture: true });
});
})();Verification
To confirm the bug is reproducible before a fix:
- Open the iD editor in Edge on a touch-capable Windows device.
- Begin drawing an area.
- Rapidly double-tap the closing node.
- Attempt to pinch-to-zoom or use the zoom controls — if zoom is stuck, the bug is present.
To verify a fix resolves the issue:
- Apply the patch and rebuild or reload the patched version.
- Repeat the rapid double-tap sequence at least five times consecutively.
- After each closing double-tap, verify:
- Pinch-to-zoom functions normally.
- Single-finger pan does not unexpectedly trigger zoom.
- The zoom control buttons (+/−) respond correctly.
- Confirm that no page reload is required between test iterations.
Rollback indicator:
If zoom remains broken after applying a candidate fix, the drag/button-state reset is either incomplete or occurs on the wrong code path. Re-examine mode-exit sequences and pointer-capture release points.
Prevention
- State machine invariant enforcement: Add assertions or defensive resets ensuring that the map's drag state and pointer-capture state are always clean when any editing mode exits, regardless of the triggering event type.
- Regression test: Add an automated integration test (using a framework such as Playwright or Cypress with touch event simulation) that performs a rapid double-tap area-close and asserts that zoom behavior is unaffected afterward.
- Touch-specific CI coverage: Run iD's test suite with a simulated touch device profile in CI to catch pointer-event-sequence regressions before they reach production.
- Edge version pinning in test matrix: Include Chromium-based Edge in the browser matrix for touch-input tests, as Edge's pointer event coalescing behavior on Windows can differ from Chrome.
pointercancelhandling audit: Regularly audit all interactive behaviors that capture pointer events to ensurepointercancelalways produces a safe fallback state, preventing future orphaned drag states from any cause.