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-31 - [Performance] Optimize CodeBlock rendering
**Learning:** In `CodeBlock.tsx`, a validation check to ensure parsed tokens matched the raw string used nested array `map` chains with `.join('')` inside a render loop. For large codeblocks during streaming, this re-evaluated synchronously on every delta causing heavy allocations (O(lines) arrays and O(lines) strings) which bogged down the main thread.
**Action:** Use manual loops and length checks instead of `.map(...).join('')` chains for synchronizing debounced state against raw data on hot paths. This verifies string equivalency incrementally, eliminating intermediate array and string allocations on every render frame.
28 changes: 18 additions & 10 deletions apps/web/src/components/chat/ChatThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@ 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('|');
// Optimize performance: use direct string concatenation instead of Array.prototype.join()
// to prevent allocating an intermediate array on a hot path during streaming.
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
25 changes: 24 additions & 1 deletion apps/web/src/components/chat/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,30 @@ 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;

let tokensMatch = false;
if (tokens) {
let expectedLen = 0;
for (let i = 0; i < tokens.length; i++) {
const line = tokens[i];
for (let j = 0; j < line.length; j++) {
expectedLen += line[j].content.length;
}
if (i < tokens.length - 1) expectedLen += 1;
}
if (expectedLen === code.length) {
let expectedStr = '';
for (let i = 0; i < tokens.length; i++) {
const line = tokens[i];
for (let j = 0; j < line.length; j++) {
expectedStr += line[j].content;
}
if (i < tokens.length - 1) expectedStr += '\n';
}
tokensMatch = expectedStr === code;
}
}
const shown = tokensMatch ? tokens : null;

async function onCopy() {
const ok = await copyText(code);
Expand Down
52 changes: 52 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading