Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions web/src/hooks/useSidebarResize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,41 @@ export function useSidebarResize() {
const [isDragging, setIsDragging] = useState(false)
const startXRef = useRef(0)
const startWidthRef = useRef(0)
const activePointerIdRef = useRef<number | null>(null)

const onPointerDown = useCallback((e: React.PointerEvent) => {
e.preventDefault()
activePointerIdRef.current = e.pointerId
startXRef.current = e.clientX
startWidthRef.current = width
setIsDragging(true)
;(e.target as HTMLElement).setPointerCapture(e.pointerId)
}, [width])

const onPointerMove = useCallback((e: React.PointerEvent) => {
// Global listeners ensure pointerup is always captured even if cursor leaves the handle
useEffect(() => {
if (!isDragging) return
const delta = e.clientX - startXRef.current
setWidth(clamp(startWidthRef.current + delta))
}, [isDragging])

const onPointerUp = useCallback(() => {
if (!isDragging) return
setIsDragging(false)
const onMove = (e: PointerEvent) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] The document listeners still accept every PointerEvent while isDragging is true. That means a second finger/stylus/mouse can move the sidebar, and an unrelated pointerup/pointercancel can stop the drag, instead of keeping ownership with the pointer that began resizing.

Suggested fix:

const activePointerIdRef = useRef<number | null>(null)

const onPointerDown = useCallback((e: React.PointerEvent) => {
    e.preventDefault()
    activePointerIdRef.current = e.pointerId
    startXRef.current = e.clientX
    startWidthRef.current = width
    setIsDragging(true)
}, [width])

const onMove = (e: PointerEvent) => {
    if (e.pointerId !== activePointerIdRef.current) return
    const delta = e.clientX - startXRef.current
    setWidth(clamp(startWidthRef.current + delta))
}

const onUp = (e: PointerEvent) => {
    if (e.pointerId !== activePointerIdRef.current) return
    activePointerIdRef.current = null
    setIsDragging(false)
}

if (e.pointerId !== activePointerIdRef.current) return
const delta = e.clientX - startXRef.current
setWidth(clamp(startWidthRef.current + delta))
}

const onUp = (e: PointerEvent) => {
if (e.pointerId !== activePointerIdRef.current) return
activePointerIdRef.current = null
setIsDragging(false)
}

document.addEventListener('pointermove', onMove)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] These document listeners are not filtered by pointerId, so once a resize starts, any other pointer on the page can move or end it. That is a regression from setPointerCapture, which kept the drag bound to the initiating pointer only.

Suggested fix:

const activePointerIdRef = useRef<number | null>(null)

const onPointerDown = useCallback((e: React.PointerEvent) => {
    e.preventDefault()
    activePointerIdRef.current = e.pointerId
    startXRef.current = e.clientX
    startWidthRef.current = width
    setIsDragging(true)
}, [width])

const onMove = (e: PointerEvent) => {
    if (e.pointerId !== activePointerIdRef.current) return
    const delta = e.clientX - startXRef.current
    setWidth(clamp(startWidthRef.current + delta))
}

const onUp = (e: PointerEvent) => {
    if (e.pointerId !== activePointerIdRef.current) return
    activePointerIdRef.current = null
    setIsDragging(false)
}

document.addEventListener('pointerup', onUp)
document.addEventListener('pointercancel', onUp)

return () => {
document.removeEventListener('pointermove', onMove)
document.removeEventListener('pointerup', onUp)
document.removeEventListener('pointercancel', onUp)
}
}, [isDragging])

// Persist width to localStorage when drag ends
Expand All @@ -65,5 +82,5 @@ export function useSidebarResize() {
}
}, [isDragging])

return { width, isDragging, onPointerDown, onPointerMove, onPointerUp }
return { width, isDragging, onPointerDown }
}
2 changes: 0 additions & 2 deletions web/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,6 @@ function SessionsPage() {
className="sidebar-resize-handle hidden lg:block shrink-0"
data-dragging={sidebar.isDragging || undefined}
onPointerDown={sidebar.onPointerDown}
onPointerMove={sidebar.onPointerMove}
onPointerUp={sidebar.onPointerUp}
/>

<div className={`${isSessionsIndex ? 'hidden lg:flex' : 'flex'} min-w-0 flex-1 flex-col bg-[var(--app-bg)]`}>
Expand Down
Loading