Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-04-28 - Component Array Sorting Performance
**Learning:** In React components like `ConversationsSidebar`, copying and sorting arrays on every render (e.g., `[...conversations].sort(...)`) causes unnecessary object allocation and CPU overhead, especially if the component has local state causing frequent re-renders (like input selection). Additionally, when dealing with standard ISO 8601 timestamp strings, allocating `new Date()` objects purely for chronological sorting is an expensive anti-pattern.
**Action:** Always wrap array mapping/sorting in `useMemo` when deriving lists from props. Furthermore, use lexicographical string comparison (`b.localeCompare(a)`) to sort ISO 8601 date strings instead of parsing them into `Date` objects.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The recommendation to use localeCompare for ISO 8601 strings is slightly suboptimal for performance-critical code. Direct string comparison is faster and sufficient for these timestamps.

Suggested change
**Action:** Always wrap array mapping/sorting in `useMemo` when deriving lists from props. Furthermore, use lexicographical string comparison (`b.localeCompare(a)`) to sort ISO 8601 date strings instead of parsing them into `Date` objects.
**Action:** Always wrap array mapping/sorting in useMemo when deriving lists from props. Furthermore, use direct string comparison (b > a) to sort ISO 8601 date strings instead of parsing them into Date objects or using localeCompare.

12 changes: 6 additions & 6 deletions apps/app/src/components/ConversationsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Conversations sidebar component β€” left sidebar with conversation list.
*/

import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useApp } from "../AppContext";

interface ConversationsSidebarProps {
Expand Down Expand Up @@ -54,11 +54,11 @@ export function ConversationsSidebar({
}
}, [editingId]);

const sortedConversations = [...conversations].sort((a, b) => {
const aTime = new Date(a.updatedAt).getTime();
const bTime = new Date(b.updatedAt).getTime();
return bTime - aTime;
});
const sortedConversations = useMemo(() => {
// ⚑ Bolt: Memoize the sorting and use lexicographical string comparison
// for ISO 8601 timestamps to avoid expensive new Date() allocations on every render.
return [...conversations].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

While localeCompare is an improvement over new Date(), it is significantly slower than direct string comparison operators (> and <) for ISO 8601 timestamps. Since this PR focuses on performance, using basic comparison is more efficient as it avoids the overhead of locale-sensitive collation logic. Note that formatRelativeTime (line 190) still performs new Date() allocations on every render; to fully achieve the stated performance goals, you might consider memoizing those values as well.

Suggested change
return [...conversations].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
return [...conversations].sort((a, b) => (b.updatedAt > a.updatedAt ? 1 : b.updatedAt < a.updatedAt ? -1 : 0));

}, [conversations]);

const handleDoubleClick = (conv: { id: string; title: string }) => {
setEditingId(conv.id);
Expand Down
Loading