From fa997ecb939b0c089219752e08a7bebc1bf0ebb9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:02:47 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20CodeBlock=20and?= =?UTF-8?q?=20ChatThread=20string=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: - Memoized `shown` code tokens calculation using manual `for` loops in `CodeBlock.tsx` instead of chained `.map().join()`. - Refactored `sig` signature generation in `ChatThread.tsx` to use manual string concatenation rather than allocating a new array and calling `.join()`. 🎯 Why: - These are hot UI paths in chat polling and rendering. Repeatedly calling `.map().join()` created unnecessary intermediate array and string allocations, causing CPU and Memory overhead, leading to GC pressure. - Following `.jules/bolt.md` performance insights to prevent these overheads. 📊 Impact: - Reduces memory allocations and Garbage Collection pressure when rendering multiple code blocks and on chat polls. 🔬 Measurement: - Use React Profiler or performance tools to verify memory usage doesn't spike on active chat polling. - Unit and UI tests passed successfully. Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com> --- apps/web/src/components/chat/ChatThread.tsx | 18 ++++++++---------- apps/web/src/components/chat/CodeBlock.tsx | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/chat/ChatThread.tsx b/apps/web/src/components/chat/ChatThread.tsx index 50a0209c1b..c1b9c537f1 100644 --- a/apps/web/src/components/chat/ChatThread.tsx +++ b/apps/web/src/components/chat/ChatThread.tsx @@ -18,16 +18,14 @@ 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/components/chat/CodeBlock.tsx b/apps/web/src/components/chat/CodeBlock.tsx index a86bfe9584..a333222d95 100644 --- a/apps/web/src/components/chat/CodeBlock.tsx +++ b/apps/web/src/components/chat/CodeBlock.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'preact/hooks'; +import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { Check, Copy } from 'lucide-preact'; import { copyText } from '../../lib/clipboard'; import { highlightCode, normalizeLang, type CodeToken } from '../../lib/highlight'; @@ -26,7 +26,19 @@ export function CodeBlock({ code, lang }: { code: string; lang?: string }) { window.clearTimeout(t); }; }, [code, lang]); - const shown = tokens && tokens.map((l) => l.map((t) => t.content).join('')).join('\n') === code ? tokens : null; + + const shown = useMemo(() => { + if (!tokens) return null; + let text = ''; + for (let i = 0; i < tokens.length; i++) { + const line = tokens[i]; + for (let j = 0; j < line.length; j++) { + text += line[j].content; + } + if (i < tokens.length - 1) text += '\n'; + } + return text === code ? tokens : null; + }, [tokens, code]); async function onCopy() { const ok = await copyText(code);