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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
## 2026-07-28 - Memoize text processing in React
**Learning:** Performing expensive string manipulations like splitting long texts (`transcript.split('\n')`) or generating dynamic Regex expressions inside a component body causes significant CPU overhead on every re-render (like keystroke updates in a search box).
**Action:** Extract pure transformation logic on static/infrequent data into `useMemo` hooks (e.g., memoizing the paragraph split on `transcript` and precomputing search `RegExp` based on `searchQuery`).
## 2026-07-23 - Optimize string operations in React filtering loops
**Learning:** Found an inefficiency in `InteractiveTranscript.tsx` where `.toLowerCase()` on the search query was evaluated *inside* a `.filter()` loop, repeating a constant operation O(N) times. Additionally, the expensive text matching was evaluated even if the speaker filter failed.
**Action:** When filtering large arrays in React `useMemo` hooks, always hoist constant operations (like query lowercasing) outside the loop and use short-circuit evaluation (`if (!matchesPreviousCondition) return false;`) to skip expensive string methods. Also remember to add safety checks like `val ? val.toLowerCase() : ''` to avoid crashes.
16 changes: 13 additions & 3 deletions apps/web/src/components/InteractiveTranscript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,22 @@ export default function InteractiveTranscript({
);

const filteredSegments = useMemo(() => {
// Optimization: Pre-compute the lowercase search query outside the loop
// to prevent recalculating it for every segment. Also short-circuit the
// expensive string matching if the speaker filter already fails.
const lowerQuery = searchQuery ? searchQuery.toLowerCase() : '';

return segments.filter((seg) => {
const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker;

// Short-circuit: if speaker doesn't match, we can skip the text search
if (!matchesSpeaker) return false;

const matchesSearch =
!searchQuery ||
seg.text.toLowerCase().includes(searchQuery.toLowerCase());
return matchesSpeaker && matchesSearch;
!lowerQuery ||
(seg.text ? seg.text.toLowerCase().includes(lowerQuery) : false);

return matchesSearch;
});
}, [segments, filterSpeaker, searchQuery]);

Expand Down
Loading