Mobile navbar intermittently dead until the PWA is killed (leaked drawer-gesture touch state) #173
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#173
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Symptom
On mobile, the navbar sometimes stops working entirely — the drawer can no longer be opened by swipe (and when it happens mid-drag, every tap on the screen is swallowed). Nothing in-app recovers it; the only fix is killing and restarting the PWA.
Root cause
AppShell's follow-finger drawer gesture (#140) binds touchmove/touchend/touchcancel on
window, but touch events are dispatched at the element the finger went DOWN on. When an SSE-driven re-render (chat log, runs rail, streaming tool chips) detaches that node mid-touch, the sequence's touchend/touchcancel fire on the detached node and never bubble to window:activeTouchIdis never cleared, so every later touchstart early-returns — the swipe gesture is permanently dead. On/runs/:idthere is no top strip, so the swipe IS the nav there.draggingalso sticks, keeping the full-viewport.shell-scrim(z-index 25, above the topstrip's 20) mounted — an invisible tap-eater with no reset path.On Android the next finger can reuse the stale identifier and accidentally settle the orphan; iOS WebKit never reuses identifiers, so in an iPhone home-screen PWA the breakage is permanent until the app is killed. A secondary path with the same signature: iOS backgrounding the PWA mid-touch without delivering touchcancel.
ToolPanel's bottom-sheet dismiss gesture (#145) has the identical hole with element-scoped listeners (touch starting on streaming tool output that re-renders away), bounded there by the sheet's unmount cleanup.
Fix
Self-heal at touchstart in both bindings: if the tracked identifier is gone from
e.touches, or re-appears ine.changedTouches(a finger cannot go down twice — identifier reuse proves the old sequence ended unheard), settle the orphan as cancelled and start fresh. Plus avisibilitychange→hidden reset in AppShell for the iOS backgrounding path. Regression tests at the binding seam (jsdom-fabricated touches, ToolPanel.test precedent) cover both leak phases, identifier reuse, and the visibility reset.Landing audit — PR #174
fix(web): self-heal leaked drawer/sheet touch sequencesmain's tip (clean merge)fix(web): …valid Conventional CommitsCloses #173present, will auto-close on mergeAppShell.tsx,ToolPanel.tsx, + 2 test files)onTouchCancelidioms;findActivematches onactiveTouchIdas assumedci / nativesuccess in 8m55s (run #192); relied on, not re-runVerdict: PASS. Awaiting human free-text go-ahead before merge.