diff --git a/web/src/components/ContinueWatchingCard.test.tsx b/web/src/components/ContinueWatchingCard.test.tsx
index aa081c5b..c465245e 100644
--- a/web/src/components/ContinueWatchingCard.test.tsx
+++ b/web/src/components/ContinueWatchingCard.test.tsx
@@ -176,6 +176,8 @@ describe("ContinueWatchingCard", () => {
expect(markup).toContain('href="/watch/ep-001"');
expect(markup).toContain('href="/item/ep-001"');
+ expect(markup).toContain('href="/item/series-1"');
+ expect(markup).toContain('aria-label="Play Breaking Bad"');
expect(markup).toContain("Breaking Bad");
expect(markup).toContain("Season 1 Episode 1");
expect(markup).toContain("Pilot");
@@ -183,6 +185,46 @@ describe("ContinueWatchingCard", () => {
expect(markup).toContain("More actions");
});
+ it("links the poster to the item page and reserves playback for the play button", () => {
+ const queryClient = new QueryClient();
+ const markup = renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+
+ // The poster link renders first and must navigate to the item page; the
+ // watch href is reserved for the explicit play button.
+ const posterLinkIndex = markup.indexOf('href="/item/movie-001"');
+ const playLinkIndex = markup.indexOf('href="/watch/movie-001"');
+ expect(posterLinkIndex).toBeGreaterThan(-1);
+ expect(playLinkIndex).toBeGreaterThan(-1);
+ expect(posterLinkIndex).toBeLessThan(playLinkIndex);
+ expect(markup).toContain('aria-label="Play Apex"');
+ });
+
it("routes audiobook continue cards to the audiobook detail player", () => {
const queryClient = new QueryClient();
const markup = renderToStaticMarkup(
diff --git a/web/src/components/ContinueWatchingCard.tsx b/web/src/components/ContinueWatchingCard.tsx
index 02780505..3ab59c86 100644
--- a/web/src/components/ContinueWatchingCard.tsx
+++ b/web/src/components/ContinueWatchingCard.tsx
@@ -45,6 +45,7 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
libraryId: props.libraryId,
}),
title: props.sectionItem.title,
+ seriesId: props.sectionItem.series_id,
seriesTitle: props.sectionItem.series_title,
seasonNumber: props.sectionItem.season_number,
episodeNumber: props.sectionItem.episode_number,
@@ -65,6 +66,7 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
libraryId: props.libraryId,
}),
title: props.detail.title,
+ seriesId: props.detail.series_id,
seriesTitle: props.detail.series_title,
seasonNumber: props.detail.season_number,
episodeNumber: props.detail.episode_number,
@@ -105,7 +107,14 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
card.durationSeconds > 0 ? (card.positionSeconds / card.durationSeconds) * 100 : 0;
const hasPartialProgress = progressPercent > 0 && progressPercent < 100;
const hasEpisodeMeta = card.seasonNumber != null && card.episodeNumber != null;
- const heading = hasEpisodeMeta && card.seriesTitle ? card.seriesTitle : card.title;
+ const headingIsSeries = hasEpisodeMeta && !!card.seriesTitle;
+ const heading = headingIsSeries ? card.seriesTitle : card.title;
+ // The heading shows the series title for episodes, so it should navigate to
+ // the series page; everything else heads to the item's own page.
+ const headingHref =
+ headingIsSeries && card.seriesId
+ ? buildItemHref({ contentId: card.seriesId, libraryId: props.libraryId })
+ : card.itemHref;
const episodeLabel = hasEpisodeMeta
? `Season ${card.seasonNumber} Episode ${card.episodeNumber}`
: null;
@@ -195,18 +204,14 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
return (
-
-
+
+
{imageSrc ? (

) : (
@@ -223,12 +228,8 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
/>
)}
- {/* Play overlay */}
-
+ {/* Hover dim behind the play button */}
+
{/* Progress bar */}
{!isNextUp && progressPercent > 0 && (
@@ -244,6 +245,14 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
)}
+
+
+
{/* Info */}
-
- {heading}
+
+
+ {heading}
+
{episodeMeta &&
{episodeMeta}
}
{premiereBadge && (
@@ -279,7 +293,7 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
)}
{timeLeftLabel &&
{timeLeftLabel}
}
-
+
);
}
diff --git a/web/src/components/SectionRow.test.tsx b/web/src/components/SectionRow.test.tsx
index 63778238..3380c4d3 100644
--- a/web/src/components/SectionRow.test.tsx
+++ b/web/src/components/SectionRow.test.tsx
@@ -57,6 +57,10 @@ vi.mock("@/hooks/useViewTransition", () => ({
useViewTransitionNavigate: () => mockNavigate,
}));
+vi.mock("@/playback/watchPlaybackContext", () => ({
+ useWatchPlaybackController: () => ({ startPlayback: () => {} }),
+}));
+
describe("SectionRow", () => {
beforeEach(() => {
latestCarouselProps = undefined;