From 90d916106549c0198c7f5afaaa4ad32f38ae6abe Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 20 Apr 2026 09:55:36 -0500 Subject: [PATCH] fix(org): cover touch pan click suppression Co-Authored-By: Paperclip --- ui/src/pages/OrgChart.test.tsx | 27 +++++++++++++++++++++++++++ ui/src/pages/OrgChart.tsx | 15 ++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/ui/src/pages/OrgChart.test.tsx b/ui/src/pages/OrgChart.test.tsx index 06c2864a4e1..ee4b9f36dd9 100644 --- a/ui/src/pages/OrgChart.test.tsx +++ b/ui/src/pages/OrgChart.test.tsx @@ -219,6 +219,33 @@ describe("OrgChart mobile gestures", () => { expect(layer.style.transform).toBe("translate(50px, 105px) scale(1)"); }); + it("suppresses card navigation after a touch pan", async () => { + const { viewport } = await renderOrgChart(); + const card = container.querySelector("[data-org-card]") as HTMLDivElement; + + await act(async () => { + viewport.dispatchEvent(createTouchEvent("touchstart", [{ clientX: 100, clientY: 100 }])); + viewport.dispatchEvent(createTouchEvent("touchmove", [{ clientX: 130, clientY: 145 }])); + viewport.dispatchEvent(createTouchEvent("touchend", [])); + card.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); + }); + + expect(navigateMock).not.toHaveBeenCalled(); + }); + + it("allows card navigation after a touch tap without movement", async () => { + const { viewport } = await renderOrgChart(); + const card = container.querySelector("[data-org-card]") as HTMLDivElement; + + await act(async () => { + viewport.dispatchEvent(createTouchEvent("touchstart", [{ clientX: 100, clientY: 100 }])); + viewport.dispatchEvent(createTouchEvent("touchend", [])); + card.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); + }); + + expect(navigateMock).toHaveBeenCalledWith("/agents/ceo"); + }); + it("pinch-zooms toward the touch center", async () => { const { viewport, layer } = await renderOrgChart(); diff --git a/ui/src/pages/OrgChart.tsx b/ui/src/pages/OrgChart.tsx index 033193a04b0..c5ea70e501a 100644 --- a/ui/src/pages/OrgChart.tsx +++ b/ui/src/pages/OrgChart.tsx @@ -229,6 +229,15 @@ export function OrgChart() { moved: false, }); const suppressNextCardClick = useRef(false); + const suppressClickTimerRef = useRef(null); + + useEffect(() => { + return () => { + if (suppressClickTimerRef.current !== null) { + window.clearTimeout(suppressClickTimerRef.current); + } + }; + }, []); // Center the chart on first load const hasInitialized = useRef(false); @@ -400,8 +409,12 @@ export function OrgChart() { const handleTouchEnd = useCallback(() => { if (touchGesture.current.moved) { suppressNextCardClick.current = true; - window.setTimeout(() => { + if (suppressClickTimerRef.current !== null) { + window.clearTimeout(suppressClickTimerRef.current); + } + suppressClickTimerRef.current = window.setTimeout(() => { suppressNextCardClick.current = false; + suppressClickTimerRef.current = null; }, 400); } touchGesture.current = {