Skip to content

Add video support#47

Open
RobbieTheWagner wants to merge 1 commit into
mainfrom
video-support
Open

Add video support#47
RobbieTheWagner wants to merge 1 commit into
mainfrom
video-support

Conversation

@RobbieTheWagner

@RobbieTheWagner RobbieTheWagner commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added a refreshed mobile navigation with a slide-over menu, sticky app bar, and improved CTAs.
    • Podcast episodes can now support both audio and video playback, including a watch/listen switch and expandable video theater.
    • Navigation and CTA labels can now be customized in the site configuration.
  • Bug Fixes

    • Improved playback state handling so episode artwork and video sources stay in sync when switching episodes.
    • Added reduced-motion support for menu animations and better accessible menu behavior.

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
starpod Ready Ready Preview, Comment Jun 24, 2026 12:40pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds HLS video playback by parsing podcast:alternateEnclosure entries from raw RSS XML via fast-xml-parser. Replaces the audio-only Player with a persistent <video> element backed optionally by hls.js. Introduces EpisodeCta, a unified play/watch CTA component. Adds MobileNav with a focus-trapped slide-over. Reshapes Layout.astro into a sticky navigation rail with CTA resolution. Ships DESIGN.md, PRODUCT.md, and .impeccable design artifacts.

Changes

Starpod video playback, sticky rail, and mobile navigation

Layer / File(s) Summary
Episode video field and HLS RSS parsing
package.json, src/lib/rss.ts
Adds fast-xml-parser and hls.js dependencies. Extends Episode with an optional video field. Introduces getEpisodeVideos() to extract podcast:alternateEnclosure HLS sources from raw XML and hydrates each episode with the result.
State signals, CTA config, and nav links
src/components/state.ts, src/utils/config.ts, src/lib/cta.ts, src/lib/navLinks.ts
Expands currentEpisode signal type with video and episodeImage. Adds mediaMode, isTheaterOpen, and isMenuOpen signals. Adds optional cta to StarpodConfig. Introduces ResolvedCta type and resolveCta() with platform-priority fallback. Adds navLinks constant.
Unified video/audio Player with hls.js
src/components/Player.tsx, src/components/player/..., tests/unit/Player.test.tsx
Replaces audio-only Player with a single persistent <video> element. Lazily loads hls.js when native HLS is unavailable. Adds audio/video toggle and theater controls. Widens all sub-component audioPlayer prop types to HTMLMediaElement. Updates PlayButton to use .play-affordance. Adds video-specific Player unit tests.
EpisodeCta component and episode page integration
src/components/EpisodeCta.tsx, src/components/EpisodeList.astro, src/components/FullPlayButton.tsx, src/pages/[episode].astro
Introduces EpisodeCta with round/pill variants updating currentEpisode, mediaMode, and isTheaterOpen on click. Propagates video and episodeImage through EpisodeList and FullPlayButton. Episode page renders conditional video + audio CTAs with a cover overlay.
MobileNav, Layout wiring, and CSS animations
src/components/MobileNav.tsx, src/layouts/Layout.astro, src/styles/global.css, .impeccable/live/config.json
Adds MobileNav with a focus-trapped slide-over (Escape/scrim dismiss, body scroll lock, focus restoration). Rewires Layout.astro to render MobileNav, a sticky CTA rail using resolveCta/navLinks, and removes the mobile-only footer and InfoCard. Adds .play-affordance, .menu-panel, .menu-scrim CSS with keyframes and a prefers-reduced-motion override.
Design system and product documentation
DESIGN.md, PRODUCT.md, .impeccable/briefs/..., .impeccable/design.json
Adds DESIGN.md with design tokens, component guidance, and do/don't rules. Adds PRODUCT.md with audience definitions, principles, and WCAG 2.2 AA baseline. Adds the reshape feature brief and a machine-readable design system JSON.

Sequence Diagram

