feat(home): browse-by-category cards + /stack/$category pages + stacked social dropdown#935
feat(home): browse-by-category cards + /stack/$category pages + stacked social dropdown#935tannerlinsley wants to merge 2 commits into
Conversation
… 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.
📝 WalkthroughWalkthroughThis 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 ChangesLibrary categorization and category browsing
Navbar social links dropdown
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
src/components/Navbar.tsxsrc/components/stack/CategoryArticle.tsxsrc/components/stack/stack-categories.tssrc/libraries/libraries.tssrc/routeTree.gen.tssrc/routes/index.tsxsrc/routes/stack.$category.tsx
| const topPick = | ||
| libraries.find((lib) => lib.id === meta.topPickId) ?? libraries[0] | ||
| const relatedPosts = libraries |
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
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.
| <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".
| {items.map(({ post, lib }) => ( | ||
| <li key={post.slug}> | ||
| <Link |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd src/components/stack && wc -l CategoryArticle.tsxRepository: TanStack/tanstack.com
Length of output: 89
🏁 Script executed:
cd src/components/stack && sed -n '260,285p' CategoryArticle.tsxRepository: 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.tsxRepository: TanStack/tanstack.com
Length of output: 2514
🏁 Script executed:
cd src/components/stack && sed -n '1,250p' CategoryArticle.tsxRepository: TanStack/tanstack.com
Length of output: 8737
🏁 Script executed:
cd src/utils && find . -name "*blog*" -type fRepository: TanStack/tanstack.com
Length of output: 95
🏁 Script executed:
cd src/utils && grep -n "getPostsForLibrary" blog.ts blog.functions.tsRepository: TanStack/tanstack.com
Length of output: 143
🏁 Script executed:
cd src/utils && cat blog.tsRepository: TanStack/tanstack.com
Length of output: 2338
🏁 Script executed:
cd src && find . -name "*categories*" -o -name "*stack*" | head -20Repository: TanStack/tanstack.com
Length of output: 938
🏁 Script executed:
cd src/components/stack && ls -laRepository: TanStack/tanstack.com
Length of output: 323
🏁 Script executed:
find src -name "stack-categories*" -type fRepository: TanStack/tanstack.com
Length of output: 106
🏁 Script executed:
cd src/components/stack && cat stack-categories.tsRepository: TanStack/tanstack.com
Length of output: 3659
🏁 Script executed:
cd content && find . -name "*.md" -type f | head -5 | xargs head -20Repository: TanStack/tanstack.com
Length of output: 123
🏁 Script executed:
cd content && grep -r "library:" . | head -10Repository: TanStack/tanstack.com
Length of output: 123
🏁 Script executed:
find . -type d -name "blog" | head -5Repository: TanStack/tanstack.com
Length of output: 76
🏁 Script executed:
ls -la src/blog | head -20Repository: TanStack/tanstack.com
Length of output: 1552
🏁 Script executed:
cd src/blog && head -30 announcing-tanstack-query-v5.mdRepository: TanStack/tanstack.com
Length of output: 3028
🏁 Script executed:
cd src/blog && head -30 announcing-tanstack-form-v1.mdRepository: TanStack/tanstack.com
Length of output: 895
🏁 Script executed:
cd src/blog && grep -E "^library:" *.md | head -20Repository: 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.mdRepository: 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 -10Repository: TanStack/tanstack.com
Length of output: 191
🏁 Script executed:
cd src && rg "librariesByGroup" -A 50 | head -100Repository: TanStack/tanstack.com
Length of output: 5708
🏁 Script executed:
cd src && cat libraries/libraries.ts | head -100Repository: 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.
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
frameworklibrary group — Start and Router move out of Data & State into their own framework-tier category./stack/$categorylanding 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.Dropdown.What is NOT in this PR (from #931)
Test plan
pnpm test(tsc + lint) — 0 errors, only pre-existing warnings in shop/users componentspnpm test:smoke— 10/10 (home, blog, ethos, query/router/table docs, OG images)/,/stack/framework,/stack/state,/stack/ui,/stack/performance,/stack/toolingall return 200; unknown slug returns 404Summary by CodeRabbit
/stack/$category