## Summary
Row-overlay Trace and Filter buttons silently did nothing on touch
swipes. `ensureRowOverlay` stamped `data-hash` only on the Copy button,
while `onClickAction` gates both `trace` and `filter` navigation on
`hash && ...` — so the click handler short-circuited before
`location.hash` was set. Users saw the buttons but tapping them was a
no-op.
## Fix
`public/touch-gestures.js` — in `ensureRowOverlay`, stamp `data-hash` on
all three buttons (Trace, Filter, Copy) from the same source the Copy
button already used (`row.getAttribute('data-hash') ||
row.getAttribute('data-id')`). One-line factoring of the attribute
fragment to avoid duplicating the escape logic.
Behavior after fix:
- Trace → `#/packets/<hash>`
- Filter → `#/packets?hash=<hash>`
- Copy → clipboard (unchanged)
All three match the existing branches in `onClickAction`.
## TDD
- **RED commit** (`dd90f72c`): removes the cov1/cov2 workaround in
`test-touch-gestures-coverage-e2e.js` that artificially stamped
`data-hash` on trace/filter buttons from the test harness. With this
commit alone, cov1/cov2 fail their `location.hash` assertions because
`onClickAction`'s guard short-circuits.
- **GREEN commit** (`a526c30f`): production fix in `ensureRowOverlay`.
cov1/cov2 now pass natively against the real production code path with
no harness-side stamping.
## Browser verified
Coverage E2E (`test-touch-gestures-coverage-e2e.js`) exercises the real
swipe → overlay → button-click → navigation path in headless Chromium
against the running server. cov1 asserts `location.hash ===
#/packets/<hash>`, cov2 asserts `location.hash ===
#/packets?hash=<hash>` — these assertions are the regression gate.
E2E assertion added: test-touch-gestures-coverage-e2e.js:227 (cov1
trace) and test-touch-gestures-coverage-e2e.js:259 (cov2 filter).
## Preflight
All hard gates and warnings pass.
Fixes#1305
---------
Co-authored-by: openclaw <bot@openclaw>
Red commit: bbb98cf81aae38bff1ef77a7c8a701813b25bb77 (CI run: pending —
see Checks tab)
Fixes#1062. Parent: #1052.
## Gesture system
Adds touch-gesture handling on phones (≤768px):
1. **Swipe-left on a packets/nodes/observers row** → reveals row-action
overlay (trace, filter, copy hash). Threshold: 24% of row width OR 80px.
Sub-threshold = visual peek that snaps back.
2. **Horizontal swipe on the bottom-nav strip** → advances tabs in TAB
order from `bottom-nav.js`. Packets ↔ Live ↔ Map etc.
3. **Swipe-down on a slide-over panel** → calls
`window.SlideOver.close()`.
## Hard constraints met
- **Pointer Events ONLY** — no `touchstart`/`touchend` mixing.
`setPointerCapture` for tracking continuity.
- **Axis-lock** — direction committed in first 8–12px movement. Vertical
scroll is never blocked unless we explicitly committed to a horizontal
swipe. `body { touch-action: pan-y }` so the browser owns vertical
natively.
- **Leaflet exclusion** — handlers early-bail on
`e.target.closest('.leaflet-container')` so pinch/pan on the map tab are
untouched.
- **Singleton pattern** — module-scoped `__touchGestures1062InitCount`
guard. Document-level pointer listeners registered exactly once even if
the script loads multiple times (mirrors the #1180 fix class).
- **prefers-reduced-motion** — animations have `transition-duration: 0s`
under the media query; gestures still trigger, snaps are instant.
## E2E
`test-gestures-1062-e2e.js` — Playwright with synthesized PointerEvents
(page.touchscreen unreliable in headless for axis-locked custom
handlers). Wired into the deploy.yml matrix.
E2E assertion added: test-gestures-1062-e2e.js:120 (overlay-visible
after left-swipe), :201 (tab advance), :219 (Leaflet exclusion), :247
(slide-over dismiss).
---------
Co-authored-by: openclaw-bot <bot@openclaw>
Co-authored-by: OpenClaw Bot <bot@openclaw.dev>
Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com>
Co-authored-by: clawbot <clawbot@users.noreply.github.com>
Co-authored-by: corescope-bot <bot@corescope.local>
Co-authored-by: openclaw-bot <bot@openclaw.local>