sequenceDiagram
  participant User
  participant EpisodeCta
  participant Signals as State Signals
  participant Player
  participant getEpisodeVideos
  participant HLSorNative as hls.js / native video

  User->>EpisodeCta: click Watch/Play
  EpisodeCta->>Signals: set currentEpisode, mediaMode, isPlaying, isTheaterOpen
  Signals-->>Player: reactive update
  Player->>getEpisodeVideos: episode.video.src available?
  alt video mode, no native HLS
    Player->>HLSorNative: lazy import hls.js, loadSource(src), attachMedia(videoEl)
  else native or audio
    Player->>HLSorNative: videoEl.src = src
  end
  HLSorNative-->>Player: loadedmetadata
  Player->>HLSorNative: currentTime = resume pos, play()
  HLSorNative-->>Signals: timeupdate → currentTime, play/pause → isPlaying
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 A rabbit hops the sticky rail,
With video and audio both in tail,
The slide-over opens, focus trapped neat,
HLS streams and hls.js meet,
One <video> rules them all — how sweet! 🎙️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% 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 accurately captures the main change: the app now supports video playback and related UI/navigation updates.
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 video-support

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/FullPlayButton.tsx (1)

59-68: 🎯 Functional Correctness | 🟡 Minor

Set playback mode explicitly in FullPlayButton
currentEpisode is updated here, but mediaMode and isTheaterOpen are left as-is. Since mediaMode defaults to 'video', list play on an episode with video will start video playback while the theater stays closed, and later clicks inherit whatever mode a previous CTA left behind. Set mediaMode.value here (and open the theater for video, or force 'audio' if this button should stay audio-only).

🤖 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/FullPlayButton.tsx` around lines 59 - 68, `FullPlayButton`
updates `currentEpisode` and `isPlaying`, but leaves `mediaMode` and
`isTheaterOpen` stale, so playback can inherit the previous CTA state. In the
`FullPlayButton` click flow where `currentEpisode.value` is assigned, explicitly
set `mediaMode.value` to the intended mode for this button and update
`isTheaterOpen.value` accordingly when starting video playback, or force audio
mode if this action should remain audio-only. Keep the behavior localized to
this handler so future clicks always start from a predictable state.
🧹 Nitpick comments (3)
src/components/EpisodeCta.tsx (1)

100-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor consistency nits.

  • Line 100: becomingCurrent ? true : false is just becomingCurrent.
  • Line 116 casts the icon as JSX.Element but the pill variant (Line 126) does not. Align them or drop the cast.

Also applies to: 116-116, 126-126

🤖 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/EpisodeCta.tsx` at line 100, Simplify the conditional
assignment in EpisodeCta so isPlaying.value uses becomingCurrent directly
instead of a ternary that returns true/false. Also make the icon rendering
consistent between the two branches by either removing the unnecessary
JSX.Element cast on the icon in the current branch or adding the same cast in
the pill variant branch, using the existing render logic around EpisodeCta and
its icon/pill JSX.
src/pages/[episode].astro (1)

170-183: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

CTA wiring looks correct; consider extracting the repeated episode literal.

The same episode={{ audio, video, episodeImage, episodeNumber, id, title }} object is duplicated four times. Hoisting it into a single frontmatter const (e.g. const ctaEpisode = {...}) would remove the repetition and keep the field list in sync.

Also applies to: 204-247

