Files
hermes-webui/tests/test_pwa_sidebar_swipe.py
T
55738a50ea Release v0.51.581 — full-screen PWA mobile sidebar drawer (#4660) (#4683)
* stage #4660 v2 (franksong2702): full-screen PWA mobile sidebar drawer — REBUILT on current master

REBUILD: the prior stage was cut before tonight's #4597/#4584/#4583 ships, so its 3-dot
diff falsely appeared to revert them (Codex CORE/SILENT flags). Rebuilt on current master
(authoritative head a035b1343): clean apply, tonight's fixes confirmed INTACT (_apply_sidebar_
state_db_overrides, bottomDistance, overflow-x:hidden all present). #4660's own change is
additive — full-screen drawer width, edge-guard, mobile close button, overlay->display:none;
does NOT touch the scroll/perf/mobile-pan lines. Nathan concept-greenlit + visual signed off.
71 mobile-layout+swipe tests pass.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>

* fix #4660 edge-guard regression: defer preventDefault until horizontal intent + touch-action:pan-y; update 2 tests to assert corrected behavior

* fix #4660 edge-guard hit-test interception (Codex CORE): pointer-events:none + drop guard listener

Deeper fix than the prior preventDefault change: the #pwaSidebarEdgeGuard was a fixed
interactive element (z-index 198, no pointer-events:none) that intercepted hit-testing for
taps/vertical-scrolls starting in the left 24px strip (since .messages is the real scroller
under body:overflow:hidden). Now guard is pointer-events:none with NO dedicated touch
listener; the existing window-level CAPTURE handlers still catch the edge-swipe-to-open.
Removed the orphaned _onPwaSidebarEdgeGuardStart fn. Updated 2 tests to assert the new
non-interactive design. 71 mobile tests pass.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-22 01:10:42 -07:00

76 lines
4.5 KiB
Python

from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
def test_pwa_edge_swipe_gesture_is_registered_for_mobile_sidebar():
assert "function _installPwaSidebarSwipeGesture" in BOOT_JS
# #4660 review (Codex CORE): the guard element must NOT have its own touch
# listener — it's pointer-events:none and the window-level capture handlers
# below see the edge swipe regardless, so taps/scrolls starting in the strip
# pass through to the app instead of being intercepted.
assert "guard.addEventListener(" not in BOOT_JS
assert "_onPwaSidebarEdgeGuardStart" not in BOOT_JS
assert "window.addEventListener('touchstart', _onPwaSidebarSwipeStart, {capture:true,passive:true})" in BOOT_JS
assert "window.addEventListener('touchmove', _onPwaSidebarSwipeMove, {capture:true,passive:false})" in BOOT_JS
assert "window.addEventListener('touchend', _onPwaSidebarSwipeEnd, {capture:true,passive:true})" in BOOT_JS
assert "window.addEventListener('touchcancel', _onPwaSidebarSwipeCancel, {capture:true,passive:true})" in BOOT_JS
assert "window.addEventListener('pointerdown', _onPwaSidebarSwipeStart" in BOOT_JS
assert "window.addEventListener('pointermove', _onPwaSidebarSwipeMove" in BOOT_JS
assert "window.addEventListener('pointerup', _onPwaSidebarSwipeEnd" in BOOT_JS
assert "window.addEventListener('pointercancel', _onPwaSidebarSwipeCancel" in BOOT_JS
assert "function _isTouchPointerEvent" in BOOT_JS
assert "if(_isTouchPointerEvent(e))return" in BOOT_JS
def test_pwa_sidebar_swipe_is_edge_gated_standalone_and_horizontal():
assert "_isPwaStandalone()" in BOOT_JS
assert "_PWA_SIDEBAR_SWIPE_EDGE" in BOOT_JS
assert "_PWA_SIDEBAR_SWIPE_CLAIM" in BOOT_JS
assert "_PWA_SIDEBAR_SWIPE_TRIGGER" in BOOT_JS
assert "_PWA_SIDEBAR_SWIPE_MAX_VERTICAL" in BOOT_JS
assert "clientX>_PWA_SIDEBAR_SWIPE_EDGE" in BOOT_JS.replace(" ", "")
assert "dx>=_PWA_SIDEBAR_SWIPE_CLAIM" in BOOT_JS.replace(" ", "")
assert "e.preventDefault()" in BOOT_JS[BOOT_JS.find("function _onPwaSidebarSwipeMove"):BOOT_JS.find("function _onPwaSidebarSwipeEnd")]
assert "dx>=_PWA_SIDEBAR_SWIPE_TRIGGER" in BOOT_JS.replace(" ", "")
assert "Math.abs(dy)<=_PWA_SIDEBAR_SWIPE_MAX_VERTICAL" in BOOT_JS.replace(" ", "")
assert "dx>Math.abs(dy)*1.5" in BOOT_JS.replace(" ", "")
assert "input,textarea,select,button,a,[contenteditable=\"true\"],.topbar-chips,.composer-left,.sidebar,.rightpanel" in BOOT_JS
assert ".messages" not in BOOT_JS[BOOT_JS.find("function _isInteractiveSwipeTarget"):BOOT_JS.find("function _openMobileSidebarFromGesture")]
def test_pwa_sidebar_edge_guard_is_non_interactive_and_swipe_uses_window_capture():
# #4660 review (Codex CORE): the left edge guard must not intercept hit-testing.
# It is pointer-events:none (CSS) with NO dedicated touch listener, so taps and
# vertical scrolls starting in the strip fall through to the .messages scroller.
# The edge-swipe-to-open gesture is handled by window-level CAPTURE listeners,
# and _onPwaSidebarSwipeMove only preventDefaults once horizontal intent is set.
assert "_onPwaSidebarEdgeGuardStart" not in BOOT_JS, (
"the interactive edge-guard handler must be gone (guard is pointer-events:none)"
)
assert "guard.addEventListener(" not in BOOT_JS
move = BOOT_JS[BOOT_JS.find("function _onPwaSidebarSwipeMove"):BOOT_JS.find("function _onPwaSidebarSwipeEnd")]
assert "_PWA_SIDEBAR_SWIPE_CLAIM" in move and "e.preventDefault()" in move, (
"horizontal-intent claim should preventDefault only inside the move handler"
)
def test_pwa_sidebar_swipe_opens_existing_mobile_drawer_without_desktop_collapse():
assert "_openMobileSidebarFromGesture" in BOOT_JS
assert "sidebar.classList.remove('sidebar-collapsed')" in BOOT_JS
assert "sidebar.classList.add('mobile-open')" in BOOT_JS
body = BOOT_JS[BOOT_JS.find("function _openMobileSidebarFromGesture"):BOOT_JS.find("function _installPwaSidebarSwipeGesture")]
assert "overlay.classList.add('visible')" not in body
assert "toggleSidebar(" not in BOOT_JS[BOOT_JS.find("function _openMobileSidebarFromGesture"):BOOT_JS.find("function _installPwaSidebarSwipeGesture")]
def test_pwa_sidebar_swipe_does_not_disable_horizontal_scrollers_globally():
compact = STYLE_CSS.replace(" ", "")
assert "html{touch-action" not in compact
assert "body{touch-action" not in compact
assert ".layout{touch-action" not in compact