Skip to content

feat: [performance improvement] optimize tag search using single-pass early breakout#298

Closed
anyulled wants to merge 1 commit into
mainfrom
feat/optimize-tag-search-4444219242813496544
Closed

feat: [performance improvement] optimize tag search using single-pass early breakout#298
anyulled wants to merge 1 commit into
mainfrom
feat/optimize-tag-search-4444219242813496544

Conversation

@anyulled

@anyulled anyulled commented Jun 27, 2026

Copy link
Copy Markdown
Owner

💡 What: Optimized tag searching in app/[year]/tags/[tag]/page.tsx and app/2026/tags/[tag]/page.tsx by removing flatMap + find chains and replacing them with a state-mutating nested some loop with early breakout.
🎯 Why: To improve performance by eliminating unnecessary memory allocations and garbage collection overhead caused by flatMap intermediate arrays, and to avoid O(N) full array traversals when a match is found early.
📊 Impact: Reduces memory overhead and execution time during static site generation for the tags pages, especially when iterating over the large list of conference talks.
🔬 Measurement: Verified statically that intermediate arrays are no longer created and the loop short-circuits. Built successfully with npm run build and all tests passed via npm run test.


PR created automatically by Jules for task 4444219242813496544 started by @anyulled

Summary by CodeRabbit

  • Bug Fixes
    • Tag pages now display cleaner, more accurate tag names in page titles, breadcrumbs, and headers.
    • Matching tags is now more reliable across tag pages, including year-based and archive views.
    • Improved tag lookup flow to reduce unnecessary processing and make pages respond more smoothly.

… early breakout

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devbcn-nextjs Building Building Preview, Comment Jun 27, 2026 8:16am

Request Review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Tag pages now normalize incoming tags, track the first matched display tag in mutable state, and use that state for metadata and rendering. The guidance notes also add recommendations for grouped Map mutation and avoiding flatMap().find() chains.

Changes

Tag page matching and guidance notes

Layer / File(s) Summary
Metadata tag resolution
app/2026/tags/[tag]/page.tsx, app/[year]/tags/[tag]/page.tsx
generateMetadata in both routes now scans normalized tags with mutable state and reads the matched display tag from that state instead of a flatMap(...).find(...) fallback.
Page tag filtering
app/2026/tags/[tag]/page.tsx, app/[year]/tags/[tag]/page.tsx
Page and TagPage now update shared display-tag state during normalized tag filtering, then use that state for the displayed label.
Guidance entries
.jules/bolt.md
The guidance file adds notes on .push() for grouped Map arrays and on replacing flatMap().find() chains with some() or for...of loops.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • anyulled/devbcn-nextjs#105: Shares the .jules/bolt.md guidance update about replacing array spread-based grouping with in-place mutation.
  • anyulled/devbcn-nextjs#203: Also updates .jules/bolt.md with guidance against array spread when appending into grouped arrays.

Poem

Hop, hop — I sniff the tags with care 🐇
A matching name appears right there
No flatMap maze, just loops that glide
And tucked-in push keeps rabbits proud inside
Thump-thump! The page now shows the sign

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: optimizing tag search with a single-pass early-exit approach for performance.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/optimize-tag-search-4444219242813496544

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request documents a performance optimization in .jules/bolt.md and applies it to the tag page metadata and rendering logic in both app/2026/tags/[tag]/page.tsx and app/[year]/tags/[tag]/page.tsx by replacing .flatMap().find() chains with .some() and .filter(). While this successfully avoids intermediate array allocations, the feedback highlights that mutating external state inside these array callbacks is an anti-pattern. It is recommended to refactor these iterations into clean, idiomatic for...of loops to eliminate side-effects while maintaining the performance benefits.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +43 to +56
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };

allTalks.some((talk) => {
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using .some() to iterate and mutate an external state object (state.displayTag) is an anti-pattern that introduces side-effects into array traversal methods. Instead, we can use a clean, idiomatic nested for...of loop with a labeled break. This achieves the same early breakout and performance optimization without mutating external state.

  const targetTag = decodedTag.toLowerCase();
  let displayTag = decodedTag.replaceAll("-", " ");

  outer: for (const talk of allTalks) {
    for (const t of getTagsFromTalk(talk)) {
      if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
        displayTag = t;
        break outer;
      }
    }
  }

Comment on lines +73 to +86
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };

const filteredTalks = allTalks.filter((talk) => {
const talkTags = getTagsFromTalk(talk);

return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mutating external state (state.displayTag) inside a .filter() callback is a side-effect that violates the functional purity of array methods. Since .filter() evaluates all elements in the array, it also results in redundant re-assignments.

We can optimize this into a single-pass for...of loop that filters the talks and resolves the correct displayTag casing simultaneously, without any side-effects or intermediate array allocations.

  const targetTag = decodedTag.toLowerCase();
  let displayTag = decodedTag.replaceAll("-", " ");
  const filteredTalks: typeof allTalks = [];

  for (const talk of allTalks) {
    let matches = false;
    for (const t of getTagsFromTalk(talk)) {
      if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
        displayTag = t;
        matches = true;
      }
    }
    if (matches) {
      filteredTalks.push(talk);
    }
  }

Comment on lines +50 to +63
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };

allTalks.some((talk) => {
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using .some() to iterate and mutate an external state object (state.displayTag) is an anti-pattern that introduces side-effects into array traversal methods. Instead, we can use a clean, idiomatic nested for...of loop with a labeled break. This achieves the same early breakout and performance optimization without mutating external state.

  const targetTag = decodedTag.toLowerCase();
  let displayTag = decodedTag.replaceAll("-", " ");

  outer: for (const talk of allTalks) {
    for (const t of getTagsFromTalk(talk)) {
      if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
        displayTag = t;
        break outer;
      }
    }
  }

Comment on lines +79 to +92
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };

const filteredTalks = allTalks.filter((talk) => {
const talkTags = getTagsFromTalk(talk);

return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mutating external state (state.displayTag) inside a .filter() callback is a side-effect that violates the functional purity of array methods. Since .filter() evaluates all elements in the array, it also results in redundant re-assignments.

We can optimize this into a single-pass for...of loop that filters the talks and resolves the correct displayTag casing simultaneously, without any side-effects or intermediate array allocations.

  const targetTag = decodedTag.toLowerCase();
  let displayTag = decodedTag.replaceAll("-", " ");
  const filteredTalks: typeof allTalks = [];

  for (const talk of allTalks) {
    let matches = false;
    for (const t of getTagsFromTalk(talk)) {
      if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
        displayTag = t;
        matches = true;
      }
    }
    if (matches) {
      filteredTalks.push(talk);
    }
  }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
.jules/bolt.md (1)

6-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer for...of or a helper over endorsing side effects inside .some().

This guidance is broad enough to normalize the bug introduced in the tag pages: mutating external state from nested .some() is easy to get wrong once that logic is reused inside .filter(), because later matches silently overwrite earlier ones. I’d frame for...of or a helper that returns the matched value as the default, and keep callback mutation as a narrow exception.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.jules/bolt.md around lines 6 - 9, The guidance in the document should avoid
recommending nested .some() with external state mutation as the default pattern;
update the advice to prefer a for...of loop or a small helper that returns the
matched value when replacing flatMap().find(). Reference the existing
performance note section in the markdown and adjust the guidance so it clearly
favors explicit iteration and early return, with callback mutation mentioned
only as an exception.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/`[year]/tags/[tag]/page.tsx:
- Around line 79-93: The tag label resolution in the page component is mutating
state during the `filteredTalks` filter pass, which lets later matches overwrite
the displayed tag; update the logic around `targetTag`, `filteredTalks`, and
`displayTag` so the first matching tag wins consistently. Prefer a shared
tag-resolver used by both `generateMetadata()` and the page render, or otherwise
compute `displayTag` once from the first match instead of assigning to
`state.displayTag` inside the filter callback.

In `@app/2026/tags/`[tag]/page.tsx:
- Around line 73-87: The tag display value is being overwritten in the talk
filter, so only the first matched tag should determine the page label and
metadata. Update the logic around targetTag, filteredTalks, and displayTag in
page.tsx to capture the first matching original tag once (or compute it in a
shared helper) instead of mutating state for every match, so generateMetadata
and the page header stay aligned.

---

Nitpick comments:
In @.jules/bolt.md:
- Around line 6-9: The guidance in the document should avoid recommending nested
.some() with external state mutation as the default pattern; update the advice
to prefer a for...of loop or a small helper that returns the matched value when
replacing flatMap().find(). Reference the existing performance note section in
the markdown and adjust the guidance so it clearly favors explicit iteration and
early return, with callback mutation mentioned only as an exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7af0324-10f8-4e79-ac17-8a1727dfddd4

📥 Commits

Reviewing files that changed from the base of the PR and between df1657d and fe860b1.

📒 Files selected for processing (3)
  • .jules/bolt.md
  • app/2026/tags/[tag]/page.tsx
  • app/[year]/tags/[tag]/page.tsx

Comment on lines +79 to +93
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };

const filteredTalks = allTalks.filter((talk) => {
const talkTags = getTagsFromTalk(talk);

return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t let later matches rewrite the page label.

Lines 82-90 keep mutating state.displayTag throughout the filter pass, so the rendered tag label comes from the last matching talk. generateMetadata() still uses the first match, which means the page title/header can diverge from the metadata for the same tag slug. Preserve the first match only, or share one resolver between metadata and page rendering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`[year]/tags/[tag]/page.tsx around lines 79 - 93, The tag label
resolution in the page component is mutating state during the `filteredTalks`
filter pass, which lets later matches overwrite the displayed tag; update the
logic around `targetTag`, `filteredTalks`, and `displayTag` so the first
matching tag wins consistently. Prefer a shared tag-resolver used by both
`generateMetadata()` and the page render, or otherwise compute `displayTag` once
from the first match instead of assigning to `state.displayTag` inside the
filter callback.

Comment on lines +73 to +87
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };

const filteredTalks = allTalks.filter((talk) => {
const talkTags = getTagsFromTalk(talk);

return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Capture the first matched display tag once.

Lines 76-84 overwrite state.displayTag for every matching talk. generateMetadata() stops on the first match, so the page header can disagree with the metadata title whenever equivalent tags appear with different original casing/text across talks. Keep the first matched tag, or resolve displayTag in a shared helper before filtering.

Suggested fix
   const targetTag = decodedTag.toLowerCase();
-  const state = { displayTag: decodedTag.replaceAll("-", " ") };
+  let matchedDisplayTag: string | undefined;

   const filteredTalks = allTalks.filter((talk) => {
     return getTagsFromTalk(talk).some((t) => {
       if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
-        state.displayTag = t;
+        matchedDisplayTag ??= t;
         return true;
       }
       return false;
     });
   });

-  const displayTag = state.displayTag;
+  const displayTag = matchedDisplayTag ?? decodedTag.replaceAll("-", " ");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const targetTag = decodedTag.toLowerCase();
const state = { displayTag: decodedTag.replaceAll("-", " ") };
const filteredTalks = allTalks.filter((talk) => {
const talkTags = getTagsFromTalk(talk);
return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
state.displayTag = t;
return true;
}
return false;
});
});
const displayTag = state.displayTag;
const targetTag = decodedTag.toLowerCase();
let matchedDisplayTag: string | undefined;
const filteredTalks = allTalks.filter((talk) => {
return getTagsFromTalk(talk).some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTag) {
matchedDisplayTag ??= t;
return true;
}
return false;
});
});
const displayTag = matchedDisplayTag ?? decodedTag.replaceAll("-", " ");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/2026/tags/`[tag]/page.tsx around lines 73 - 87, The tag display value is
being overwritten in the talk filter, so only the first matched tag should
determine the page label and metadata. Update the logic around targetTag,
filteredTalks, and displayTag in page.tsx to capture the first matching original
tag once (or compute it in a shared helper) instead of mutating state for every
match, so generateMetadata and the page header stay aligned.

@anyulled

anyulled commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Closing because this PR does not meet the automation criteria: it is labeled size/M instead of size S or XS, the measurement section does not provide direct performance evidence beyond static reasoning plus build/test success, and there are unresolved major review comments requesting code changes (including concerns about mutable state in the tag resolution logic).

@anyulled anyulled closed this Jul 7, 2026
@anyulled anyulled deleted the feat/optimize-tag-search-4444219242813496544 branch July 7, 2026 06:01
@google-labs-jules

Copy link
Copy Markdown
Contributor

Closing because this PR does not meet the automation criteria: it is labeled size/M instead of size S or XS, the measurement section does not provide direct performance evidence beyond static reasoning plus build/test success, and there are unresolved major review comments requesting code changes (including concerns about mutable state in the tag resolution logic).

Understood. Acknowledging that this work is now obsolete and stopping work on this task.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant