Skip to content

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

Open
tannerlinsley wants to merge 2 commits into
mainfrom
taren/editorial-cherry-pick
Open

feat(home): browse-by-category cards + /stack/$category pages + stacked social dropdown#935
tannerlinsley wants to merge 2 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
    • Added dedicated category pages for browsing organized library collections at /stack/$category
    • Refactored navbar social links into a compact dropdown menu showing stacked icons with all social channels

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

📝 Walkthrough

Walkthrough

This PR adds a library categorization system by reorganizing libraries into framework and state groups, creates browsable category landing pages with featured picks and related blog posts, updates the homepage to display categories as cards, wires dynamic /stack/$category routes, and refactors navbar social links into a dropdown component.

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 and GroupId types with bidirectional slug-to-group mappings and category metadata (names, headlines, intros, top picks, gradient classes). Reorganizes libraries by moving start and router to framework group while keeping query, db, store, ai in state. Exports getCategoryLibraries utility.
Category landing page component
src/components/stack/CategoryArticle.tsx
Implements CategoryArticle that renders breadcrumb, hero section, featured top-pick library card with frameworks and GitHub link, full ordered library list with gradient badges, and optional related-posts section. Includes subcomponents for library entries, post listings, section headers, and framework chips.
Category route and page wiring
src/routes/stack.$category.tsx
Creates /stack/$category file-based route with parameter validation, loader returning category metadata, SEO head generation, and page component rendering CategoryArticle.
Generated route type registries
src/routeTree.gen.ts
Registers /stack/$category across TanStack Router type maps (FileRoutesByFullPath, FileRoutesByTo, FileRoutesById), extends FileRouteTypes unions, adds to RootRouteChildren, updates module augmentation, and wires into runtime route tree.
Homepage category cards display
src/routes/index.tsx
Replaces individual LibraryCard rendering on homepage with StackCategoryCard grid. Each card displays category name, ordered libraries with gradient color swatches (TanStack prefix stripped), and Browse link to category page via groupToSlug.

Navbar social links dropdown

Layer / File(s) Summary
Social dropdown component
src/components/Navbar.tsx
Adds Dropdown component imports, replaces inline socialLinks JSX with SocialStack() call, defines SOCIAL_LINKS constant with all channel metadata (GitHub, Discord, YouTube, X, Bluesky, Instagram), and implements SocialStack to render overlapped social icons in dropdown trigger with all channels in menu content using rel="noopener noreferrer".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • TanStack/tanstack.com#911: Introduces getPostsForLibrary in src/utils/blog.ts, which is consumed by CategoryArticle's related-posts section in this PR.

Suggested reviewers

  • LadyBluenotes
  • schiller-manuel

Poem

🐰 With categories stacked and social links neat,
We browse the libraries from end to end street.
The framework and state now have their own place,
While dropdowns descend with a top-three embrace.
Features link onwards through routes yet untrod,
A carousel-click from the homepage we trod! 🌟

🚥 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 directly and clearly summarizes the three main changes: browse-by-category cards on home, new /stack/$category pages, and stacked social dropdown in 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.

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