🤖 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/pages/`[episode].astro around lines 170 - 183, The EpisodeCta usage is
repeating the same episode object shape in multiple places, so hoist that
literal into a single frontmatter constant in [episode].astro and reuse it
wherever EpisodeCta is rendered. Update the repeated EpisodeCta calls to
reference the shared object (using the existing audio, video, episodeImage,
episodeNumber, id, and title fields) so the field list stays synchronized across
all instances.
src/lib/rss.ts (1)

101-104: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

HLS type match is case-sensitive.

alt['@_type'] === HLS_TYPE requires an exact application/x-mpegURL. Feeds commonly use application/x-mpegurl (lowercase) or application/vnd.apple.mpegurl, which would be missed. A case-insensitive compare is more robust.

♻️ Proposed change
     const hls = alternates.find(
-      (alt: Record<string, unknown>) => alt['`@_type`'] === HLS_TYPE
+      (alt: Record<string, unknown>) =>
+        String(alt['`@_type`']).toLowerCase() === HLS_TYPE.toLowerCase()
     );
🤖 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/lib/rss.ts` around lines 101 - 104, The HLS alternate lookup in rss.ts is
too strict because the @'_type' comparison in the alternates.find logic only
matches one exact case/form. Update the HLS_TYPE matching in the relevant rss
parsing flow to do a case-insensitive comparison and accept common variants like
application/x-mpegurl and application/vnd.apple.mpegurl, while keeping the
existing hls selection behavior intact.
🤖 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 @.impeccable/briefs/reshape-sticky-rail.md:
- Around line 145-147: The config-validation guidance is inconsistent with the
repository standard: update the `defineStarpodConfig` guidance for the new `cta`
field to recommend Valibot-based validation instead of an
identity-function/default skip. Keep the instructions aligned with the existing
config pattern in `defineStarpodConfig` and `starpod.config.ts`, and remove any
wording that tells implementers not to introduce Valibot in this pass.

In `@DESIGN.md`:
- Around line 295-300: The mobile navigation docs are out of sync with the
shipped layout behavior: the identity rail no longer moves its content into a
footer on mobile, and the architecture uses a mobile slide-over nav instead.
Update the relevant design text around the Identity rail and nav behavior to
describe the actual mobile slide-over pattern and remove any mention of a
mobile-only footer so future changes reference the correct structure.
- Around line 281-283: The focus styling guidance currently tells readers to use
!outline-none, but for Tailwind v4 the intended utility is outline-hidden when
hiding the default outline while preserving forced-colors behavior. Update the
focus indicator guidance in DESIGN.md to reference outline-hidden instead of
!outline-none, keeping the rest of the border-focused behavior described around
the focus state intact.

In `@src/components/Player.tsx`:
- Around line 28-30: The Player component has a Rules-of-Hooks issue because it
returns early when currentEpisode.value is null before its useEffect hooks run,
which changes hook order between renders. Move the guard below the hooks in
Player and keep the hooks unconditional, then add the null check inside each
useEffect body (or otherwise guard the effect logic) so the hook order stays
stable.

In `@src/lib/navLinks.ts`:
- Around line 15-20: The navLinks constant includes a show-specific hardcoded
Store URL, which makes the reusable template ship an чужд/incorrect link for
adopters. Update navLinks in navLinks.ts to remove that show-specific entry or
gate it behind configuration, and review the related sponsor link in the same
array to ensure only template-safe defaults remain.

---

Outside diff comments:
In `@src/components/FullPlayButton.tsx`:
- Around line 59-68: `FullPlayButton` updates `currentEpisode` and `isPlaying`,
but leaves `mediaMode` and `isTheaterOpen` stale, so playback can inherit the
previous CTA state. In the `FullPlayButton` click flow where
`currentEpisode.value` is assigned, explicitly set `mediaMode.value` to the
intended mode for this button and update `isTheaterOpen.value` accordingly when
starting video playback, or force audio mode if this action should remain
audio-only. Keep the behavior localized to this handler so future clicks always
start from a predictable state.

---

Nitpick comments:
In `@src/components/EpisodeCta.tsx`:
- Line 100: Simplify the conditional assignment in EpisodeCta so isPlaying.value
uses becomingCurrent directly instead of a ternary that returns true/false. Also
make the icon rendering consistent between the two branches by either removing
the unnecessary JSX.Element cast on the icon in the current branch or adding the
same cast in the pill variant branch, using the existing render logic around
EpisodeCta and its icon/pill JSX.

