Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
## 2026-10-15 - [Performance] Prevent unnecessary UI array recalculations
**Learning:** In the chat component (`useThreadList`), derived state that requires filtering and sorting large arrays (like generating nested agent lists) was running synchronously on every render. Given that chat polling triggers frequent re-renders, this caused unnecessary CPU strain on lists that only rarely change.
**Action:** Use `useMemo` for derived states like `rows` and `current` in custom hooks when they involve `.filter()`, `.map()`, or `.sort()` and depend on data that updates less frequently than the component renders.

## 2026-10-27 - [Performance] Optimize Chat Thread Autoscroll Signature Generation
**Learning:** During chat polling and streaming, \`useStickToBottom\` in \`ChatThread.tsx\` generates a signature \`sig\` on every render to detect if the chat content has grown and needs to be autoscrolled. It was using \`[...].join('|')\`. In \`chat-messages.ts\`, \`messagesSignature\` was also using \`[...].join('|')\` for a similar purpose. \`Array.prototype.join()\` allocates an intermediate array and then creates the string, causing CPU and memory overhead on hot paths like frequent polling and streaming renders.
**Action:** Replaced \`[...].join('|')\` with direct template string concatenation (\`\` `${a}|${b}|...` \`\`) to avoid the intermediate array allocation and improve performance. Prefer direct string concatenation over \`Array.prototype.join()\` on hot paths.
11 changes: 1 addition & 10 deletions apps/web/src/components/chat/ChatThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,7 @@ export function useStickToBottom(messages: ChatMessage[], resetKey: unknown) {
if (!box) return;
// On ne défile que si le contenu a réellement changé.
const last = messages[messages.length - 1];
const sig = [
messages.length,
last?.content.length ?? 0,
last?.toolCalls?.length ?? 0,
last?.actions?.length ?? 0,
last?.reflections?.length ?? 0,
last?.milestones?.length ?? 0,
last?.liveLabel ?? '',
last?.streaming ? 1 : 0,
].join('|');
const sig = `${messages.length}|${last?.content.length ?? 0}|${last?.toolCalls?.length ?? 0}|${last?.actions?.length ?? 0}|${last?.reflections?.length ?? 0}|${last?.milestones?.length ?? 0}|${last?.liveLabel ?? ''}|${last?.streaming ? 1 : 0}`;
if (sig === lastSig.current) return;
const first = lastSig.current === '';
lastSig.current = sig;
Expand Down
8 changes: 1 addition & 7 deletions apps/web/src/lib/chat-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@ export function messagesSignature(messages: IdentifiedMessage[]): string {
}
}

return [
messages.length,
last?.id ?? '',
last?.content.length ?? 0,
last?.toolCalls?.length ?? 0,
reactionsSig,
].join('|');
return `${messages.length}|${last?.id ?? ''}|${last?.content.length ?? 0}|${last?.toolCalls?.length ?? 0}|${reactionsSig}`;
}

/**
Expand Down
Loading