Skip to content

feat(home): browse-by-category cards + /stack/$category pages + stacked social dropdown#935

Merged
tannerlinsley merged 5 commits into
mainfrom
taren/editorial-cherry-pick
May 25, 2026
Merged

feat(home): browse-by-category cards + /stack/$category pages + stacked social dropdown#935
tannerlinsley merged 5 commits into
mainfrom
taren/editorial-cherry-pick

Conversation

@tannerlinsley
Copy link
Copy Markdown
Member

@tannerlinsley tannerlinsley commented May 20, 2026

Cherry-picks just the genuinely useful pieces from #931 on top of main. Leaves the editorial top nav, new partners layout, and most of the home rewrite alone.

Summary

  • New framework library group — Start and Router move out of Data & State into their own framework-tier category.
  • Browse the stack on home — replaces the bulky Open Source Libraries grid with five condensed category cards (one per group), each listing the libraries in that group and linking to the new category page.
  • /stack/$category landing pages — header, "Where to start" top pick, the full list of libraries in the category, and category-tagged blog posts. Intentionally doesn't port jhislop-design's verdict block, criteria/"how we think about it" section, in-this-guide TOC rail, or compare-across-the-stack rail.
  • Stacked social dropdown in the existing Navbar — the front three social icons are layered into a single trigger that opens a labeled dropdown of all six channels (GitHub, Discord, YouTube, X, Bluesky, Instagram), using the project's existing Radix-backed Dropdown.