In `@src/lib/rss.ts`:
- Around line 101-104: The HLS alternate lookup in rss.ts is too strict because
the @'_type' comparison in the alternates.find logic only matches one exact
case/form. Update the HLS_TYPE matching in the relevant rss parsing flow to do a
case-insensitive comparison and accept common variants like
application/x-mpegurl and application/vnd.apple.mpegurl, while keeping the
existing hls selection behavior intact.

In `@src/pages/`[episode].astro:
- Around line 170-183: The EpisodeCta usage is repeating the same episode object
shape in multiple places, so hoist that literal into a single frontmatter
constant in [episode].astro and reuse it wherever EpisodeCta is rendered. Update
the repeated EpisodeCta calls to reference the shared object (using the existing
audio, video, episodeImage, episodeNumber, id, and title fields) so the field
list stays synchronized across all instances.
🪄 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: af3b7ae5-a36b-496e-a50d-cce525b343ab

📥 Commits

Reviewing files that changed from the base of the PR and between 8c0de9f and 6e4862f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • .impeccable/briefs/reshape-sticky-rail.md
  • .impeccable/design.json
  • .impeccable/live/config.json
  • DESIGN.md
  • PRODUCT.md
  • package.json
  • src/components/EpisodeCta.tsx
  • src/components/EpisodeList.astro
  • src/components/FullPlayButton.tsx
  • src/components/MobileNav.tsx
  • src/components/Player.tsx
  • src/components/player/ForwardButton/index.tsx
  • src/components/player/PlayButton.tsx
  • src/components/player/PlaybackRateButton.tsx
  • src/components/player/RewindButton/index.tsx
  • src/components/player/Slider/index.tsx
  • src/components/state.ts
  • src/layouts/Layout.astro
  • src/lib/cta.ts
  • src/lib/navLinks.ts
  • src/lib/rss.ts
  • src/pages/[episode].astro
  • src/styles/global.css
  • src/utils/config.ts
  • tests/unit/Player.test.tsx

Comment on lines +145 to +147
1. **Config validation:** `defineStarpodConfig` is currently an identity function
(no Valibot), despite CLAUDE.md. **Default:** keep the identity-function pattern
for the new `cta` field; don't introduce Valibot in this pass.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Align the config-validation default with repository standards.

Line 145-147 currently recommends skipping Valibot, which conflicts with the project rule and creates contradictory implementation guidance.

As per coding guidelines, src/utils/config.ts: "Use Valibot for configuration validation in starpod.config.ts."

🤖 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 @.impeccable/briefs/reshape-sticky-rail.md around lines 145 - 147, The
config-validation guidance is inconsistent with the repository standard: update
the `defineStarpodConfig` guidance for the new `cta` field to recommend
Valibot-based validation instead of an identity-function/default skip. Keep the
instructions aligned with the existing config pattern in `defineStarpodConfig`
and `starpod.config.ts`, and remove any wording that tells implementers not to
introduce Valibot in this pass.

Source: Coding guidelines

Comment thread DESIGN.md
Comment on lines +281 to +283
- **Focus:** Border color shifts to the focused hairline tone; **ring and outline
are explicitly suppressed** (`!ring-0`, `!outline-none`) — the border shift is
the entire focus signal. Maintain a visible, AA-contrast focus indicator when

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section in DESIGN.md
git ls-files DESIGN.md
wc -l DESIGN.md
sed -n '270,305p' DESIGN.md

Repository: shipshapecode/starpod

Length of output: 2067


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section in DESIGN.md
git ls-files DESIGN.md
wc -l DESIGN.md
sed -n '270,305p' DESIGN.md

Repository: shipshapecode/starpod

