From 5adfbed97d8bb1822f118cd17a7dfb7b6e5d4d3b Mon Sep 17 00:00:00 2001 From: maaz Date: Fri, 10 Jul 2026 04:28:24 +0530 Subject: [PATCH] fix: discard pending sidebar width on collapse --- .../src/renderer/components/Sidebar.test.tsx | 33 +++++++++++++++++++ frontend/src/renderer/hooks/useResizable.ts | 16 +++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/frontend/src/renderer/components/Sidebar.test.tsx b/frontend/src/renderer/components/Sidebar.test.tsx index c42a5238b..f85ffcc46 100644 --- a/frontend/src/renderer/components/Sidebar.test.tsx +++ b/frontend/src/renderer/components/Sidebar.test.tsx @@ -511,4 +511,37 @@ describe("Sidebar", () => { expect(document.documentElement.style.getPropertyValue("--ao-sidebar-w")).toBe("280px"); expect(window.localStorage.getItem("ao-sidebar-w")).toBe("280"); }); + + it("discards a queued narrow resize frame when collapsing", async () => { + let queuedFrame: FrameRequestCallback | undefined; + const requestAnimationFrameSpy = vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => { + queuedFrame = callback; + return 1; + }); + const cancelAnimationFrameSpy = vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => undefined); + + try { + renderSidebar(); + + const resizeHandle = document.querySelector(".resize-handle--right"); + if (!(resizeHandle instanceof HTMLElement)) throw new Error("Resize handle not found"); + + fireEvent.pointerDown(resizeHandle, { clientX: 240 }); + fireEvent.pointerMove(window, { clientX: 205 }); + fireEvent.pointerMove(window, { clientX: 120 }); + + await waitFor(() => { + expect(document.querySelector('[data-slot="sidebar"][data-state="collapsed"]')).toBeInTheDocument(); + }); + expect(cancelAnimationFrameSpy).toHaveBeenCalledWith(1); + expect(window.localStorage.getItem("ao-sidebar-w")).toBe("240"); + expect(document.documentElement.style.getPropertyValue("--ao-sidebar-w")).toBe("240px"); + + queuedFrame?.(performance.now()); + expect(document.documentElement.style.getPropertyValue("--ao-sidebar-w")).toBe("240px"); + } finally { + requestAnimationFrameSpy.mockRestore(); + cancelAnimationFrameSpy.mockRestore(); + } + }); }); diff --git a/frontend/src/renderer/hooks/useResizable.ts b/frontend/src/renderer/hooks/useResizable.ts index 52d35d293..4f47bb058 100644 --- a/frontend/src/renderer/hooks/useResizable.ts +++ b/frontend/src/renderer/hooks/useResizable.ts @@ -79,6 +79,14 @@ export function useResizable({ if (pending !== null) apply(pending); }, [apply]); + const discardPending = useCallback(() => { + if (frameRef.current !== null) { + window.cancelAnimationFrame(frameRef.current); + frameRef.current = null; + } + pendingWidthRef.current = null; + }, []); + // Restore persisted width on mount. useEffect(() => { const saved = Number(window.localStorage.getItem(storageKey)); @@ -109,8 +117,10 @@ export function useResizable({ const nextWidth = startWidth + sign * (e.clientX - startX); if (collapseBelow !== undefined && onCollapse && nextWidth <= collapseBelow) { collapsed = true; - flushPending(); - window.localStorage.setItem(storageKey, String(Math.min(max, Math.max(min, startWidth)))); + const preservedWidth = Math.min(max, Math.max(min, startWidth)); + discardPending(); + apply(preservedWidth); + window.localStorage.setItem(storageKey, String(preservedWidth)); onUp(); onCollapse(); return; @@ -120,7 +130,7 @@ export function useResizable({ window.addEventListener("pointermove", onMove); window.addEventListener("pointerup", onUp); }, - [applyOnFrame, collapseBelow, edge, flushPending, max, min, onCollapse, storageKey], + [apply, applyOnFrame, collapseBelow, discardPending, edge, flushPending, max, min, onCollapse, storageKey], ); const onCollapsedPointerDown = useCallback(