From 4cb91121dfceef7c8dc7664f1f77b1c087b1d72c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:57:03 +0000 Subject: [PATCH 01/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. --- .jules/bolt.md | 3 +++ apps/web/src/components/InteractiveTranscript.tsx | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) 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..a00d9d7a8 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); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From b5603ea583f623202d0b57e5b9392b55bf2164ac Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:06:47 +0000 Subject: [PATCH 02/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From fc7f8a261ceb44ec8456008549d2d51ffc46e5b6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:12:33 +0000 Subject: [PATCH 03/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From 94a4380a803ceceec416560afc20d455600013fb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:16:42 +0000 Subject: [PATCH 04/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From aca4199ac6e7367fe7542886e4d25efbc163a31e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:54:06 +0000 Subject: [PATCH 05/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From 70d68f6fb1ea0e6cf3ab8e951ff82560e08332a4 Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:11:05 -0500 Subject: [PATCH 06/21] fix(web): preserve unique transcript filter optimizations from #917 --- apps/web/src/components/InteractiveTranscript.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index a00d9d7a8..c22f7489b 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -166,13 +166,17 @@ export default function InteractiveTranscript({ ); const filteredSegments = useMemo(() => { + if (!filterSpeaker && !searchQuery) return segments; + const searchLower = searchQuery ? searchQuery.toLowerCase() : ''; return segments.filter((seg) => { const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; - const matchesSearch = + if (!matchesSpeaker) return false; + + return ( !searchLower || - seg.text.toLowerCase().includes(searchLower); - return matchesSpeaker && matchesSearch; + (seg.text ? seg.text.toLowerCase().includes(searchLower) : false) + ); }); }, [segments, filterSpeaker, searchQuery]); From 21fee91c27f60598dc867aa2e8b90a65ecf9867c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 02:14:55 +0000 Subject: [PATCH 07/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. --- apps/web/src/components/InteractiveTranscript.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index c22f7489b..a00d9d7a8 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -166,17 +166,13 @@ export default function InteractiveTranscript({ ); const filteredSegments = useMemo(() => { - if (!filterSpeaker && !searchQuery) return segments; - const searchLower = searchQuery ? searchQuery.toLowerCase() : ''; return segments.filter((seg) => { const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; - if (!matchesSpeaker) return false; - - return ( + const matchesSearch = !searchLower || - (seg.text ? seg.text.toLowerCase().includes(searchLower) : false) - ); + seg.text.toLowerCase().includes(searchLower); + return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From a41967136ec6bdc1d31183202e5b3b73a6fb6fc9 Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:08:31 -0400 Subject: [PATCH 08/21] fix: preserve transcript text fallback in search Restore the prior runtime guard while keeping the lowercased query outside the filter loop. --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index a00d9d7a8..09ddb8a95 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - seg.text.toLowerCase().includes(searchLower); + (seg.text ? seg.text.toLowerCase().includes(searchLower) : false); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From 47f5977362f9535d6ab16b8e0cc12b576506f8db Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 03:19:03 +0000 Subject: [PATCH 09/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index 09ddb8a95..a00d9d7a8 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - (seg.text ? seg.text.toLowerCase().includes(searchLower) : false); + seg.text.toLowerCase().includes(searchLower); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From 36c6cceae32825a22622b1a18419e582d50abda5 Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:12:06 -0400 Subject: [PATCH 10/21] fix: retain transcript text guard after agent churn Restore the nullish-text fallback re-removed by automated head churn while preserving query normalization outside the filter loop. --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index a00d9d7a8..09ddb8a95 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - seg.text.toLowerCase().includes(searchLower); + (seg.text ? seg.text.toLowerCase().includes(searchLower) : false); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From 7e7c519ec3e1bd93ed45f462912daa1667338638 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 04:22:50 +0000 Subject: [PATCH 11/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index 09ddb8a95..a00d9d7a8 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - (seg.text ? seg.text.toLowerCase().includes(searchLower) : false); + seg.text.toLowerCase().includes(searchLower); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From 97a6a2e572535365c995672c4c6aca35d8ed275f Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Fri, 24 Jul 2026 01:13:24 -0400 Subject: [PATCH 12/21] fix: make transcript text fallback explicit Use optional chaining with a false fallback so external nullish transcript data cannot crash search while query normalization remains outside the filter loop. --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index a00d9d7a8..878dd0bb1 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - seg.text.toLowerCase().includes(searchLower); + (seg.text?.toLowerCase().includes(searchLower) ?? false); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From fafb5a24c1b8f42f610e5866869189a9046d9269 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 05:23:46 +0000 Subject: [PATCH 13/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index 878dd0bb1..a00d9d7a8 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - (seg.text?.toLowerCase().includes(searchLower) ?? false); + seg.text.toLowerCase().includes(searchLower); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From 43aa72565f0b5087087c28066fc36628588544ab Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Fri, 24 Jul 2026 02:16:09 -0400 Subject: [PATCH 14/21] fix: restore transcript nullish guard Issue: #908 Run: eventrelay-blocker-watch-20260724T0613Z Agent: groupthinking --- apps/web/src/components/InteractiveTranscript.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/InteractiveTranscript.tsx b/apps/web/src/components/InteractiveTranscript.tsx index a00d9d7a8..878dd0bb1 100644 --- a/apps/web/src/components/InteractiveTranscript.tsx +++ b/apps/web/src/components/InteractiveTranscript.tsx @@ -171,7 +171,7 @@ export default function InteractiveTranscript({ const matchesSpeaker = !filterSpeaker || seg.speaker === filterSpeaker; const matchesSearch = !searchLower || - seg.text.toLowerCase().includes(searchLower); + (seg.text?.toLowerCase().includes(searchLower) ?? false); return matchesSpeaker && matchesSearch; }); }, [segments, filterSpeaker, searchQuery]); From 8c2d7aed7ce20bbacf9b3aa22380b50c20718e51 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 06:33:47 +0000 Subject: [PATCH 15/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From afdc2a978fc4d95a6d889e1fcc441c38e5679d21 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 07:35:31 +0000 Subject: [PATCH 16/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From c911cd4a6e6f6da5863abd842c2691f580b7e6d0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:25:26 +0000 Subject: [PATCH 17/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From 17f34764174f0426c0e1cf61324ad7a2f1970b39 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:16:01 +0000 Subject: [PATCH 18/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From f40d36b4b37bf7130c0628aaffbe782e4bafd3f3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:17:38 +0000 Subject: [PATCH 19/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From d828079e79edcb430fd068f93fc85897db474683 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:18:39 +0000 Subject: [PATCH 20/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string. From f0832860d5708f7345dfcb48095691026c14558e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:22:09 +0000 Subject: [PATCH 21/21] =?UTF-8?q?=E2=9A=A1=20Bolt:=20optimize=20transcript?= =?UTF-8?q?=20search=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoists `searchQuery.toLowerCase()` out of the O(n) `.filter()` loop inside the `filteredSegments` memo in `InteractiveTranscript.tsx`. Preserves undefined fallback safety by defaulting to an empty string.