Length of output: 2067


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate Tailwind-related version/config information
git ls-files | rg '(^|/)(package.json|pnpm-lock.yaml|package-lock.json|yarn.lock|tailwind\\.config\\.|tailwind\\.css|postcss\\.config\\.)$'
rg -n '"tailwindcss"|tailwindcss@|outline-hidden|outline-none' package.json pnpm-lock.yaml package-lock.json yarn.lock tailwind.config.* postcss.config.* DESIGN.md 2>/dev/null || true

Repository: shipshapecode/starpod

Length of output: 839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate Tailwind-related version/config information
git ls-files | rg '(^|/)(package.json|pnpm-lock.yaml|package-lock.json|yarn.lock|tailwind\\.config\\.|tailwind\\.css|postcss\\.config\\.)$'
rg -n '"tailwindcss"|tailwindcss@|outline-hidden|outline-none' package.json pnpm-lock.yaml package-lock.json yarn.lock tailwind.config.* postcss.config.* DESIGN.md 2>/dev/null || true

Repository: shipshapecode/starpod

Length of output: 839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate Tailwind-related version/config information
git ls-files | rg '(^|/)(package.json|pnpm-lock.yaml|package-lock.json|yarn.lock|tailwind\\.config\\.|tailwind\\.css|postcss\\.config\\.)$'
rg -n '"tailwindcss"|tailwindcss@|outline-hidden|outline-none' package.json pnpm-lock.yaml package-lock.json yarn.lock tailwind.config.* postcss.config.* DESIGN.md 2>/dev/null || true

Repository: shipshapecode/starpod

Length of output: 839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find Tailwind version/config declarations
git ls-files | rg '(^|/)(package.json|pnpm-lock.yaml|package-lock.json|yarn.lock|tailwind\\.config\\.|postcss\\.config\\.)$'
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  [ -f "$f" ] && { echo "== $f =="; rg -n '"tailwindcss"|tailwindcss@|outline-hidden|outline-none' "$f" || true; }
done

Repository: shipshapecode/starpod

Length of output: 651


🌐 Web query:

Tailwind CSS v4 outline-none outline-hidden docs

💡 Result:

In Tailwind CSS v4, the outline-none and outline-hidden utilities serve distinct purposes for managing browser focus styles [1][2]. outline-none In v4, this utility now sets the CSS outline-style property to none [3][2]. This is a departure from Tailwind CSS v3, where outline-none was implemented as a 2px solid transparent outline with a 2px offset to maintain visibility for users in forced-colors modes [4][5][6]. outline-hidden This utility was introduced in v4 to provide the behavior that outline-none previously offered [3][2]. It is specifically designed to hide the default browser outline while still preserving a visible focus state for users when forced-colors mode is active [1]. Recommendation Tailwind documentation highly recommends that you apply your own custom focus styling for accessibility whenever you use either of these utilities [1]. Use outline-hidden if you need to ensure accessibility compliance for users with forced-colors settings enabled [1].

Citations:


Use outline-hidden for Tailwind v4 focus styling. !outline-none is the v4 utility that removes the outline style; if the intent is to hide the default outline while preserving forced-colors behavior, switch to outline-hidden instead.

🤖 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 `@DESIGN.md` around lines 281 - 283, The focus styling guidance currently tells
readers to use !outline-none, but for Tailwind v4 the intended utility is
outline-hidden when hiding the default outline while preserving forced-colors
behavior. Update the focus indicator guidance in DESIGN.md to reference
outline-hidden instead of !outline-none, keeping the rest of the border-focused
behavior described around the focus state intact.

Comment thread DESIGN.md
Comment on lines +295 to +300
- **Identity rail:** A fixed left column (`28rem`/`lg`, `30rem`/`xl`) holding
artwork, title, blurb, listen links, and hosts. On mobile it collapses; rail
content (hosts, info) moves into the footer.
- **Nav links (`.animated-bottom-border`):** Body type with an **underline that
grows from left to right on hover** (gradient: Iris in light, Aqua in dark,
500ms ease).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update mobile navigation docs to match shipped layout behavior.

Line 297 says rail content moves into a footer, but the current architecture uses a mobile slide-over nav and removes the mobile-only footer. This doc mismatch will mislead follow-up changes.

🤖 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 `@DESIGN.md` around lines 295 - 300, The mobile navigation docs are out of sync
with the shipped layout behavior: the identity rail no longer moves its content
into a footer on mobile, and the architecture uses a mobile slide-over nav
instead. Update the relevant design text around the Identity rail and nav
behavior to describe the actual mobile slide-over pattern and remove any mention
of a mobile-only footer so future changes reference the correct structure.

Comment thread src/components/Player.tsx
Comment on lines 28 to 30
if (currentEpisode.value === null) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline src/components/Player.tsx
git log --oneline -5 -- src/components/Player.tsx

Repository: shipshapecode/starpod

Length of output: 250


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant portion of Player.tsx with line numbers.
sed -n '1,260p' src/components/Player.tsx | nl -ba | sed -n '1,260p'

# Map the hook usage structurally, if possible, to confirm ordering.
ast-grep run --pattern $'function Player() {\n  $$$\n}' --lang tsx src/components/Player.tsx

Repository: shipshapecode/starpod

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant portion of Player.tsx with line numbers.
sed -n '1,260p' src/components/Player.tsx | nl -ba | sed -n '1,260p'

# Map the hook usage structurally, if possible, to confirm ordering.
ast-grep run --pattern $'function Player() {\n  $$$\n}' --lang tsx src/components/Player.tsx

Repository: shipshapecode/starpod

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant portion of Player.tsx with line numbers.
sed -n '1,260p' src/components/Player.tsx | nl -ba | sed -n '1,260p'

# Map the hook usage structurally, if possible, to confirm ordering.
ast-grep run --pattern $'function Player() {\n  $$$\n}' --lang tsx src/components/Player.tsx

Repository: shipshapecode/starpod

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

nl -ba src/components/Player.tsx | sed -n '1,240p'

Repository: shipshapecode/starpod

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the file with line numbers without relying on `nl`.
awk '{printf("%6d  %s\n", NR, $0)}' src/components/Player.tsx | sed -n '1,240p'

# Structural outline for hook ordering.
ast-grep outline src/components/Player.tsx --view expanded

Repository: shipshapecode/starpod

Length of output: 9987


Rules-of-Hooks violation: move the early return below the hooks. Player returns when currentEpisode.value === null before the useEffect calls, so hook order changes between the empty and loaded states. Keep the hooks unconditional and guard the effect bodies instead.

🤖 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/Player.tsx` around lines 28 - 30, The Player component has a
Rules-of-Hooks issue because it returns early when currentEpisode.value is null
before its useEffect hooks run, which changes hook order between renders. Move
the guard below the hooks in Player and keep the hooks unconditional, then add
the null check inside each useEffect body (or otherwise guard the effect logic)
so the hook order stays stable.

Comment thread src/lib/navLinks.ts
Comment on lines +15 to +20
export const navLinks: Array<NavLink> = [
{ label: 'About', href: '/about' },
{ label: 'Contact', href: '/contact' },
{ label: 'Store', href: 'https://whiskey.fund/' },
{ label: 'Become a sponsor', href: '/sponsor' }
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Show-specific URL hardcoded in a template-level constant.

Starpod is a reusable podcast template, but { label: 'Store', href: 'https://whiskey.fund/' } (and arguably /sponsor) is specific to one show. Adopters who copy this template will ship a broken/foreign Store link. Until the planned config migration noted above lands, consider dropping the show-specific entry or moving it behind config.

🤖 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/lib/navLinks.ts` around lines 15 - 20, The navLinks constant includes a
show-specific hardcoded Store URL, which makes the reusable template ship an
чужд/incorrect link for adopters. Update navLinks in navLinks.ts to remove that
show-specific entry or gate it behind configuration, and review the related
sponsor link in the same array to ensure only template-safe defaults remain.

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