authorgravatar for 110hqc@gmail.comQiaochu Hu <110hqc@gmail.com> 2026-03-13 03:32:00+08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-12 12:32:00-07:00
log724f54c2c1f4a1efcd7423a5ff0e3db625629815
tree01e66aac32a01b9401ea93fc1c42d294742a68b4
parentb6eba3347d7a4153981269b064c4cee3b0166ebb
signature Signed by PGP key B5690EEEBB952194

fix: clean up timeout in PlanSidebar to prevent memory leaks (#949)

Co-authored-by: hobostay <hobostay@users.noreply.github.com>

1 files changed, 18 insertions(+), 2 deletions(-)

apps/web/src/components/PlanSidebar.tsx+18-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { memo, useState, useCallback } from "react";1import { memo, useState, useCallback, useRef, useEffect } from "react";
2import { Badge } from "./ui/badge";2import { Badge } from "./ui/badge";
3import { Button } from "./ui/button";3import { Button } from "./ui/button";
4import { ScrollArea } from "./ui/scroll-area";4import { ScrollArea } from "./ui/scroll-area";
...@@ -66,6 +66,7 @@ const PlanSidebar = memo(function PlanSidebar({...@@ -66,6 +66,7 @@ const PlanSidebar = memo(function PlanSidebar({
66 const [proposedPlanExpanded, setProposedPlanExpanded] = useState(false);66 const [proposedPlanExpanded, setProposedPlanExpanded] = useState(false);
67 const [isSavingToWorkspace, setIsSavingToWorkspace] = useState(false);67 const [isSavingToWorkspace, setIsSavingToWorkspace] = useState(false);
68 const [copied, setCopied] = useState(false);68 const [copied, setCopied] = useState(false);
69 const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
6970
70 const planMarkdown = activeProposedPlan?.planMarkdown ?? null;71 const planMarkdown = activeProposedPlan?.planMarkdown ?? null;
71 const displayedPlanMarkdown = planMarkdown ? stripDisplayedPlanMarkdown(planMarkdown) : null;72 const displayedPlanMarkdown = planMarkdown ? stripDisplayedPlanMarkdown(planMarkdown) : null;
...@@ -74,8 +75,15 @@ const PlanSidebar = memo(function PlanSidebar({...@@ -74,8 +75,15 @@ const PlanSidebar = memo(function PlanSidebar({
74 const handleCopyPlan = useCallback(() => {75 const handleCopyPlan = useCallback(() => {
75 if (!planMarkdown) return;76 if (!planMarkdown) return;
76 void navigator.clipboard.writeText(planMarkdown);77 void navigator.clipboard.writeText(planMarkdown);
78 if (copiedTimerRef.current != null) {
79 clearTimeout(copiedTimerRef.current);
80 }
81
77 setCopied(true);82 setCopied(true);
78 setTimeout(() => setCopied(false), 2000);83 copiedTimerRef.current = setTimeout(() => {
84 setCopied(false);
85 copiedTimerRef.current = null;
86 }, 2000);
79 }, [planMarkdown]);87 }, [planMarkdown]);
8088
81 const handleDownload = useCallback(() => {89 const handleDownload = useCallback(() => {
...@@ -120,6 +128,14 @@ const PlanSidebar = memo(function PlanSidebar({...@@ -120,6 +128,14 @@ const PlanSidebar = memo(function PlanSidebar({
120 {/* Header */}128 {/* Header */}
121 <div className="flex h-12 shrink-0 items-center justify-between border-b border-border/60 px-3">129 <div className="flex h-12 shrink-0 items-center justify-between border-b border-border/60 px-3">
122 <div className="flex items-center gap-2">130 <div className="flex items-center gap-2">
131 // Cleanup timeout on unmount
132 useEffect(() => {
133 return () => {
134 if (copiedTimerRef.current \!= null) {
135 clearTimeout(copiedTimerRef.current);
136 }
137 };
138 }, []);
123 <Badge139 <Badge
124 variant="secondary"140 variant="secondary"
125 className="rounded-md bg-blue-500/10 px-1.5 py-0 text-[10px] font-semibold tracking-wide text-blue-400 uppercase"141 className="rounded-md bg-blue-500/10 px-1.5 py-0 text-[10px] font-semibold tracking-wide text-blue-400 uppercase"