fix: episode cards navigate to season detail + anchor (API SeasonId on browse items) (fixes #220)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m23s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m31s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped

Episode cards in the SPA search and media-browse screens were inert (mediaDetailPath had no
Episode case, and LibraryBrowseItemResponseModel carried no parent-season id to route with).

- API: add nullable SeasonId to LibraryBrowseItemResponseModel; populate it in
  LibraryBrowseItemMapper.GetEpisodes (the single shared hydration site used by both the
  library-browse search/browse handler and the season episode drill-in), leave it null for
  every other kind. Regenerated v1.json + v1.d.ts per docs/api-conventions.md §5.
- SPA: mediaDetailPath now routes Episode items with a seasonId to
  /app/media/seasons/{seasonId}#episode-{id} (matching Blazor's Search.razor:241 link), null
  otherwise. MediaPosterCard accepts an id/highlighted pair; SeasonDetailScreen's episode grid
  gives each card a stable `episode-{id}` anchor and scrolls/highlights it on mount and on
  hashchange (deep-link support).
- Tests: GetLibraryBrowseItemsHandlerTests asserts SeasonId is populated for episode drill-in
  results and null for other kinds; web tests cover mediaDetailPath's episode cases and the
  anchor/scroll/highlight behavior (jsdom scrollIntoView stub).
- Docs: blazor-route-parity.md's episode-browse row and the Search cluster verdict updated —
  the standalone SPA episode browse exists and episode cards now navigate, closing the
  adversarial-reviewer#18 finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 23:26:08 +02:00
co-authored by Claude Fable 5
parent 8b77d5e739
commit 7129d9c5b5
12 changed files with 166 additions and 6 deletions
+16
View File
@@ -2529,6 +2529,22 @@ body {
box-shadow: 0 0 0 1px var(--action-primary);
}
/* Deep-link target highlight (#220), e.g. `#episode-{id}` from search/browse episode cards. */
.ctv-media-card-highlighted {
border-color: var(--action-primary);
box-shadow: 0 0 0 2px var(--action-primary);
animation: ctv-media-card-highlight-fade 2400ms ease-out 1;
}
@keyframes ctv-media-card-highlight-fade {
0% {
box-shadow: 0 0 0 4px var(--action-primary);
}
100% {
box-shadow: 0 0 0 2px var(--action-primary);
}
}
.ctv-media-card-poster {
position: relative;
overflow: hidden;