feat(home): editorial homepage, sticky top nav, and category drill-down#931
feat(home): editorial homepage, sticky top nav, and category drill-down#931jhislop-design wants to merge 1 commit into
Conversation
Reframes the home and library-browse surfaces as an editorial review
site (Tom's Hardware / Top 10 Reviews vibe) where every block is a
teaser into a deeper page rather than a single tall landing.
New surfaces
- src/components/home/HomeEditorial.tsx: featured Start hero with
embedded application starter, top-libraries leaderboard side rail,
trust pillars, Trusted-By marquee, live OSS Stats deferred section,
four "Top picks by category" cards, latest writing, tiered partners
(Gold/Silver/Bronze), Discord + YouTube community pair.
- src/components/editorial/EditorialTopNav.tsx: sticky global top nav
(eyebrow strip + brand + 11-item primary nav + search + compact 2x3
social cluster + theme + mobile drawer) and a pinned Gold Partners
strip with "Sponsored" hover tooltips.
- src/components/stack/* + src/routes/stack.\$category.tsx: four
buyer's-guide-style category articles
(/stack/{state,ui,performance,tooling}) with hero, Top Pick, quick
verdict table, ranked list, criteria block, related writing and a
sticky right-rail TOC + cross-category compare.
Global shell
- src/routes/__root.tsx: render EditorialTopNav on every page; non-
editorial pages still wrap children with <Navbar hideHeader> so the
contextual library left rail is preserved on docs pages.
- src/components/Navbar.tsx: add hideHeader prop. When true the global
Navbar skips its own fixed top header and lets EditorialTopNav own
--navbar-height (published by EditorialTopNav via ResizeObserver).
Page opt-ins
- /, /stack/\$category, /libraries, /libraries/\$framework, /partners,
/blog, /showcase + children, /stats/npm, /merch all set
staticData.showNavbar: false to drop the library left rail and use
the editorial top-nav-only layout.
Data and dev DX
- src/utils/partners.tsx: promote Railway from bronze -> gold.
- src/utils/documents.server.ts: when DATABASE_URL is unset (dev
without Postgres), fall back from getCachedGitHubTextFile to the
in-memory fetchCached path so library docs pages render locally
without a DB.
Notes for reviewers
- Removed deferred home sections (HomeSocialProofSection,
HomeCommunitySection, HomeBytesSection, standalone
HomeApplicationStarter usage) are kept as components -- only their
homepage render is gone.
- routeTree.gen.ts is regenerated by the build; included for
completeness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/editorial/EditorialTopNav.tsx`:
- Around line 124-131: The Search button in EditorialTopNav is rendered as an
active control but lacks any click handler; either wire it to the search action
(e.g., call the existing openSearch / onOpenSearch / toggleSearchModal prop or
dispatch a search-open action from EditorialTopNav) or mark it non-interactive
by adding the disabled attribute and adjusting aria-disabled/title to reflect
unavailability (or hide it). Locate the button element in EditorialTopNav (the
one with aria-label="Search (⌘K)" and <Search /> icon) and either attach the
proper onClick handler that opens the search UI or change its attributes/classes
to disabled/hidden and update accessible labels accordingly.
In `@src/components/stack/CategoryArticle.tsx`:
- Line 129: The section element in CategoryArticle.tsx uses id={library.id} in
two places, causing duplicate IDs; update both occurrences to produce unique IDs
(for example append a stable suffix such as the library index, type, or role) so
each section id is distinct — e.g. replace id={library.id} with something like
id={`${library.id}-${index}`} or id={`${library.id}-editors`} in the
editor's-pick render path and any other place that reused library.id, and ensure
any internal anchor links/refs that relied on library.id are updated to match
the new unique id format.
- Around line 32-34: relatedPosts can contain the same post multiple times (from
different libraries) causing duplicate entries and key collisions with
post.slug; dedupe the array returned from libraries.flatMap by post identifier
(e.g., post.id or post.slug) before calling slice(0, 4) so you only pick unique
posts, then render those; also update the list key usage (where post.slug is
used) to a truly unique key if you intentionally render duplicates (e.g.,
`${post.slug}-${lib.id}`) or just use post.id after deduping to avoid
collisions.
In `@src/routes/__root.tsx`:
- Line 235: The Navbar is always rendered with hideHeader enabled which hides
the header/menu trigger; change the prop so it reflects the hideNavbar flag
instead of always being true — replace the current JSX that renders <Navbar
hideHeader>{children}</Navbar> with <Navbar
hideHeader={hideNavbar}>{children}</Navbar> (or simply
<Navbar>{children}</Navbar> if you want the header shown when hideNavbar is
false) so the Navbar header/menu trigger remains available when appropriate;
update the expression around hideNavbar/children accordingly.
In `@src/utils/documents.server.ts`:
- Around line 412-421: The dev-only in-memory fallback is being triggered
whenever DATABASE_URL is unset, bypassing the InvalidCacheKeyError guard; change
the condition so the fallback runs only in development (e.g.,
process.env.NODE_ENV === 'development') and/or a dedicated dev flag, and keep
the InvalidCacheKeyError guard in place: update the block that calls
fetchCached({ key, ttl, fn: () => fetchRepoFileFromOrigin(repoPair, ref,
filepath) }) to run only when in dev, and ensure any InvalidCacheKeyError thrown
during cache/key validation (the existing InvalidCacheKeyError path) still
bubbles or is handled before falling back to fetchRepoFileFromOrigin.
🪄 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: ccb3d127-13be-4ac8-bc61-86347e5eb31a
📒 Files selected for processing (21)
src/components/Navbar.tsxsrc/components/editorial/EditorialTopNav.tsxsrc/components/home/HomeEditorial.tsxsrc/components/stack/CategoryArticle.tsxsrc/components/stack/stack-categories.tssrc/routeTree.gen.tssrc/routes/__root.tsxsrc/routes/blog.tsxsrc/routes/index.tsxsrc/routes/libraries.tsxsrc/routes/libraries_.$framework.tsxsrc/routes/merch.tsxsrc/routes/partners.tsxsrc/routes/showcase/$id.tsxsrc/routes/showcase/edit.$id.tsxsrc/routes/showcase/index.tsxsrc/routes/showcase/submit.tsxsrc/routes/stack.$category.tsxsrc/routes/stats/npm/index.tsxsrc/utils/documents.server.tssrc/utils/partners.tsx
| <button | ||
| type="button" | ||
| aria-label="Search (⌘K)" | ||
| title="Search · ⌘K" | ||
| className="hidden h-9 w-9 items-center justify-center rounded-md text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900 sm:flex dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white" | ||
| > | ||
| <Search size={16} /> | ||
| </button> |
There was a problem hiding this comment.
Make the search control functional or explicitly disabled.
Line 124 renders an active button with a search affordance/title, but it has no click behavior. This creates a broken primary-nav action for users.
Suggested direction
- <button
+ <button
type="button"
aria-label="Search (⌘K)"
title="Search · ⌘K"
+ onClick={openSearch} // wire existing command palette/search entry point
className="hidden h-9 w-9 items-center justify-center rounded-md text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900 sm:flex dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
>
<Search size={16} />
</button>If search is not ready yet, render it disabled/hidden instead of interactive.
🤖 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/editorial/EditorialTopNav.tsx` around lines 124 - 131, The
Search button in EditorialTopNav is rendered as an active control but lacks any
click handler; either wire it to the search action (e.g., call the existing
openSearch / onOpenSearch / toggleSearchModal prop or dispatch a search-open
action from EditorialTopNav) or mark it non-interactive by adding the disabled
attribute and adjusting aria-disabled/title to reflect unavailability (or hide
it). Locate the button element in EditorialTopNav (the one with
aria-label="Search (⌘K)" and <Search /> icon) and either attach the proper
onClick handler that opens the search UI or change its attributes/classes to
disabled/hidden and update accessible labels accordingly.
| const relatedPosts = libraries | ||
| .flatMap((lib) => getPostsForLibrary(lib.id).map((p) => ({ post: p, lib }))) | ||
| .slice(0, 4) |
There was a problem hiding this comment.
De-duplicate related posts before slicing/rendering.
Lines 32-34 can include the same blog post multiple times (across multiple libraries), and Line 419 then reuses post.slug keys, which can collide.
Minimal fix
- const relatedPosts = libraries
- .flatMap((lib) => getPostsForLibrary(lib.id).map((p) => ({ post: p, lib })))
- .slice(0, 4)
+ const relatedPosts = Array.from(
+ new Map(
+ libraries
+ .flatMap((lib) =>
+ getPostsForLibrary(lib.id).map((p) => ({ post: p, lib })),
+ )
+ .map((item) => [item.post.slug, item] as const),
+ ).values(),
+ ).slice(0, 4)Also applies to: 417-419
🤖 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 32 - 34, relatedPosts
can contain the same post multiple times (from different libraries) causing
duplicate entries and key collisions with post.slug; dedupe the array returned
from libraries.flatMap by post identifier (e.g., post.id or post.slug) before
calling slice(0, 4) so you only pick unique posts, then render those; also
update the list key usage (where post.slug is used) to a truly unique key if you
intentionally render duplicates (e.g., `${post.slug}-${lib.id}`) or just use
post.id after deduping to avoid collisions.
| accent: { from: string; to: string } | ||
| }) { | ||
| return ( | ||
| <section id={library.id} className="scroll-mt-6"> |
There was a problem hiding this comment.
Avoid duplicate element IDs for the editor’s pick library.
Line 129 and Line 297 can produce the same id, which makes in-page anchors ambiguous and violates unique-id constraints.
Minimal fix
- <section id={library.id} className="scroll-mt-6">
+ <section id={`top-pick-${library.id}`} className="scroll-mt-6">Also applies to: 297-297
🤖 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 129, The section element in
CategoryArticle.tsx uses id={library.id} in two places, causing duplicate IDs;
update both occurrences to produce unique IDs (for example append a stable
suffix such as the library index, type, or role) so each section id is distinct
— e.g. replace id={library.id} with something like id={`${library.id}-${index}`}
or id={`${library.id}-editors`} in the editor's-pick render path and any other
place that reused library.id, and ensure any internal anchor links/refs that
relied on library.id are updated to match the new unique id format.
| <PageViewTracker /> | ||
| {hideNavbar ? children : <Navbar>{children}</Navbar>} | ||
| <EditorialTopNav /> | ||
| {hideNavbar ? children : <Navbar hideHeader>{children}</Navbar>} |
There was a problem hiding this comment.
hideHeader is always enabled here, which can leave left-rail navigation inaccessible.
On Line 235, hideHeader is passed as a boolean shorthand, so it is always true whenever Navbar renders. That removes the Navbar header/menu trigger, but the sidebar still depends on that trigger on non-inline layouts.
Suggested fix
- {hideNavbar ? children : <Navbar hideHeader>{children}</Navbar>}
+ {hideNavbar ? children : <Navbar hideHeader={hideNavbar}>{children}</Navbar>}🤖 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/routes/__root.tsx` at line 235, The Navbar is always rendered with
hideHeader enabled which hides the header/menu trigger; change the prop so it
reflects the hideNavbar flag instead of always being true — replace the current
JSX that renders <Navbar hideHeader>{children}</Navbar> with <Navbar
hideHeader={hideNavbar}>{children}</Navbar> (or simply
<Navbar>{children}</Navbar> if you want the header shown when hideNavbar is
false) so the Navbar header/menu trigger remains available when appropriate;
update the expression around hideNavbar/children accordingly.
There was a problem hiding this comment.
Dev fallback is currently active in all environments and skips invalid-path protection.
Line 415 enables this path whenever DATABASE_URL is missing, not just in development, and it bypasses the InvalidCacheKeyError guard path. That can mask prod misconfig and allow malformed paths to hit origin fetches.
Suggested patch
- if (!process.env.DATABASE_URL) {
+ if (process.env.NODE_ENV === 'development' && !process.env.DATABASE_URL) {
+ if (!isValidFilepath(filepath)) {
+ return null
+ }
return fetchCached({
key,
ttl: 60_000,
fn: () => fetchRepoFileFromOrigin(repoPair, ref, filepath),
})
}🤖 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/documents.server.ts` around lines 412 - 421, The dev-only in-memory
fallback is being triggered whenever DATABASE_URL is unset, bypassing the
InvalidCacheKeyError guard; change the condition so the fallback runs only in
development (e.g., process.env.NODE_ENV === 'development') and/or a dedicated
dev flag, and keep the InvalidCacheKeyError guard in place: update the block
that calls fetchCached({ key, ttl, fn: () => fetchRepoFileFromOrigin(repoPair,
ref, filepath) }) to run only when in dev, and ensure any InvalidCacheKeyError
thrown during cache/key validation (the existing InvalidCacheKeyError path)
still bubbles or is handled before falling back to fetchRepoFileFromOrigin.
…ed social dropdown (#935) * feat(home): browse-by-category cards, /stack/$category pages, stacked 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. * style: oxfmt tidy on Navbar and index * Fix docs webhook invalidation * Add friendly URL-aware 404 page

Summary
Reframes the TanStack home and library-browse surfaces as an editorial review-site (Tom's Hardware / Top 10 Reviews) where every block is a teaser into a deeper page rather than one tall landing page. The contextual library left rail on docs pages is preserved.
What's new
Editorial homepage (
/)HomeStatsSectiondeferred-loaded with editorial framingSticky editorial top nav (sitewide)
Sponsoredtooltip on hover (`rel="sponsored"` for SEO honesty)Category drill-down (`/stack/{state,ui,performance,tooling}`)
Pages that drop the left rail and use the editorial top nav
`/`, `/stack/$category`, `/libraries`, `/libraries/$framework`, `/partners`, `/blog`, `/showcase` (+ children), `/stats/npm`, `/merch`
Heads-up changes for maintainers
Test plan
Open questions for @tannerlinsley
🤖 Generated with Claude Code
Summary by CodeRabbit