Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions apps/web/src/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
20 changes: 18 additions & 2 deletions apps/web/src/components/PlanSidebar.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<ReturnType<typeof setTimeout> | null>(null);

const planMarkdown = activeProposedPlan?.planMarkdown ?? null;
const displayedPlanMarkdown = planMarkdown ? stripDisplayedPlanMarkdown(planMarkdown) : null;
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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 (
<div className="flex h-full w-[340px] shrink-0 flex-col border-l border-border/70 bg-card/50">
{/* Header */}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/session-logic.ts
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical

Issue on line in apps/web/src/session-logic.ts:367:

The sort comparator uses ?? to fall back to id comparison, but localeCompare returns 0 when strings are equal — not null or undefined. The expression 0 ?? left.id.localeCompare(right.id) evaluates to 0 instead of the ID comparison, breaking the tie-breaker and producing non-deterministic ordering when multiple plans share the same updatedAt. Consider restoring || so 0 triggers the secondary sort key.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/web/src/session-logic.ts around line 367:

The sort comparator uses `??` to fall back to `id` comparison, but `localeCompare` returns `0` when strings are equal — not `null` or `undefined`. The expression `0 ?? left.id.localeCompare(right.id)` evaluates to `0` instead of the ID comparison, breaking the tie-breaker and producing non-deterministic ordering when multiple plans share the same `updatedAt`. Consider restoring `||` so `0` triggers the secondary sort key.

Evidence trail:
apps/web/src/session-logic.ts lines 376-377 and 392-393 at REVIEWED_COMMIT show the sort comparator: `left.updatedAt.localeCompare(right.updatedAt) ?? left.id.localeCompare(right.id)`. JavaScript `localeCompare` returns `0` for equal strings (MDN documentation). The `??` operator only checks for `null`/`undefined`, not `0` (ECMAScript specification for nullish coalescing).

Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/wsTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
}

Expand Down