diff --git a/.jules/bolt.md b/.jules/bolt.md index af3158d3..44fb1b46 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. + +## 2024-07-20 - Avoid chained flatMap().find() and flatMap().filter() on grouped data structures + +**Learning:** When dealing with nested or grouped data structures (like `SessionGroup[]` where each group contains `Session[]`), chaining `.flatMap()` to flatten the arrays followed by `.find()` or `.filter()` causes the engine to allocate an entirely new flattened O(N) array in memory before performing the search. This introduces significant memory allocation overhead and multiple linear traversals. +**Action:** Instead of `flatMap().find()`, use nested loops or `groupedData.some(group => group.items.some(condition))` to allow for O(1) early bailout matching without pre-allocating a flattened array. For `.filter()`, use nested `.forEach()` loops and manually `.push()` matching items into a state array to perform the filtering in a single memory-efficient pass. diff --git a/app/2026/tags/[tag]/page.tsx b/app/2026/tags/[tag]/page.tsx index 5c9a6ebc..cfaa5410 100644 --- a/app/2026/tags/[tag]/page.tsx +++ b/app/2026/tags/[tag]/page.tsx @@ -3,6 +3,7 @@ import TalkCard from "@/components/layout/TalkCard"; import CTASection from "@/components/sections/CTASection"; import { getEditionConfig } from "@/config/editions"; import { getTagsFromTalk, getTalks } from "@/hooks/useTalks"; +import { Talk } from "@/hooks/types"; import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; @@ -39,9 +40,21 @@ export async function generateMetadata({ params }: { params: Promise<{ tag: stri const decodedTag = decodeURIComponent(tag); 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 targetTagLower = decodedTag.toLowerCase(); + + const state = { matchedTag: undefined as string | undefined }; + sessionGroups.some((group) => + group.sessions.some((talk) => { + const found = getTagsFromTalk(talk).find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower); + if (found) { + state.matchedTag = found; + return true; + } + return false; + }) + ); + + const displayTag = state.matchedTag ?? decodedTag.replaceAll("-", " "); return { title: `Talks tagged "${displayTag}" - DevBcn ${year}`, @@ -56,17 +69,29 @@ export default async function Page({ params }: { params: Promise<{ tag: string } const eventData = getEditionConfig(year); 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 targetTagLower = decodedTag.toLowerCase(); - const filteredTalks = allTalks.filter((talk) => { - const talkTags = getTagsFromTalk(talk); + const state = { + filteredTalks: [] as Talk[], + displayTag: undefined as string | undefined, + }; - return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()); + sessionGroups.forEach((group) => { + group.sessions.forEach((talk) => { + const matchedTag = getTagsFromTalk(talk).find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower); + if (matchedTag) { + if (!state.displayTag) { + state.displayTag = matchedTag; + } + state.filteredTalks.push(talk); + } + }); }); + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); + const filteredTalks = state.filteredTalks; + if (filteredTalks.length === 0) { notFound(); } diff --git a/app/[year]/tags/[tag]/page.tsx b/app/[year]/tags/[tag]/page.tsx index 35c74f61..9cbf269e 100644 --- a/app/[year]/tags/[tag]/page.tsx +++ b/app/[year]/tags/[tag]/page.tsx @@ -3,6 +3,7 @@ import TalkCard from "@/components/layout/TalkCard"; import CTASection from "@/components/sections/CTASection"; import { getArchivedEditions, getEditionConfig } from "@/config/editions"; import { getTagsFromTalk, getTalks } from "@/hooks/useTalks"; +import { Talk } from "@/hooks/types"; import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; @@ -46,9 +47,21 @@ export async function generateMetadata({ params }: Readonly): Prom const decodedTag = decodeURIComponent(tag); 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 targetTagLower = decodedTag.toLowerCase(); + + const state = { matchedTag: undefined as string | undefined }; + sessionGroups.some((group) => + group.sessions.some((talk) => { + const found = getTagsFromTalk(talk).find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower); + if (found) { + state.matchedTag = found; + return true; + } + return false; + }) + ); + + const displayTag = state.matchedTag ?? decodedTag.replaceAll("-", " "); return { title: `Talks tagged "${displayTag}" - DevBcn ${year}`, @@ -62,17 +75,29 @@ export default async function TagPage({ params }: Readonly) { const eventData = getEditionConfig(year); 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 targetTagLower = decodedTag.toLowerCase(); - const filteredTalks = allTalks.filter((talk) => { - const talkTags = getTagsFromTalk(talk); + const state = { + filteredTalks: [] as Talk[], + displayTag: undefined as string | undefined, + }; - return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()); + sessionGroups.forEach((group) => { + group.sessions.forEach((talk) => { + const matchedTag = getTagsFromTalk(talk).find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower); + if (matchedTag) { + if (!state.displayTag) { + state.displayTag = matchedTag; + } + state.filteredTalks.push(talk); + } + }); }); + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); + const filteredTalks = state.filteredTalks; + if (filteredTalks.length === 0) { notFound(); }