What is NOT in this PR (from #931)

  • Editorial sticky top nav, gold partner strip, dark utility banner
  • Partners layout changes on home
  • Featured Stack card / leaderboard rail / editorial homepage rewrite
  • "Quick verdict", "How we think about it", "In this guide", and "Compare across the stack" sections on category pages
  • Reviewer framing ("X libraries reviewed", "Read the full review", etc.)

Test plan

  • pnpm test (tsc + lint) — 0 errors, only pre-existing warnings in shop/users components
  • pnpm test:smoke — 10/10 (home, blog, ethos, query/router/table docs, OG images)
  • Manual: /, /stack/framework, /stack/state, /stack/ui, /stack/performance, /stack/tooling all return 200; unknown slug returns 404

Summary by CodeRabbit

  • New Features

    • Dedicated category pages and browsing cards for organized library collections
    • Full category article view with top picks, ranked lists, and related posts
    • Route for category browsing added and linked from home
  • Refactor

    • Navbar social links reworked into a compact stacked-icons dropdown
    • Home libraries section replaced with category cards
  • Chores

    • Webhook secret validation improved
  • Bug Fixes / Performance

    • Centralized docs cache headers; reduced docs and GitHub cache TTLs and simplified refresh behavior

Review Change Stack

… social dropdown

Cherry-picks the genuinely useful pieces from #931 onto main:

- Add a "framework" library group containing Start and Router, separate from
  data-and-state, so the framework-tier libraries get their own category.
- Replace the bulky Open Source Libraries grid on the home page with a
  condensed five-card "Browse the stack" section (one card per category,
  linking to a dedicated category page).
- Add /stack/$category landing pages: header, "Where to start" top pick,
  the full list of libraries in the category, and category-tagged blog
  posts. Drops the editorial-style verdict block, criteria section,
  in-this-guide rail, and compare-across-the-stack rail.
- Replace the inline 2x3 social icon strip in Navbar with a stacked
  three-icon trigger that opens a labeled dropdown of all six channels.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37084546-f308-46b1-bfd6-92c7c9f07717

📥 Commits

Reviewing files that changed from the base of the PR and between 72d801e and 03d0e19.

📒 Files selected for processing (2)
  • src/libraries/libraries.ts
  • src/routes/$libraryId/$version.docs.framework.$framework.examples.$.tsx

📝 Walkthrough

Walkthrough

Adds library categories and category pages with homepage cards and typed route registration for /stack/$category; centralizes docs cache header computation and wires it into docs routes; tightens GitHub/docs cache refresh behavior; refactors navbar social links into a dropdown; replaces 404 with a route-recovery NotFound UI.

Changes

Library categorization and category browsing

Layer / File(s) Summary
Category system types and metadata
src/components/stack/stack-categories.ts, src/libraries/libraries.ts
Defines CategorySlug/GroupId, slug↔group maps, categoryMeta metadata (names, headlines, intros, top picks, gradients), and getCategoryLibraries. Moves start/router to a new framework group and updates librariesGroupNamesMap.
Category landing page and subcomponents
src/components/stack/CategoryArticle.tsx
Adds CategoryArticle and subcomponents rendering hero, top-pick card (framework chips, GitHub), full ordered library list with gradient badges, and optional related-posts list.
Category route and homepage integration
src/routes/stack.$category.tsx, src/routes/index.tsx
Creates /stack/$category route with param validation, loader, head, and component; updates homepage to render StackCategoryCard grid linking to category pages.
Generated route type registries
src/routeTree.gen.ts
Registers StackCategoryRoute across generated route maps/unions, module augmentation, and runtime rootRouteChildren so /stack/$category is typed and reachable.

Navbar social links dropdown

Layer / File(s) Summary
Social dropdown component
src/components/Navbar.tsx
Adds Dropdown imports, replaces inline social links with SocialStack(), defines SOCIAL_LINKS and implements a stacked-icon trigger plus dropdown menu listing all social channels with rel="noopener noreferrer" and accessible labels.

Docs caching and headers

Layer / File(s) Summary
Docs cache header utility
src/utils/docs-cache-headers.ts
Adds getDocsCacheHeaders({ libraryId, version }) that returns Cache-Control, CDN-Cache-Control, Netlify-Cache-Tag, and Vary based on library/branch/version and whether the version is latest.
Wire headers into docs routes & handlers
src/routes/*($version.docs*).tsx, src/routes/*[.]md.tsx
Replaces inline/static cache header logic across multiple docs routes and markdown endpoints with getDocsCacheHeaders({ libraryId, version }) and adjusts library resolution where required.
Docs fetch TTL tuning
src/utils/docs.functions.ts
Reduces cache TTLs in fetchDocs (300→60s) and in fetchFile/fetchRepoDirectoryContents (3600→300s).

GitHub/docs content cache behavior

Layer / File(s) Summary
Cache freshness and refresh semantics
src/utils/github-content-cache.server.ts
Reduces POSITIVE_STALE_MS from 24h to 5m, removes background queueRefresh helper, and updates forced-invalidation comments.
Single-flight refresh & stale fallback behavior
src/utils/github-content-cache.server.ts
Removes SWR-style paths that returned stale-but-present cached values while queueing refresh; non-fresh cached values now go through withPendingRefresh, with stale served only on refresh failure.

NotFound & blog integration

Layer / File(s) Summary
Route recovery NotFound
src/components/NotFound.tsx, src/routes/blog.tsx
Replaces simple 404 with scoring-based route-recovery UI, adds DestinationCard, and updates blog not-found rendering to use <NotFound />.

Scripts

Layer / File(s) Summary
Webhook secret validation
scripts/sync-docs-webhooks.ts
Adds validation for GITHUB_WEBHOOK_SECRET in non-dry-run mode (minimum length and no whitespace), and retains existing missing-secret guard behavior for non-dry-run runs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • TanStack/tanstack.com#933: Related changes to GitHub/docs caching and forced-invalidation behavior that overlap with cache decision paths updated here.

Suggested reviewers

  • LadyBluenotes
  • schiller-manuel

Poem

🐰 A stack of categories, neat and spry,
Dropdown socials peep from the sky.
Docs cached tighter, builds kept in line,
Routes point to picks that brightly shine.
Hop, click, and browse — the site feels fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% 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 PR title accurately summarizes the three main features being introduced: browse-by-category cards on the home page, new /stack/$category pages, and a stacked social dropdown in the navbar.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 taren/editorial-cherry-pick

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 skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@src/components/stack/CategoryArticle.tsx`:
- Around line 25-27: The code assumes libraries has at least one item so topPick
= libraries.find(...) ?? libraries[0] can be undefined; before dereferencing
topPick.id in the render (where topPick is used) add a guard/fallback UI when
libraries.length === 0 (e.g., return null, a placeholder, or an empty-state
component) or ensure topPick is a defined object (fallback to a safe default) so
rendering never accesses topPick.id on undefined; update the logic around
topPick, libraries and any use of meta.topPickId in the CategoryArticle
component to handle empty arrays safely.
- Line 89: The duplicated id={library.id} causes invalid HTML because
TopPickBlock and LibraryEntry both render the same id; fix by only rendering the
id on one of those components or generating a unique id for the TopPick variant
(e.g., prepend/append a stable prefix/suffix) so anchors remain deterministic;
locate the id usage in the section element and in the TopPickBlock/LibraryEntry
renderings and change one side to use a unique key like `top-pick-${library.id}`
or remove the id from the non-anchor instance.
- Around line 136-139: The span currently always outputs the literal "tanstack/"
plus library.repo?.split('/').pop(), causing "tanstack/undefined" when
library.repo is missing; update the rendering in CategoryArticle (the span that
includes <GitHub /> and the repo text) to only render the "tanstack/" prefix and
repo fragment when library.repo is truthy (e.g., conditionally render the entire
suffix or replace with a fallback/omit the prefix when library.repo is absent)
so the UI never shows "tanstack/undefined".
- Around line 270-272: In CategoryArticle.tsx where items.map(({ post, lib }) =>
...) renders each <li> with key={post.slug}, replace the unstable key with a
composite unique key that includes the library identifier (for example use
post.slug combined with lib.id or lib.slug) to avoid collisions when the same
post appears under multiple libraries; update the map key expression (in the
items.map callback) to something like `${post.slug}-${lib.id}` and ensure lib.id
(or chosen lib identifier) is present before using it.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03188ce8-e70f-4c08-97c6-ec661b44dc13

📥 Commits

Reviewing files that changed from the base of the PR and between 73c6f64 and ad7cc3c.

📒 Files selected for processing (7)
  • src/components/Navbar.tsx
  • src/components/stack/CategoryArticle.tsx
  • src/components/stack/stack-categories.ts
  • src/libraries/libraries.ts
  • src/routeTree.gen.ts
  • src/routes/index.tsx
  • src/routes/stack.$category.tsx

Comment on lines +25 to +27
const topPick =
libraries.find((lib) => lib.id === meta.topPickId) ?? libraries[0]
const relatedPosts = libraries
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against empty category library lists before dereferencing topPick.id.

If getCategoryLibraries(slug) returns an empty array, topPick is undefined and Line 77 will throw at render time. Add an early guard/fallback UI before using topPick.id.

Suggested fix
 export function CategoryArticle({ slug }: { slug: CategorySlug }) {
   const meta = categoryMeta[slug]
   const libraries = getCategoryLibraries(slug)
+  if (libraries.length === 0) {
+    return null
+  }
   const topPick =
     libraries.find((lib) => lib.id === meta.topPickId) ?? libraries[0]

Also applies to: 77-77

🤖 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 `@src/components/stack/CategoryArticle.tsx` around lines 25 - 27, The code
assumes libraries has at least one item so topPick = libraries.find(...) ??
libraries[0] can be undefined; before dereferencing topPick.id in the render
(where topPick is used) add a guard/fallback UI when libraries.length === 0
(e.g., return null, a placeholder, or an empty-state component) or ensure
topPick is a defined object (fallback to a safe default) so rendering never
accesses topPick.id on undefined; update the logic around topPick, libraries and
any use of meta.topPickId in the CategoryArticle component to handle empty
arrays safely.


function TopPickBlock({ library }: { library: LibrarySlim }) {
return (
<section id={library.id} className="scroll-mt-6">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

id attributes are duplicated for the top-pick library.

The top-pick appears once in TopPickBlock and again in LibraryEntry, both with id={library.id}. Duplicate IDs are invalid HTML and can make anchor targeting inconsistent.

Also applies to: 185-185

🤖 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 `@src/components/stack/CategoryArticle.tsx` at line 89, The duplicated
id={library.id} causes invalid HTML because TopPickBlock and LibraryEntry both
render the same id; fix by only rendering the id on one of those components or
generating a unique id for the TopPick variant (e.g., prepend/append a stable
prefix/suffix) so anchors remain deterministic; locate the id usage in the
section element and in the TopPickBlock/LibraryEntry renderings and change one
side to use a unique key like `top-pick-${library.id}` or remove the id from the
non-anchor instance.

Comment on lines +136 to +139
<span className="ml-auto inline-flex items-center gap-1.5 text-xs font-semibold text-zinc-500 dark:text-zinc-400">
<GitHub className="h-3.5 w-3.5" /> tanstack/
{library.repo?.split('/').pop()}
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid rendering tanstack/undefined when library.repo is missing.

The current string always renders the tanstack/ prefix, even when library.repo is absent.

Suggested fix
-        <span className="ml-auto inline-flex items-center gap-1.5 text-xs font-semibold text-zinc-500 dark:text-zinc-400">
-          <GitHub className="h-3.5 w-3.5" /> tanstack/
-          {library.repo?.split('/').pop()}
-        </span>
+        {library.repo && (
+          <span className="ml-auto inline-flex items-center gap-1.5 text-xs font-semibold text-zinc-500 dark:text-zinc-400">
+            <GitHub className="h-3.5 w-3.5" /> tanstack/{library.repo.split('/').pop()}
+          </span>
+        )}
📝 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
<span className="ml-auto inline-flex items-center gap-1.5 text-xs font-semibold text-zinc-500 dark:text-zinc-400">
<GitHub className="h-3.5 w-3.5" /> tanstack/
{library.repo?.split('/').pop()}
</span>
{library.repo && (
<span className="ml-auto inline-flex items-center gap-1.5 text-xs font-semibold text-zinc-500 dark:text-zinc-400">
<GitHub className="h-3.5 w-3.5" /> tanstack/{library.repo.split('/').pop()}
</span>
)}
🤖 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 `@src/components/stack/CategoryArticle.tsx` around lines 136 - 139, The span
currently always outputs the literal "tanstack/" plus
library.repo?.split('/').pop(), causing "tanstack/undefined" when library.repo
is missing; update the rendering in CategoryArticle (the span that includes
<GitHub /> and the repo text) to only render the "tanstack/" prefix and repo
fragment when library.repo is truthy (e.g., conditionally render the entire
suffix or replace with a fallback/omit the prefix when library.repo is absent)
so the UI never shows "tanstack/undefined".

Comment on lines +270 to +272
{items.map(({ post, lib }) => (
<li key={post.slug}>
<Link
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd src/components/stack && wc -l CategoryArticle.tsx

Repository: TanStack/tanstack.com

Length of output: 89


🏁 Script executed:

cd src/components/stack && sed -n '260,285p' CategoryArticle.tsx

Repository: TanStack/tanstack.com

Length of output: 1126


🏁 Script executed:

cd src/components/stack && head -50 CategoryArticle.tsx | grep -E "import|export|interface|type|const|function"

Repository: TanStack/tanstack.com

Length of output: 570


🏁 Script executed:

cd src/components/stack && sed -n '250,320p' CategoryArticle.tsx

Repository: TanStack/tanstack.com

Length of output: 2514


🏁 Script executed:

cd src/components/stack && sed -n '1,250p' CategoryArticle.tsx

Repository: TanStack/tanstack.com

Length of output: 8737


🏁 Script executed:

cd src/utils && find . -name "*blog*" -type f

Repository: TanStack/tanstack.com

Length of output: 95


🏁 Script executed:

cd src/utils && grep -n "getPostsForLibrary" blog.ts blog.functions.ts

Repository: TanStack/tanstack.com

Length of output: 143


🏁 Script executed:

cd src/utils && cat blog.ts

Repository: TanStack/tanstack.com

Length of output: 2338


🏁 Script executed:

cd src && find . -name "*categories*" -o -name "*stack*" | head -20

Repository: TanStack/tanstack.com

Length of output: 938


🏁 Script executed:

cd src/components/stack && ls -la

Repository: TanStack/tanstack.com

Length of output: 323


🏁 Script executed:

find src -name "stack-categories*" -type f

Repository: TanStack/tanstack.com

Length of output: 106


🏁 Script executed:

cd src/components/stack && cat stack-categories.ts

Repository: TanStack/tanstack.com

Length of output: 3659


🏁 Script executed:

cd content && find . -name "*.md" -type f | head -5 | xargs head -20

Repository: TanStack/tanstack.com

Length of output: 123


🏁 Script executed:

cd content && grep -r "library:" . | head -10

Repository: TanStack/tanstack.com

Length of output: 123


🏁 Script executed:

find . -type d -name "blog" | head -5

Repository: TanStack/tanstack.com

Length of output: 76


🏁 Script executed:

ls -la src/blog | head -20

Repository: TanStack/tanstack.com

Length of output: 1552


🏁 Script executed:

cd src/blog && head -30 announcing-tanstack-query-v5.md

Repository: TanStack/tanstack.com

Length of output: 3028


🏁 Script executed:

cd src/blog && head -30 announcing-tanstack-form-v1.md

Repository: TanStack/tanstack.com

Length of output: 895


🏁 Script executed:

cd src/blog && grep -E "^library:" *.md | head -20

Repository: TanStack/tanstack.com

Length of output: 918


🏁 Script executed:

cd src/blog && grep -E "^library:" *.md | grep ","

Repository: TanStack/tanstack.com

Length of output: 178


🏁 Script executed:

cd src/blog && grep -E "^(library|title):" tanstack-start-solid-v2.md why-tanstack-start-and-router.md

Repository: TanStack/tanstack.com

Length of output: 352


🏁 Script executed:

cd src && cat -n components/stack/stack-categories.ts | grep -A 3 "slugToGroup"

Repository: TanStack/tanstack.com

Length of output: 440


🏁 Script executed:

cd src && find . -name "libraries*" -o -name "index.ts" | grep -i librar | head -10

Repository: TanStack/tanstack.com

Length of output: 191


🏁 Script executed:

cd src && rg "librariesByGroup" -A 50 | head -100

Repository: TanStack/tanstack.com

Length of output: 5708


🏁 Script executed:

cd src && cat libraries/libraries.ts | head -100

Repository: TanStack/tanstack.com

Length of output: 4936


Use a stable unique key per related-post row.

key={post.slug} can collide when the same post appears for multiple libraries in this flattened list. Since posts can be tagged with multiple libraries (e.g., library: router, start) and both may belong to the same category, use a composite key (e.g., key={post.slug}-${lib.id}).

🤖 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 `@src/components/stack/CategoryArticle.tsx` around lines 270 - 272, In
CategoryArticle.tsx where items.map(({ post, lib }) => ...) renders each <li>
with key={post.slug}, replace the unstable key with a composite unique key that
includes the library identifier (for example use post.slug combined with lib.id
or lib.slug) to avoid collisions when the same post appears under multiple
libraries; update the map key expression (in the items.map callback) to
something like `${post.slug}-${lib.id}` and ensure lib.id (or chosen lib
identifier) is present before using it.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/utils/github-content-cache.server.ts`:
- Line 9: POSITIVE_STALE_MS is too short (5 minutes) and combined with the
“refresh synchronously now” behavior causes request-path traffic to block on
origin/build; either increase POSITIVE_STALE_MS to a much longer TTL (e.g., 1h)
or split the policy into two constants (e.g., POSITIVE_STALE_MS for serving
stale and POSITIVE_SYNC_REFRESH_MS for when a caller explicitly requests sync
refresh), and change the cache logic so that when an entry is only stale you
return the cached value and schedule an async background refresh instead of
awaiting a synchronous rebuild; update the code paths that call this helper (the
callers at docs.functions.ts:238 and documents.server.ts:413) to rely on the
non-blocking stale-while-revalidate behavior or to explicitly opt into sync
refresh when necessary.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed3b6925-6d90-4e5c-8e9d-034219d426c9

📥 Commits

Reviewing files that changed from the base of the PR and between ad7cc3c and 9667296.

📒 Files selected for processing (11)
  • scripts/sync-docs-webhooks.ts
  • src/routes/$libraryId/$version.docs.$.tsx
  • src/routes/$libraryId/$version.docs.community-resources.tsx
  • src/routes/$libraryId/$version.docs.framework.$framework.$.tsx
  • src/routes/$libraryId/$version.docs.framework.$framework.examples.$.tsx
  • src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx
  • src/routes/$libraryId/$version.docs.tsx
  • src/routes/$libraryId/$version.docs.{$}[.]md.tsx
  • src/utils/docs-cache-headers.ts
  • src/utils/docs.functions.ts
  • src/utils/github-content-cache.server.ts

} from '~/db/schema'

const POSITIVE_STALE_MS = 24 * 60 * 60 * 1000
const POSITIVE_STALE_MS = 5 * 60 * 1000
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid forcing request-path refreshes every 5 minutes.

With POSITIVE_STALE_MS at 5 minutes and the new “refresh synchronously now” behavior, expired positive entries will block on origin/build instead of serving cached data. Since src/utils/docs.functions.ts:238 and src/utils/documents.server.ts:413 await these helpers directly, this makes ordinary page traffic depend on GitHub/build latency every few minutes per key. Please either keep a longer positive TTL here or split the policy so request-path artifacts can still serve stale while refresh runs.

Also applies to: 133-136

🤖 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 `@src/utils/github-content-cache.server.ts` at line 9, POSITIVE_STALE_MS is too
short (5 minutes) and combined with the “refresh synchronously now” behavior
causes request-path traffic to block on origin/build; either increase
POSITIVE_STALE_MS to a much longer TTL (e.g., 1h) or split the policy into two
constants (e.g., POSITIVE_STALE_MS for serving stale and
POSITIVE_SYNC_REFRESH_MS for when a caller explicitly requests sync refresh),
and change the cache logic so that when an entry is only stale you return the
cached value and schedule an async background refresh instead of awaiting a
synchronous rebuild; update the code paths that call this helper (the callers at
docs.functions.ts:238 and documents.server.ts:413) to rely on the non-blocking
stale-while-revalidate behavior or to explicitly opt into sync refresh when
necessary.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/components/NotFound.tsx`:
- Around line 159-197: getLibraryDestinations currently selects top libraries by
score then maps to destinations, which can yield empty results for libraries
without a routable `to` or `latestVersion`; change the flow so you compute each
library's destinations first (using the same logic that creates
NotFoundDestination entries based on `library.to` and `library.latestVersion`),
discard libraries that produce zero destinations, then sort by score and take
the top 2 before flattening to return destinations; update the mapping/filtering
around getLibraryDestinations to ensure only libraries that produce at least one
destination are considered when slicing the top matches.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2557d4ec-c5dd-4064-b932-8bb78c67c5a9

📥 Commits

Reviewing files that changed from the base of the PR and between 9667296 and 72d801e.

📒 Files selected for processing (2)
  • src/components/NotFound.tsx
  • src/routes/blog.tsx

Comment on lines +159 to +197
function getLibraryDestinations(pathname: string) {
return libraries
.filter((library) => library.visible !== false)
.map((library) => ({
library,
score: scoreLibraryMatch(library, pathname),
}))
.filter((match) => match.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 2)
.flatMap(({ library, score }) => {
const destinations: NotFoundDestination[] = []

if (library.to) {
destinations.push({
key: `library-${library.id}`,
label: library.name,
description: library.tagline,
href: library.to,
icon: BookOpen,
accent: 'emerald',
score: score + 2,
})
}

if (library.latestVersion) {
destinations.push({
key: `library-docs-${library.id}`,
label: `${library.name.replace(/^TanStack\s+/i, '')} docs`,
description: 'Open the current docs for this project.',
href: `/${library.id}/latest/docs/${library.defaultDocs ?? 'overview'}`,
icon: BookOpen,
accent: 'cyan',
score: score + 1,
})
}

return destinations
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Filter non-routable libraries before taking top matches

Line 168 slices to top matches before checking whether each library can actually produce a destination. If a top-scored library has neither to nor latestVersion, it yields no cards and can suppress valid suggestions. This is reachable with entries like react-charts and create-tsrouter-app in src/libraries/libraries.ts (line range 782-834).

Proposed fix
 function getLibraryDestinations(pathname: string) {
   return libraries
-    .filter((library) => library.visible !== false)
+    .filter(
+      (library) =>
+        library.visible !== false && Boolean(library.to || library.latestVersion),
+    )
     .map((library) => ({
       library,
       score: scoreLibraryMatch(library, pathname),
     }))
📝 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
function getLibraryDestinations(pathname: string) {
return libraries
.filter((library) => library.visible !== false)
.map((library) => ({
library,
score: scoreLibraryMatch(library, pathname),
}))
.filter((match) => match.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 2)
.flatMap(({ library, score }) => {
const destinations: NotFoundDestination[] = []
if (library.to) {
destinations.push({
key: `library-${library.id}`,
label: library.name,
description: library.tagline,
href: library.to,
icon: BookOpen,
accent: 'emerald',
score: score + 2,
})
}
if (library.latestVersion) {
destinations.push({
key: `library-docs-${library.id}`,
label: `${library.name.replace(/^TanStack\s+/i, '')} docs`,
description: 'Open the current docs for this project.',
href: `/${library.id}/latest/docs/${library.defaultDocs ?? 'overview'}`,
icon: BookOpen,
accent: 'cyan',
score: score + 1,
})
}
return destinations
})
function getLibraryDestinations(pathname: string) {
return libraries
.filter(
(library) =>
library.visible !== false && Boolean(library.to || library.latestVersion),
)
.map((library) => ({
library,
score: scoreLibraryMatch(library, pathname),
}))
.filter((match) => match.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 2)
.flatMap(({ library, score }) => {
const destinations: NotFoundDestination[] = []
if (library.to) {
destinations.push({
key: `library-${library.id}`,
label: library.name,
description: library.tagline,
href: library.to,
icon: BookOpen,
accent: 'emerald',
score: score + 2,
})
}
if (library.latestVersion) {
destinations.push({
key: `library-docs-${library.id}`,
label: `${library.name.replace(/^TanStack\s+/i, '')} docs`,
description: 'Open the current docs for this project.',
href: `/${library.id}/latest/docs/${library.defaultDocs ?? 'overview'}`,
icon: BookOpen,
accent: 'cyan',
score: score + 1,
})
}
return destinations
})
🤖 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 `@src/components/NotFound.tsx` around lines 159 - 197, getLibraryDestinations
currently selects top libraries by score then maps to destinations, which can
yield empty results for libraries without a routable `to` or `latestVersion`;
change the flow so you compute each library's destinations first (using the same
logic that creates NotFoundDestination entries based on `library.to` and
`library.latestVersion`), discard libraries that produce zero destinations, then
sort by score and take the top 2 before flattening to return destinations;
update the mapping/filtering around getLibraryDestinations to ensure only
libraries that produce at least one destination are considered when slicing the
top matches.

# Conflicts:
#	src/routes/$libraryId/$version.docs.framework.$framework.examples.$.tsx
@tannerlinsley tannerlinsley merged commit e7a18da into main May 25, 2026
9 checks passed
@tannerlinsley tannerlinsley deleted the taren/editorial-cherry-pick branch May 25, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant