diff --git a/.jules/bolt.md b/.jules/bolt.md index af3158d3..ae49c408 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -7,3 +7,8 @@ **Learning:** Using `Object.entries(obj).find(([key]) => key === target)` creates O(N) array allocations for the entries and traverses them linearly just to do a simple property lookup. This adds unnecessary memory allocation overhead and Garbage Collection. **Action:** Use direct property lookup instead: `Object.prototype.hasOwnProperty.call(obj, target) ? obj[target as keyof typeof obj] : undefined`. This maintains O(1) performance while satisfying `security/detect-object-injection` linting rules. + +## 2026-08-09 - Avoid array.flatMap().find() for early breakout + +**Learning:** To prevent memory bloat and O(N) full array traversals during data-heavy operations, avoid the `array.flatMap(mapFn).find(findFn)` pattern. +**Action:** Use nested `for...of` loops or `.some()` with a state object to enable single-pass execution with early breakout. diff --git a/app/[year]/tags/[tag]/page.tsx b/app/[year]/tags/[tag]/page.tsx index 3d0e1f3c..04de22db 100644 --- a/app/[year]/tags/[tag]/page.tsx +++ b/app/[year]/tags/[tag]/page.tsx @@ -23,11 +23,12 @@ export async function generateStaticParams() { for (const year of years) { try { const sessionGroups = await getTalks(year); - const allTalks = sessionGroups.flatMap((group) => group.sessions); const allTags = new Set(); - for (const talk of allTalks) { - getTagsFromTalk(talk).forEach((tag) => allTags.add(tag)); + for (const group of sessionGroups) { + group.sessions.forEach((talk) => { + getTagsFromTalk(talk).forEach((tag) => allTags.add(tag)); + }); } for (const tag of allTags) { @@ -46,9 +47,11 @@ export async function generateMetadata({ params }: Readonly): Prom const decodedTag = decodeURIComponent(tag); const sessionGroups = await getTalks(year); - const allTalks = sessionGroups.flatMap((group) => group.sessions); const searchTag = decodedTag.toLowerCase(); - const matchingTalk = allTalks.find((talk) => getTagsFromTalk(talk).some((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag)); + const matchingGroup = sessionGroups.find((group) => + group.sessions.some((talk) => getTagsFromTalk(talk).some((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag)) + ); + const matchingTalk = matchingGroup?.sessions.find((talk) => getTagsFromTalk(talk).some((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag)); const displayTag = matchingTalk ? (getTagsFromTalk(matchingTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag) ?? decodedTag.replaceAll("-", " ")) : decodedTag.replaceAll("-", " "); @@ -65,15 +68,16 @@ export default async function TagPage({ params }: Readonly) { const eventData = getEditionConfig(year); const sessionGroups = await getTalks(year); - const allTalks = sessionGroups.flatMap((group) => group.sessions); - const searchTag = decodedTag.toLowerCase(); + const filteredTalks: import("@/hooks/types").Talk[] = []; - const filteredTalks = allTalks.filter((talk) => { - const talkTags = getTagsFromTalk(talk); - - return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag); - }); + for (const group of sessionGroups) { + group.sessions.forEach((talk) => { + if (getTagsFromTalk(talk).some((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag)) { + filteredTalks.push(talk); + } + }); + } const displayTag = filteredTalks[0] ? (getTagsFromTalk(filteredTalks[0]).find((t) => t.replaceAll(" ", "-").toLowerCase() === searchTag) ?? decodedTag.replaceAll("-", " "))