fix: prevent sticky sidebar hover drag state

This commit is contained in:
Michael Lam
2026-05-05 19:17:27 -07:00
parent d8cd5567e0
commit ecdbc8d4df
4 changed files with 44 additions and 7 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@@ -2509,15 +2509,31 @@ function renderSessionListFromCache(){
let _tapTimer=null;
let _pointerDownX=0;
let _pointerDownY=0;
let _pointerActive=false;
let _isDragging=false;
let _clearDragTimer=null;
const _clearPointerDragState=()=>{
_pointerActive=false;
if(_isDragging){
_isDragging=false;
if(_clearDragTimer){clearTimeout(_clearDragTimer);_clearDragTimer=null;}
_clearDragTimer=setTimeout(()=>{el.classList.remove('dragging');_clearDragTimer=null;},50);
}
};
el.onpointerdown=(e)=>{
if(e.pointerType==='mouse' && e.button!==0) return;
_pointerActive=true;
_pointerDownX=e.clientX;
_pointerDownY=e.clientY;
_isDragging=false;
if(_clearDragTimer){clearTimeout(_clearDragTimer);_clearDragTimer=null;}
el.classList.remove('dragging');
};
el.onpointermove=(e)=>{
// Plain hover also dispatches pointermove. Only mark a row as dragging
// after an actual press starts on this row; otherwise hovered rows stay
// faded until the next sidebar rerender clears their DOM nodes.
if(!_pointerActive) return;
if(_isDragging) return;
const dx=Math.abs(e.clientX-_pointerDownX);
const dy=Math.abs(e.clientY-_pointerDownY);
@@ -2528,8 +2544,11 @@ function renderSessionListFromCache(){
if(_clearDragTimer){clearTimeout(_clearDragTimer);_clearDragTimer=null;}
}
};
el.onpointercancel=_clearPointerDragState;
el.onpointerleave=()=>{ if(_pointerActive) _clearPointerDragState(); };
el.onpointerup=(e)=>{
if(e.pointerType==='mouse' && e.button!==0) return; // ignore right/middle click
_pointerActive=false;
if(_renamingSid) return;
if(actions&&actions.contains(e.target)) return;
if(e.target&&e.target.closest&&e.target.closest('.session-child-count,.session-child-sessions,.session-child-session')) return;

View File

@@ -2,6 +2,7 @@
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
import pytest
@@ -14,13 +15,21 @@ pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH")
def _run_node(source: str) -> str:
result = subprocess.run(
[NODE, "-e", source],
cwd=str(REPO_ROOT),
capture_output=True,
text=True,
timeout=10,
)
with tempfile.NamedTemporaryFile(
"w", suffix=".cjs", encoding="utf-8", dir=REPO_ROOT, delete=False
) as script:
script.write(source)
script_path = Path(script.name)
try:
result = subprocess.run(
[NODE, str(script_path)],
cwd=str(REPO_ROOT),
capture_output=True,
text=True,
timeout=10,
)
finally:
script_path.unlink(missing_ok=True)
if result.returncode != 0:
raise RuntimeError(result.stderr)
return result.stdout.strip()

View File

@@ -116,6 +116,15 @@ def test_timestamp_hidden_when_attention_state_is_present():
assert ".session-item.unread:not(:hover):not(:focus-within):not(.menu-open) .session-actions" in STYLE_CSS
def test_plain_mouse_hover_does_not_mark_session_row_dragging():
"""Pointermove fires during ordinary hover; drag styling must require an active press."""
assert "let _pointerActive=false;" in SESSIONS_JS
assert "_pointerActive=true;" in SESSIONS_JS
assert "if(!_pointerActive) return;" in SESSIONS_JS
assert "_pointerActive=false;" in SESSIONS_JS
assert ".session-item.dragging:hover" in STYLE_CSS
def test_sidebar_uses_local_inflight_state_for_immediate_spinner():
messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text()