Skip to content

Podcast latest season links#661

Merged
kentcdodds merged 5 commits intomainfrom
cursor/podcast-latest-season-links-62f6
Feb 22, 2026
Merged

Podcast latest season links#661
kentcdodds merged 5 commits intomainfrom
cursor/podcast-latest-season-links-62f6

Conversation

@kentcdodds
Copy link
Owner

@kentcdodds kentcdodds commented Feb 22, 2026

Update podcast navigation links to dynamically point to the latest season.

This ensures users are always directed to the most current podcast content and avoids outdated hardcoded links in the navigation.


Open in Web Open in Cursor 


Note

Medium Risk
Touches the root loader and adds server-side calls to external podcast providers (even though cached and guarded), so failures/perf regressions could affect all page loads and nav routing.

Overview
Navbar Chats/Calls links are now derived at runtime to point to the latest podcast season instead of hardcoded season routes, with mobile and desktop menus sharing this computed link set.

Root loader now fetches and exposes latestPodcastSeasonLinks, backed by a new cached server utility that queries Simplecast/Transistor, validates shape with zod, and falls back to base /chats//calls paths on failure. Active nav highlighting was adjusted via an activeTo prefix so season-specific links still show active for all /chats/* and /calls/* routes.

Written by Cursor Bugbot for commit 02aa4f6. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Navbar and mobile menu now build links dynamically and route to the latest podcast season when available
    • Improved active-route detection so navigation highlights match broader or nested routes more accurately
  • Performance

    • Latest podcast season info is fetched and cached to reduce load and improve responsiveness

@cursor
Copy link

cursor bot commented Feb 22, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Replaces static navbar link constants with a hook that derives desktop and mobile links (including optional active-route matching) from loader-provided podcast season data. Adds a server utility that fetches and caches latest podcast season numbers and surfaces them to the root loader.

Changes

Cohort / File(s) Summary
Navbar refactor
app/components/navbar.tsx
Replaces static LINKS/MOBILE_LINKS with exported useNavbarLinks and NavbarLinkItem type. NavLink accepts optional activeTo for broader route matching. MobileMenu now accepts links. Navbar reads links/mobileLinks from the hook and passes activeTo through.
Root loader update
app/root.tsx
Imports getLatestPodcastSeasonLinks, includes it in the loader's Promise.all call with a catch fallback, and adds latestPodcastSeasonLinks to returned loader data.
Podcast season utility
app/utils/podcast-latest-season.server.ts
New module exporting getLatestPodcastSeasonLinks({ request, timings }). Uses cachified, dynamic imports of provider clients, computes latest chats/calls season numbers, formats season paths, validates shape with a schema, and handles provider errors by logging and returning nulls/defaults.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant RootLoader as Root Loader
    participant Cache as Cachified Cache
    participant PodcastUtil as getLatestPodcastSeasonLinks
    participant Simplecast
    participant Transistor
    participant App as Client (Navbar)

    Browser->>RootLoader: HTTP request
    RootLoader->>Cache: read latestPodcastSeasonLinks
    alt cache miss
      Cache->>PodcastUtil: compute latest links
      PodcastUtil->>Simplecast: fetch chats metadata
      PodcastUtil->>Transistor: fetch calls metadata
      Simplecast-->>PodcastUtil: chats seasons
      Transistor-->>PodcastUtil: calls seasons
      PodcastUtil-->>Cache: store computed links
      Cache-->>RootLoader: return links
    else cache hit
      Cache-->>RootLoader: return cached links
    end
    RootLoader-->>Browser: response with latestPodcastSeasonLinks
    Browser->>App: render
    App->>App: useNavbarLinks(loaderData.latestPodcastSeasonLinks)
    App->>App: render NavLink/MobileMenu with links and activeTo
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hopped through seasons, cached and bright,

Links now stretch both left and right,
Mobile munches, NavLinks sing,
Routes align on agile spring,
I thump my foot and cheer the site.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: updating podcast navigation links to dynamically resolve to the latest season instead of hardcoded values.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/podcast-latest-season-links-62f6

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 and usage tips.

@kentcdodds kentcdodds marked this pull request as ready for review February 22, 2026 16:33
Copy link

@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.

🧹 Nitpick comments (1)
app/root.tsx (1)

111-123: Consider wrapping getLatestPodcastSeasonLinks with a fallback in the root loader.

Since this runs on every page load, an unexpected error from cachified itself (not from getFreshValue, which is already guarded) would break the entire root loader and take down the page. A defensive wrapper here would prevent that:

🛡️ Suggested defensive wrapper
-		getLatestPodcastSeasonLinks({ request, timings }),
+		getLatestPodcastSeasonLinks({ request, timings }).catch(() => null),

The inner helpers already handle fetch failures gracefully, so the risk is low — this is just an extra safety net for cache-infrastructure failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/root.tsx` around lines 111 - 123, The root loader currently awaits
getLatestPodcastSeasonLinks inside Promise.all which could throw if the
cachified/cache infra fails; wrap the call with a defensive fallback so a thrown
error doesn't break the whole loader — replace the direct call to
getLatestPodcastSeasonLinks({ request, timings }) with a wrapper that catches
errors and returns a safe default (e.g., empty links or null) so Promise.all
always resolves; reference the getLatestPodcastSeasonLinks call inside the array
passed to Promise.all and ensure the fallback shape matches what the rest of the
loader expects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/root.tsx`:
- Around line 111-123: The root loader currently awaits
getLatestPodcastSeasonLinks inside Promise.all which could throw if the
cachified/cache infra fails; wrap the call with a defensive fallback so a thrown
error doesn't break the whole loader — replace the direct call to
getLatestPodcastSeasonLinks({ request, timings }) with a wrapper that catches
errors and returns a safe default (e.g., empty links or null) so Promise.all
always resolves; reference the getLatestPodcastSeasonLinks call inside the array
passed to Promise.all and ensure the fallback shape matches what the rest of the
loader expects.

cursoragent and others added 3 commits February 22, 2026 20:18
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@cursor cursor bot force-pushed the cursor/podcast-latest-season-links-62f6 branch from 447d516 to 18155bf Compare February 22, 2026 20:18
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Copy link

@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.

🧹 Nitpick comments (1)
app/utils/podcast-latest-season.server.ts (1)

31-35: Optional: Align defensive coding with the calls version for consistency.

Both functions should either both include or both omit the ?? 0 guard on seasonNumber. While getLatestCallsSeasonNumber uses e.seasonNumber ?? 0, getLatestChatsSeasonNumber passes s.seasonNumber directly to Math.max. The Zod schemas define seasonNumber as a required z.number(), so the guard is not strictly necessary; however, aligning the implementations would improve code consistency and defensive coding practices.

♻️ Align with the calls version
- const latestSeasonNumber = seasons.reduce(
-     (max, s) => Math.max(max, s.seasonNumber),
-     0,
- )
+ const latestSeasonNumber = seasons.reduce(
+     (max, s) => Math.max(max, s.seasonNumber ?? 0),
+     0,
+ )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/utils/podcast-latest-season.server.ts` around lines 31 - 35, Align
defensive handling of undefined season numbers between
getLatestChatsSeasonNumber and getLatestCallsSeasonNumber by changing the
reducer in getLatestChatsSeasonNumber to use s.seasonNumber ?? 0 when computing
latestSeasonNumber; locate the seasons.reduce call (variable latestSeasonNumber)
and update the reducer to mirror the calls implementation so both functions
consistently guard against missing seasonNumber values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/utils/podcast-latest-season.server.ts`:
- Around line 31-35: Align defensive handling of undefined season numbers
between getLatestChatsSeasonNumber and getLatestCallsSeasonNumber by changing
the reducer in getLatestChatsSeasonNumber to use s.seasonNumber ?? 0 when
computing latestSeasonNumber; locate the seasons.reduce call (variable
latestSeasonNumber) and update the reducer to mirror the calls implementation so
both functions consistently guard against missing seasonNumber values.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Copy link

@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.

🧹 Nitpick comments (1)
app/utils/podcast-latest-season.server.ts (1)

5-14: Consider tightening latestSeasonNumber schema to integers.

z.number().nullable() accepts floats and negative values. Since season numbers are always positive integers, z.number().int().positive().nullable() would make the schema self-documenting and catch unexpected API responses earlier.

♻️ Proposed refinement
 const latestPodcastSeasonLinksSchema = z.object({
 	chats: z.object({
-		latestSeasonNumber: z.number().nullable(),
+		latestSeasonNumber: z.number().int().positive().nullable(),
 		latestSeasonPath: z.string().min(1),
 	}),
 	calls: z.object({
-		latestSeasonNumber: z.number().nullable(),
+		latestSeasonNumber: z.number().int().positive().nullable(),
 		latestSeasonPath: z.string().min(1),
 	}),
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/utils/podcast-latest-season.server.ts` around lines 5 - 14, The schema
latestPodcastSeasonLinksSchema currently allows non-integer or negative season
numbers because it uses z.number().nullable(); update both
chats.latestSeasonNumber and calls.latestSeasonNumber to
z.number().int().positive().nullable() so the validator enforces positive
integers (or null) and will fail fast on invalid API responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/utils/podcast-latest-season.server.ts`:
- Around line 5-14: The schema latestPodcastSeasonLinksSchema currently allows
non-integer or negative season numbers because it uses z.number().nullable();
update both chats.latestSeasonNumber and calls.latestSeasonNumber to
z.number().int().positive().nullable() so the validator enforces positive
integers (or null) and will fail fast on invalid API responses.

@kentcdodds kentcdodds merged commit c0b0a2e into main Feb 22, 2026
8 checks passed
@kentcdodds kentcdodds deleted the cursor/podcast-latest-season-links-62f6 branch February 22, 2026 20:42
@coderabbitai coderabbitai bot mentioned this pull request Feb 24, 2026
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.

2 participants