From 5388f043f684aa7f3ab5fcdaecaebea2c26d806a Mon Sep 17 00:00:00 2001 From: hobostay Date: Thu, 12 Mar 2026 11:56:29 +0800 Subject: [PATCH] fix: clean up timeout in PlanSidebar to prevent memory leaks The copy timeout was not being cleared when the component unmounted or when the copy button was clicked multiple times, potentially causing memory leaks and incorrect UI state. Refs #792 --- apps/web/src/components/PlanSidebar.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/PlanSidebar.tsx b/apps/web/src/components/PlanSidebar.tsx index c465f3989..2d898b009 100644 --- a/apps/web/src/components/PlanSidebar.tsx +++ b/apps/web/src/components/PlanSidebar.tsx @@ -1,4 +1,4 @@ -import { memo, useState, useCallback } from "react"; +import { memo, useState, useCallback, useRef, useEffect } from "react"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { ScrollArea } from "./ui/scroll-area"; @@ -66,6 +66,7 @@ const PlanSidebar = memo(function PlanSidebar({ const [proposedPlanExpanded, setProposedPlanExpanded] = useState(false); const [isSavingToWorkspace, setIsSavingToWorkspace] = useState(false); const [copied, setCopied] = useState(false); + const copiedTimerRef = useRef | null>(null); const planMarkdown = activeProposedPlan?.planMarkdown ?? null; const displayedPlanMarkdown = planMarkdown ? stripDisplayedPlanMarkdown(planMarkdown) : null; @@ -74,8 +75,15 @@ const PlanSidebar = memo(function PlanSidebar({ const handleCopyPlan = useCallback(() => { if (!planMarkdown) return; void navigator.clipboard.writeText(planMarkdown); + if (copiedTimerRef.current != null) { + clearTimeout(copiedTimerRef.current); + } + setCopied(true); - setTimeout(() => setCopied(false), 2000); + copiedTimerRef.current = setTimeout(() => { + setCopied(false); + copiedTimerRef.current = null; + }, 2000); }, [planMarkdown]); const handleDownload = useCallback(() => { @@ -120,6 +128,14 @@ const PlanSidebar = memo(function PlanSidebar({ {/* Header */}
+ // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (copiedTimerRef.current \!= null) { + clearTimeout(copiedTimerRef.current); + } + }; + }, []);