diff --git a/.jules/bolt.md b/.jules/bolt.md index 603b207d0..debdc3efc 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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-28 - Safely hoist string methods in React memo hooks +**Learning:** Hoisting string methods like `.toLowerCase()` outside of `.filter` loops inside `useMemo` correctly prevents N+1 string operations. However, if the hoisted string was originally evaluated lazily inside a `!string || string.toLowerCase()` guard, lifting it without a guard will cause a runtime crash if the string is undefined or null. +**Action:** Always include a safety check (e.g. `val ? val.toLowerCase() : ''`) when hoisting operations out of conditional evaluation loops to preserve the previous fallback safety. diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index 21f79d91b..878dd0bb1 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -166,11 +166,12 @@ export default function InteractiveTranscript({ ); const filteredSegments = useMemo(() => { + const searchLower = searchQuery ? searchQuery.toLowerCase() : ''; return segments.filter((seg) => { const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = - !searchQuery || - seg.text.toLowerCase().includes(searchQuery.toLowerCase()); + !searchLower || + (seg.text?.toLowerCase().includes(searchLower) ?? false); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]);