diff --git a/.jules/bolt.md b/.jules/bolt.md index edb687422..9754a0845 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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. diff --git a/apps/web/src/components/chat/ChatThread.tsx b/apps/web/src/components/chat/ChatThread.tsx index 50a0209c1..4ed9b6c43 100644 --- a/apps/web/src/components/chat/ChatThread.tsx +++ b/apps/web/src/components/chat/ChatThread.tsx @@ -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; diff --git a/apps/web/src/lib/chat-messages.ts b/apps/web/src/lib/chat-messages.ts index 4b5db1852..a4f9f2dbc 100644 --- a/apps/web/src/lib/chat-messages.ts +++ b/apps/web/src/lib/chat-messages.ts @@ -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}`; } /**