Fix action tracker hold getting permanently stuck on tablets#110
Open
Fix action tracker hold getting permanently stuck on tablets#110
Conversation
Two bugs caused the hold indicator to get permanently stuck on mobile tablets: 1. pointercancel was being ignored for confirmed holds, but on mobile touch devices it's a terminal event — no pointerup follows after it. When the OS/browser cancels the pointer (e.g. palm rejection, gesture takeover, finger sliding to bezel), the hold would never end. 2. handlePointerEnd read activePointers from its React state closure, but rapid pointer events could fire before React re-rendered with the updated Map, causing the handler to miss the pointer ID entirely. Fixes: - Treat pointercancel as a valid end event (same as pointerup) for confirmed holds. Only pointerleave is still ignored. - Mirror activePointers in a ref (activePointersRef) so event handlers always see the latest state regardless of render timing. - Wrap releasePointerCapture in try/catch since the capture may already be released after a pointercancel. - Use updateActivePointers() helper everywhere to keep state and ref in sync. - Remove activePointers from blur handler dependency array (reads from ref now) to avoid unnecessary effect re-subscriptions.
|
tytremblay
approved these changes
Mar 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Got a report that sometimes on tablets, pressing and releasing an action button in hold mode leaves the button stuck in the "held" state (glowing ring + pulse). Only way out was refreshing the page.
Video of the bug:
https://github.com/user-attachments/assets/83bef037-a7b5-4614-8395-cf0a75685c08
What was happening
To be honest, I couldn't reproduce this bug on our tablet, but this is what the AI says:
Two things going on:
pointercancelwas being ignored for confirmed holds. The idea was that the browser might fire spurious cancels while the user is still holding, but on mobilepointercancelis actually a terminal event (nopointerupfollows). So when the OS decides to cancel the touch (palm rejection, finger hitting the bezel, gesture takeover, etc.), the hold just never ends.handlePointerEndwas readingactivePointersfrom its React state closure, but with rapid tapping the state could be stale (not yet re-rendered), so the handler would miss the pointer ID entirely and silently skip cleanup.Fix
pointercancelas a valid end event for confirmed holds (same aspointerup). Onlypointerleaveis still ignored.activePointersso event handlers always read the latest state regardless of render timing.releasePointerCapturein try/catch since it can throw after apointercancel.