| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { memo, useState, useCallback } from "react"; | 1 | import { memo, useState, useCallback, useRef, useEffect } from "react"; |
| 2 | import { Badge } from "./ui/badge"; | 2 | import { Badge } from "./ui/badge"; |
| 3 | import { Button } from "./ui/button"; | 3 | import { Button } from "./ui/button"; |
| 4 | import { ScrollArea } from "./ui/scroll-area"; | 4 | import { 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); |
| 69 | | 70 | |
| 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]); |
| 80 | | 88 | |
| 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 | <Badge | 139 | <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" |