diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index f4298fc22..a3d8329ad 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -213,8 +213,12 @@ function SuspenseShikiCodeBlock({ const highlightedHtml = useMemo(() => { try { return highlighter.codeToHtml(code, { lang: language, theme: themeName }); - } catch { - // If highlighting fails for this language, render as plain text + } catch (error) { + // Log highlighting failures for debugging while falling back to plain text + console.warn( + `Code highlighting failed for language "${language}", falling back to plain text.`, + error instanceof Error ? error.message : error, + ); return highlighter.codeToHtml(code, { lang: "text", theme: themeName }); } }, [code, highlighter, language, themeName]); diff --git a/apps/web/src/components/PlanSidebar.tsx b/apps/web/src/components/PlanSidebar.tsx index c465f3989..3b455bd8c 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,14 @@ 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(() => { @@ -115,6 +122,15 @@ const PlanSidebar = memo(function PlanSidebar({ ); }, [planMarkdown, workspaceRoot]); + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (copiedTimerRef.current != null) { + clearTimeout(copiedTimerRef.current); + } + }; + }, []); + return (
{/* Header */} diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index aa8f3ffc3..9c1f43dbf 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -373,7 +373,7 @@ export function findLatestProposedPlan( .filter((proposedPlan) => proposedPlan.turnId === latestTurnId) .toSorted( (left, right) => - left.updatedAt.localeCompare(right.updatedAt) || left.id.localeCompare(right.id), + left.updatedAt.localeCompare(right.updatedAt) ?? left.id.localeCompare(right.id), ) .at(-1); if (matchingTurnPlan) { @@ -390,7 +390,7 @@ export function findLatestProposedPlan( const latestPlan = [...proposedPlans] .toSorted( (left, right) => - left.updatedAt.localeCompare(right.updatedAt) || left.id.localeCompare(right.id), + left.updatedAt.localeCompare(right.updatedAt) ?? left.id.localeCompare(right.id), ) .at(-1); if (!latestPlan) { diff --git a/apps/web/src/wsTransport.ts b/apps/web/src/wsTransport.ts index 2882098c4..46c74d909 100644 --- a/apps/web/src/wsTransport.ts +++ b/apps/web/src/wsTransport.ts @@ -186,8 +186,9 @@ export class WsTransport { this.scheduleReconnect(); }); - ws.addEventListener("error", () => { - // close will follow + ws.addEventListener("error", (event) => { + // Log WebSocket errors for debugging (close event will follow) + console.warn("WebSocket connection error", { type: event.type, url: this.url }); }); }