diff --git a/.jules/bolt.md b/.jules/bolt.md index 9345ee7a..c54f2cad 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -2,3 +2,8 @@ **Learning:** In Next.js/React applications, when grouping items (like schedules or talks) into a `Map` where the values are arrays, using the array spread operator `[...existing, item]` inside a loop (like `forEach` or `map`) causes amortized O(N^2) memory allocations and unnecessary Garbage Collection overhead. **Action:** Always use `.push()` on the existing array reference if the data structure permits local mutation. For strict ESLint configurations enforcing `no-restricted-syntax`, extract the existing array, push to it, and handle the fallback elegantly (`if (!existing) { map.set(key, [item]); } else { existing.push(item); }`). + +## 2024-05-18 - Single-Pass Iteration for Display Elements + +**Learning:** When generating page metadata or filtering elements by a URL parameter, it is common to perform an O(N) traversal (like `.flatMap(...).find(...)`) to find a valid display string, and then perform a second O(N) pass to filter the elements. This causes redundant string allocations and nested iterations. +**Action:** Always combine lookup iterations into the primary filtering loop if possible. To maintain strict `no-restricted-syntax` rules that forbid `let` declarations while mutating local tracking variables inside a `.some()` or `.filter()` loop, utilize a `const state = { displayTag?: string }` object and assign values to it when matches are found. This allows safe, early-breaking single-pass algorithms. diff --git a/app/2026/tags/[tag]/page.tsx b/app/2026/tags/[tag]/page.tsx index 5c9a6ebc..2b2e7202 100644 --- a/app/2026/tags/[tag]/page.tsx +++ b/app/2026/tags/[tag]/page.tsx @@ -40,8 +40,22 @@ export async function generateMetadata({ params }: { params: Promise<{ tag: stri const sessionGroups = await getTalks(year); const allTalks = sessionGroups.flatMap((group) => group.sessions); - const displayTag = - allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " "); + const targetTag = decodedTag.toLowerCase(); + const state: { displayTag?: string } = {}; + for (const talk of allTalks) { + const talkTags = getTagsFromTalk(talk); + const match = talkTags.some((t) => { + if (t.replaceAll(" ", "-").toLowerCase() === targetTag) { + state.displayTag = t; + return true; + } + return false; + }); + if (match) { + break; + } + } + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); return { title: `Talks tagged "${displayTag}" - DevBcn ${year}`, @@ -58,15 +72,22 @@ export default async function Page({ params }: { params: Promise<{ tag: string } const sessionGroups = await getTalks(year); const allTalks = sessionGroups.flatMap((group) => group.sessions); - const displayTag = - allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " "); + const targetTag = decodedTag.toLowerCase(); + const state: { displayTag?: string } = {}; const filteredTalks = allTalks.filter((talk) => { const talkTags = getTagsFromTalk(talk); - - return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()); + return talkTags.some((t) => { + if (t.replaceAll(" ", "-").toLowerCase() === targetTag) { + if (!state.displayTag) state.displayTag = t; + return true; + } + return false; + }); }); + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); + if (filteredTalks.length === 0) { notFound(); } diff --git a/app/[year]/tags/[tag]/page.tsx b/app/[year]/tags/[tag]/page.tsx index 35c74f61..f67084da 100644 --- a/app/[year]/tags/[tag]/page.tsx +++ b/app/[year]/tags/[tag]/page.tsx @@ -47,8 +47,22 @@ export async function generateMetadata({ params }: Readonly): Prom const sessionGroups = await getTalks(year); const allTalks = sessionGroups.flatMap((group) => group.sessions); - const displayTag = - allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " "); + const targetTag = decodedTag.toLowerCase(); + const state: { displayTag?: string } = {}; + for (const talk of allTalks) { + const talkTags = getTagsFromTalk(talk); + const match = talkTags.some((t) => { + if (t.replaceAll(" ", "-").toLowerCase() === targetTag) { + state.displayTag = t; + return true; + } + return false; + }); + if (match) { + break; + } + } + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); return { title: `Talks tagged "${displayTag}" - DevBcn ${year}`, @@ -64,15 +78,22 @@ export default async function TagPage({ params }: Readonly) { const sessionGroups = await getTalks(year); const allTalks = sessionGroups.flatMap((group) => group.sessions); - const displayTag = - allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " "); + const targetTag = decodedTag.toLowerCase(); + const state: { displayTag?: string } = {}; const filteredTalks = allTalks.filter((talk) => { const talkTags = getTagsFromTalk(talk); - - return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()); + return talkTags.some((t) => { + if (t.replaceAll(" ", "-").toLowerCase() === targetTag) { + if (!state.displayTag) state.displayTag = t; + return true; + } + return false; + }); }); + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); + if (filteredTalks.length === 0) { notFound(); }