Show PWA source ratings on every surface through one shared rating row #26

Merged
justinzeus merged 2 commits from fm/wolfskin-25-ratings-everywhere into main 2026-10-29 21:51:24 +01:00
Owner

Closes justinzeus/wolfskin#25

Every place the TV drew a Jellyfin rating now draws the PWA's ratings (Seerr: Rotten Tomatoes critics/audience, IMDb for movies, TMDB only when nothing else has a score) through ONE row component, fed by ONE data path. The Jellyfin star/tomato drawing code is deleted.

What was wrong

  • The home focused header (and every other header built from a list item) kept drawing the Jellyfin star. PR 24 only wired movie and series details pages, and home rows are requested without ProviderIds, so a focused home item had no TMDB id to ask Seerr about.
  • Episodes (Desperate Housewives S05E01) showed the Jellyfin star because episodes were never wired and an episode's own Tmdb provider id is the episode's id, not the series'.

The design

  • One component: SourceRatingsRow (ui/components/SourceRatingsRow.kt) draws badge + number pairs in the PWA order (RT critics, RT audience, IMDb or TMDB), same drawables, same spacing, honouring the CRITIC_RATING (RT) and COMMUNITY_RATING (IMDb/TMDB) display toggles. QuickDetails is the only caller; every header gets its ratings through QuickDetails(ratings = ...) or QuickDetails(item, ...).
  • One data path: rememberSourceRatings(item) -> SeerrRatingsService.forItem/cachedFor. Which items have ratings is decided in exactly one place, SeerrRatingsService, by the PWA's rule (ratings belong to the title, never to cards or lists):
Item kind Ratings
Movie its own (ratingscombined + TMDB fallback)
Series its own (tv/{id}/ratings + TMDB fallback)
Season the SERIES' ratings (the PWA shows them on the series title page the season is part of)
Episode the series' Rotten Tomatoes critics/audience for context, plus the episode's OWN TMDB user score, labelled Ep, from Seerr's season data (see below)
Collection (box set), music, photo, TV guide program, anything else none. The PWA has no equivalent
  • Home blocker fix (two parts, one cache):
    1. ProviderIds added to the home row field lists (HomeItemFields, new HomeSlimItemFields for continue watching / next up). Small, only on home requests.
    2. SeerrRatingsService.resolveTarget finds the TMDB id for anything that arrives without it: an episode/season resolves through its seriesId (one Jellyfin getItem), a movie/series without provider ids looks itself up. Results are cached per Jellyfin id for 6 h in a second Guava cache (including "this series has no TMDB id"); failures are not cached. Ratings keep the existing type-qualified (movie|tv, tmdbId) cache with the 6 h TTL, so the 2nd..nth episode of a series needs no wait and no request.
  • Debounce / cancel: forItem emits cached ratings at once, otherwise waits 350 ms, then asks Seerr. rememberSourceRatings collects it in a LaunchedEffect(item.id), so moving focus cancels the wait or the in-flight request. A fast scroll never reaches Seerr. It also resets to the new item's cached value so the previous item's ratings are never shown.
  • Fallback (decision): the Jellyfin star/tomato fallback PR 24 kept for movie/series (Seerr off, no TMDB id, call failed) is removed. It is not dead code, but it is the thing the owner reported, it is a second way to draw a rating, and the PWA shows nothing when Seerr has nothing. Consequence to know: with Seerr not configured/active, no rating shows anywhere on the TV. Reinstating it means restoring a draw path, which I recommend against.
  • Removed: QuickDetailsData.communityRating/criticRating/criticRatingFirst, the AnnotatedString building in BaseItem.kt, the inline-content star/fresh/rotten plumbing, SourceRatings.withSourceRatings/appendRatings, StarRating, StarRatingPrecision, TomatoRating, the old preview, the per-ViewModel ratings state/collectors in MovieViewModel/SeriesViewModel (which also removes the duplicate-collector shape from the PR 24 review).
  • Kept on purpose, not media ratings: the SimpleStarRating "7+" thresholds in the rating filter menus, the subtitle-search result rating in DownloadSubtitlesDialog.kt, and the community/critic rating sort and filter (server-side Jellyfin queries).
  • Discover movie/series/discover-page headers already used Seerr; they now draw through the same row and obey the same toggles (before they ignored them). They still call SeerrRatingsService.get(type, id) directly because they start from a Seerr id, not a Jellyfin item; it is the same service and cache.

Per-surface inventory

Surface (file) Before After Why
Home focused header (HomePage.kt, HomePageHeader) Jellyfin star + threshold tomato for every kind; no TMDB id on home items so Seerr never applied movie/series: Seerr row; episode/season: series' Seerr row; other kinds: none PWA title ratings; home focus is the hero-equivalent. ProviderIds + series lookup + debounce
Library grid focused header (CollectionFolderGrid.kt) Jellyfin star/tomato by item kind as above same HomePageHeader
Collection page focused item header (CollectionDetails.kt) Jellyfin star/tomato by item kind as above same HomePageHeader
Folder list header (CollectionFolderList.kt) Jellyfin star/tomato by item kind as above QuickDetails(item)
Collection header, box set itself (CollectionDetailsHeader.kt) Jellyfin star/tomato none PWA has no collection rating
Movie page (MovieDetailsHeader.kt) Seerr (PR 24), Jellyfin fallback Seerr only fallback removed
Series page (SeriesDetails.kt) Seerr (PR 24), Jellyfin fallback Seerr only fallback removed
Episode page (EpisodeDetailsHeader.kt, the S05E01 case) Jellyfin star series' RT critics/audience + the episode's own TMDB score (TMDB Ep 8.1) PWA shows the series ratings in the episode context; the episode TMDB score was added on top at the owner's request
Series overview, focused episode (FocusedEpisodeHeader.kt), home focused episode Jellyfin star same as the episode page (season fetched once, cached after the first episode) same
Season (via home / grid headers) Jellyfin star/tomato series' Seerr ratings same
Album page (AlbumDetailsPage.kt), artist page (ArtistDetailsPage.kt) Jellyfin star/tomato none no PWA equivalent
Photo slideshow header (ImageDetailsHeader.kt) Jellyfin star/tomato none no PWA equivalent
TV guide program header (TvGuideHeader.kt) none (own text) none no PWA equivalent
Discover movie / series details, Discover page header Seerr RT/IMDb/TMDB, ignoring toggles same ratings, same row, toggles honoured one component
Cards, rows, search, person pages, requests none none PWA: no ratings on cards/lists
Rating filter menu stars, subtitle-search rating Jellyfin value as a filter threshold / subtitle score unchanged not a title's rating

Episode TMDB score (second commit)

Owner asked for each episode's own TMDB score. Episodes now show: series RT critics, series RT audience, then the TMDB badge with Ep and the episode's score (e.g. [TMDB] Ep 8.1).

  • Which reads cleanest: series Rotten Tomatoes for context plus the episode TMDB, with the series' own TMDB score dropped. That leaves exactly one TMDB number on the row and it is the episode's, labelled Ep so nobody mistakes it for the series score. (The series TMDB is only ever shown when the series has no RT score, and in that case it is replaced by the episode score.) Showing both TMDB numbers would put two identical badges side by side.
  • Source: Seerr GET /tv/{tvId}/season/{seasonNumber}, episodes' voteAverage/voteCount. The series TMDB id comes from the existing resolveTarget. The season is fetched once and cached by (tmdbId, seasonNumber) for 6 h in a third Guava cache, so every other episode of that season costs no request and no wait; failures are not cached, a 404 (TMDB has no such season) is cached as an answer. Same 350 ms debounce/cancel as everything else.
  • Rules: observed only, 0-10, one decimal; zero or missing vote count is unrated (same rule as the other TMDB values, but a missing vote count also counts as unrated here since the season endpoint always reports one). An episode missing from the season, or without season/episode numbers, shows the series ratings only. A series with no TMDB id shows nothing extra.
  • One row: RatingEntry.EpisodeTmdb goes through the same SourceRatingsRow/QuickDetails as everything else, drawn with the same TMDB drawable, and is controlled by the community rating (IMDb/TMDB) display setting while RT stays under the critic rating setting.
  • Spec: the bundled Seerr spec typed the season answer with a strict Season/Episode model (non-nullable name/overview/etc.), which would throw on a null TMDB field. The season endpoint now returns new SeasonDetails/SeasonEpisode schemas with every TMDB-optional field nullable and voteAverage a decimal, voteCount int64. The Season model used by Discover is untouched.
  • Season (not episode) headers keep showing only the series ratings.

Tests

  • make test (via docker/gradle.sh): BUILD SUCCESSFUL, 462 tests, 0 failures, 0 skipped (446 before the episode commit).
  • New/changed:
    • SourceRatingsRowTest (Robolectric compose): what QuickDetails draws for movie, series, episode, season, and collection/music/photo/program; Jellyfin communityRating/criticRating (7.8 / 59%) set on every item and asserted never drawn; Seerr-has-nothing draws no rating; both display toggles; text/dot order.
    • SeerrRatingsItemsTest: the per-kind decision, an episode's own TMDB id is never used, home items resolve through Jellyfin once and are cached, "no TMDB id" cached, failed lookups retried, scrolling past 20 items makes zero Seerr calls, resting on one asks once after 350 ms, moving on cancels an in-flight request.
    • SourceRatingsTest, SeerrRatingsServiceTest updated to the new model.
    • Episode TMDB: season fetched once for several episodes, each season its own request, unrated/zero-vote/missing-vote episodes show series ratings only, missing season/episode numbers, series TMDB replaced by the episode's, failed season request retried and not cached, 404 season cached, series without a TMDB id, seasons unchanged, scrolling past episodes fetches nothing, Ep 8.1 drawn next to RT and following the display settings.
    • Note for reproducing: Maven Central returned HTTP 429 for the Robolectric jars from this host, so the Robolectric tests were run against a local copy of the same jars (Google's Maven Central mirror, checksum-verified) via a throwaway Gradle init script outside the repo. No repo file changed for this.
  • make apk-debug: BUILD SUCCESSFUL on both commits.

Not verified

  • No live Seerr and no device: the TMDB-id lookup, ratingscombined/tv/{id}/ratings answers and the focus-driven debounce were exercised only against mocks and Robolectric, not on the TV.
  • Whether the household Jellyfin returns ProviderIds.Tmdb for the series of every episode is assumed from Jellyfin's normal metadata; a series without it shows no rating.
  • Screenshot captures follow in a comment (Robolectric native graphics at 1920x1080, before = main 853a3989 vs after = this branch, same data).
Closes justinzeus/wolfskin#25 Every place the TV drew a Jellyfin rating now draws the PWA's ratings (Seerr: Rotten Tomatoes critics/audience, IMDb for movies, TMDB only when nothing else has a score) through ONE row component, fed by ONE data path. The Jellyfin star/tomato drawing code is deleted. ## What was wrong - The home focused header (and every other header built from a list item) kept drawing the Jellyfin star. PR 24 only wired movie and series *details* pages, and home rows are requested without `ProviderIds`, so a focused home item had no TMDB id to ask Seerr about. - Episodes (Desperate Housewives S05E01) showed the Jellyfin star because episodes were never wired and an episode's own `Tmdb` provider id is the episode's id, not the series'. ## The design - **One component**: `SourceRatingsRow` (`ui/components/SourceRatingsRow.kt`) draws badge + number pairs in the PWA order (RT critics, RT audience, IMDb or TMDB), same drawables, same spacing, honouring the `CRITIC_RATING` (RT) and `COMMUNITY_RATING` (IMDb/TMDB) display toggles. `QuickDetails` is the only caller; every header gets its ratings through `QuickDetails(ratings = ...)` or `QuickDetails(item, ...)`. - **One data path**: `rememberSourceRatings(item)` -> `SeerrRatingsService.forItem/cachedFor`. Which items have ratings is decided in exactly one place, `SeerrRatingsService`, by the PWA's rule (ratings belong to the title, never to cards or lists): | Item kind | Ratings | |---|---| | Movie | its own (`ratingscombined` + TMDB fallback) | | Series | its own (`tv/{id}/ratings` + TMDB fallback) | | Season | the SERIES' ratings (the PWA shows them on the series title page the season is part of) | | Episode | the series' Rotten Tomatoes critics/audience for context, plus the episode's OWN TMDB user score, labelled `Ep`, from Seerr's season data (see below) | | Collection (box set), music, photo, TV guide program, anything else | none. The PWA has no equivalent | - **Home blocker fix** (two parts, one cache): 1. `ProviderIds` added to the home row field lists (`HomeItemFields`, new `HomeSlimItemFields` for continue watching / next up). Small, only on home requests. 2. `SeerrRatingsService.resolveTarget` finds the TMDB id for anything that arrives without it: an episode/season resolves through its `seriesId` (one Jellyfin `getItem`), a movie/series without provider ids looks itself up. Results are cached per Jellyfin id for 6 h in a second Guava cache (including "this series has no TMDB id"); failures are not cached. Ratings keep the existing type-qualified `(movie|tv, tmdbId)` cache with the 6 h TTL, so the 2nd..nth episode of a series needs no wait and no request. - **Debounce / cancel**: `forItem` emits cached ratings at once, otherwise waits 350 ms, then asks Seerr. `rememberSourceRatings` collects it in a `LaunchedEffect(item.id)`, so moving focus cancels the wait or the in-flight request. A fast scroll never reaches Seerr. It also resets to the new item's cached value so the previous item's ratings are never shown. - **Fallback (decision)**: the Jellyfin star/tomato fallback PR 24 kept for movie/series (Seerr off, no TMDB id, call failed) is **removed**. It is not dead code, but it is the thing the owner reported, it is a second way to draw a rating, and the PWA shows nothing when Seerr has nothing. Consequence to know: with Seerr not configured/active, no rating shows anywhere on the TV. Reinstating it means restoring a draw path, which I recommend against. - Removed: `QuickDetailsData.communityRating/criticRating/criticRatingFirst`, the `AnnotatedString` building in `BaseItem.kt`, the inline-content `star`/`fresh`/`rotten` plumbing, `SourceRatings.withSourceRatings/appendRatings`, `StarRating`, `StarRatingPrecision`, `TomatoRating`, the old preview, the per-ViewModel ratings state/collectors in `MovieViewModel`/`SeriesViewModel` (which also removes the duplicate-collector shape from the PR 24 review). - Kept on purpose, not media ratings: the `SimpleStarRating` "7+" thresholds in the rating filter menus, the subtitle-search result rating in `DownloadSubtitlesDialog.kt`, and the community/critic rating sort and filter (server-side Jellyfin queries). - Discover movie/series/discover-page headers already used Seerr; they now draw through the same row and obey the same toggles (before they ignored them). They still call `SeerrRatingsService.get(type, id)` directly because they start from a Seerr id, not a Jellyfin item; it is the same service and cache. ## Per-surface inventory | Surface (file) | Before | After | Why | |---|---|---|---| | Home focused header (`HomePage.kt`, `HomePageHeader`) | Jellyfin star + threshold tomato for every kind; no TMDB id on home items so Seerr never applied | movie/series: Seerr row; episode/season: series' Seerr row; other kinds: none | PWA title ratings; home focus is the hero-equivalent. ProviderIds + series lookup + debounce | | Library grid focused header (`CollectionFolderGrid.kt`) | Jellyfin star/tomato | by item kind as above | same `HomePageHeader` | | Collection page focused item header (`CollectionDetails.kt`) | Jellyfin star/tomato | by item kind as above | same `HomePageHeader` | | Folder list header (`CollectionFolderList.kt`) | Jellyfin star/tomato | by item kind as above | `QuickDetails(item)` | | Collection header, box set itself (`CollectionDetailsHeader.kt`) | Jellyfin star/tomato | none | PWA has no collection rating | | Movie page (`MovieDetailsHeader.kt`) | Seerr (PR 24), Jellyfin fallback | Seerr only | fallback removed | | Series page (`SeriesDetails.kt`) | Seerr (PR 24), Jellyfin fallback | Seerr only | fallback removed | | Episode page (`EpisodeDetailsHeader.kt`, the S05E01 case) | Jellyfin star | series' RT critics/audience + the episode's own TMDB score (`TMDB Ep 8.1`) | PWA shows the series ratings in the episode context; the episode TMDB score was added on top at the owner's request | | Series overview, focused episode (`FocusedEpisodeHeader.kt`), home focused episode | Jellyfin star | same as the episode page (season fetched once, cached after the first episode) | same | | Season (via home / grid headers) | Jellyfin star/tomato | series' Seerr ratings | same | | Album page (`AlbumDetailsPage.kt`), artist page (`ArtistDetailsPage.kt`) | Jellyfin star/tomato | none | no PWA equivalent | | Photo slideshow header (`ImageDetailsHeader.kt`) | Jellyfin star/tomato | none | no PWA equivalent | | TV guide program header (`TvGuideHeader.kt`) | none (own text) | none | no PWA equivalent | | Discover movie / series details, Discover page header | Seerr RT/IMDb/TMDB, ignoring toggles | same ratings, same row, toggles honoured | one component | | Cards, rows, search, person pages, requests | none | none | PWA: no ratings on cards/lists | | Rating filter menu stars, subtitle-search rating | Jellyfin value as a filter threshold / subtitle score | unchanged | not a title's rating | ## Episode TMDB score (second commit) Owner asked for each episode's own TMDB score. Episodes now show: series RT critics, series RT audience, then the TMDB badge with `Ep` and the episode's score (e.g. `[TMDB] Ep 8.1`). - **Which reads cleanest**: series Rotten Tomatoes for context plus the episode TMDB, with the series' own TMDB score dropped. That leaves exactly one TMDB number on the row and it is the episode's, labelled `Ep` so nobody mistakes it for the series score. (The series TMDB is only ever shown when the series has no RT score, and in that case it is replaced by the episode score.) Showing both TMDB numbers would put two identical badges side by side. - **Source**: Seerr `GET /tv/{tvId}/season/{seasonNumber}`, episodes' `voteAverage`/`voteCount`. The series TMDB id comes from the existing `resolveTarget`. The season is fetched once and cached by `(tmdbId, seasonNumber)` for 6 h in a third Guava cache, so every other episode of that season costs no request and no wait; failures are not cached, a 404 (TMDB has no such season) is cached as an answer. Same 350 ms debounce/cancel as everything else. - **Rules**: observed only, 0-10, one decimal; zero or missing vote count is unrated (same rule as the other TMDB values, but a missing vote count also counts as unrated here since the season endpoint always reports one). An episode missing from the season, or without season/episode numbers, shows the series ratings only. A series with no TMDB id shows nothing extra. - **One row**: `RatingEntry.EpisodeTmdb` goes through the same `SourceRatingsRow`/`QuickDetails` as everything else, drawn with the same TMDB drawable, and is controlled by the community rating (IMDb/TMDB) display setting while RT stays under the critic rating setting. - **Spec**: the bundled Seerr spec typed the season answer with a strict `Season`/`Episode` model (non-nullable name/overview/etc.), which would throw on a null TMDB field. The season endpoint now returns new `SeasonDetails`/`SeasonEpisode` schemas with every TMDB-optional field nullable and `voteAverage` a decimal, `voteCount` int64. The `Season` model used by Discover is untouched. - Season (not episode) headers keep showing only the series ratings. ## Tests - `make test` (via `docker/gradle.sh`): BUILD SUCCESSFUL, 462 tests, 0 failures, 0 skipped (446 before the episode commit). - New/changed: - `SourceRatingsRowTest` (Robolectric compose): what `QuickDetails` draws for movie, series, episode, season, and collection/music/photo/program; Jellyfin `communityRating`/`criticRating` (7.8 / 59%) set on every item and asserted never drawn; Seerr-has-nothing draws no rating; both display toggles; text/dot order. - `SeerrRatingsItemsTest`: the per-kind decision, an episode's own TMDB id is never used, home items resolve through Jellyfin once and are cached, "no TMDB id" cached, failed lookups retried, scrolling past 20 items makes zero Seerr calls, resting on one asks once after 350 ms, moving on cancels an in-flight request. - `SourceRatingsTest`, `SeerrRatingsServiceTest` updated to the new model. - Episode TMDB: season fetched once for several episodes, each season its own request, unrated/zero-vote/missing-vote episodes show series ratings only, missing season/episode numbers, series TMDB replaced by the episode's, failed season request retried and not cached, 404 season cached, series without a TMDB id, seasons unchanged, scrolling past episodes fetches nothing, `Ep 8.1` drawn next to RT and following the display settings. - Note for reproducing: Maven Central returned HTTP 429 for the Robolectric jars from this host, so the Robolectric tests were run against a local copy of the same jars (Google's Maven Central mirror, checksum-verified) via a throwaway Gradle init script outside the repo. No repo file changed for this. - `make apk-debug`: BUILD SUCCESSFUL on both commits. ## Not verified - No live Seerr and no device: the TMDB-id lookup, `ratingscombined`/`tv/{id}/ratings` answers and the focus-driven debounce were exercised only against mocks and Robolectric, not on the TV. - Whether the household Jellyfin returns `ProviderIds.Tmdb` for the series of every episode is assumed from Jellyfin's normal metadata; a series without it shows no rating. - Screenshot captures follow in a comment (Robolectric native graphics at 1920x1080, before = `main` 853a3989 vs after = this branch, same data).
justinzeus added 1 commit 2026-10-29 21:05:20 +01:00
Show PWA source ratings on every surface through one shared rating row
All checks were successful
review/code-claude Claude code review: pass, no blocking findings
2ccce98dc1
Author
Owner

Gates and captures.

  • make test: BUILD SUCCESSFUL, 446 tests, 0 failures.
  • make apk-debug: BUILD SUCCESSFUL (assembleDefaultDebug). make apk not run, no release or signing work.
  • Captures (Robolectric native graphics, 1920x1080, same data before and after; before = main 853a3989, after = this branch) are in /root/.treehouse/firstmate-7bab20/2/firstmate/data/wolfskin-25-ratings-everywhere/capture : before-/after- for home-movie, home-episode, series-page, episode-page (Desperate Housewives S05E01), season-overview-episode (focused episode on the series overview), collection-header, movie-page (control).
    • before-home-movie: '8.1 star . 92% tomato' (Jellyfin). after: tomato 92%, popcorn 95%, IMDb 8.5.
    • before-episode-page: '8.4 star' only. after: series ratings, tomato 63%, popcorn 71% (series has no IMDb, TMDB is hidden because RT exists).
    • before-collection-header: Jellyfin star/tomato. after: none.
Gates and captures. - make test: BUILD SUCCESSFUL, 446 tests, 0 failures. - make apk-debug: BUILD SUCCESSFUL (assembleDefaultDebug). make apk not run, no release or signing work. - Captures (Robolectric native graphics, 1920x1080, same data before and after; before = main 853a3989, after = this branch) are in /root/.treehouse/firstmate-7bab20/2/firstmate/data/wolfskin-25-ratings-everywhere/capture : before-/after- for home-movie, home-episode, series-page, episode-page (Desperate Housewives S05E01), season-overview-episode (focused episode on the series overview), collection-header, movie-page (control). - before-home-movie: '8.1 star . 92% tomato' (Jellyfin). after: tomato 92%, popcorn 95%, IMDb 8.5. - before-episode-page: '8.4 star' only. after: series ratings, tomato 63%, popcorn 71% (series has no IMDb, TMDB is hidden because RT exists). - before-collection-header: Jellyfin star/tomato. after: none.
Author
Owner

Code review (Claude): PASS, no blocking findings. Reviewed head 2ccce98dc1c85a9ed533ba686ce43a5221daf25f.

Checks

  • make test: BUILD SUCCESSFUL, 446 tests, 0 failures/errors/skipped (matches the PR body). No apk build, no device.
  • PWA (conductarr origin/main): ratings are rendered once per title in TitleWorkspace.vue (TitleRatings inside the title heading); EpisodePanel.vue has no rating at all. The claim "an episode is a row inside the series title page whose header carries the series ratings" holds, so episode/season = series ratings, collection/music/photo/program = none matches the PWA.
  • resolveTarget/caches: an episode's own Tmdb id is never used (owner = seriesId); missing seriesId, series without TMDB, season, box set all end in no rating without a call or crash; failures are not cached; both caches bounded (300, 6 h).
  • Debounce: state reset per item id plus LaunchedEffect(item.id) means no stale rating of the previous item, and fast scrolling makes zero Seerr calls (covered by tests). Seerr inactive: no request at all, nothing drawn, no stray dot/separator (also with both toggles off); the PR body states this consequence.
  • ProviderIds only added to the home lists (HomeItemFields, new HomeSlimItemFields); SlimItemFields and its other consumers are untouched.
  • DRY: git grep finds no other drawing path; only SimpleStarRating (filter thresholds), subtitle-search rating and sort/filter by rating remain, as intended. Removed tests were for the deleted withSourceRatings/Jellyfin fallback; replaced by SeerrRatingsItemsTest and SourceRatingsRowTest. Captures show the row correct at TV scale.
  • Scope: only app/src; no build/version/signing change, no new screen/setting, no Conductarr client.

Non-blocking notes

  1. The pending per-episode TMDB badge commit is not part of this review. The PWA shows no per-episode rating, so that commit needs its own check (labelling next to the series' scores).
  2. The 350 ms debounce also delays ratings on detail pages for uncached titles.
  3. Stale KDoc "Seerr ratings, null until they load..." left above chosenStreams in MovieViewModel.kt:308 and SeriesViewModel.kt:898.
  4. Cached ratings are shown regardless of Seerr's active state (up to 6 h).
  5. Library-grid/search items lack ProviderIds, so a rested focus costs one extra cached Jellyfin getItem.
  6. No unit test drives rememberSourceRatings through an item change.

Full report: /root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-26/report.md

**Code review (Claude): PASS, no blocking findings.** Reviewed head `2ccce98dc1c85a9ed533ba686ce43a5221daf25f`. **Checks** - `make test`: BUILD SUCCESSFUL, 446 tests, 0 failures/errors/skipped (matches the PR body). No apk build, no device. - PWA (conductarr `origin/main`): ratings are rendered once per title in `TitleWorkspace.vue` (`TitleRatings` inside the title heading); `EpisodePanel.vue` has no rating at all. The claim "an episode is a row inside the series title page whose header carries the series ratings" holds, so episode/season = series ratings, collection/music/photo/program = none matches the PWA. - `resolveTarget`/caches: an episode's own `Tmdb` id is never used (owner = `seriesId`); missing `seriesId`, series without TMDB, season, box set all end in no rating without a call or crash; failures are not cached; both caches bounded (300, 6 h). - Debounce: state reset per item id plus `LaunchedEffect(item.id)` means no stale rating of the previous item, and fast scrolling makes zero Seerr calls (covered by tests). Seerr inactive: no request at all, nothing drawn, no stray dot/separator (also with both toggles off); the PR body states this consequence. - `ProviderIds` only added to the home lists (`HomeItemFields`, new `HomeSlimItemFields`); `SlimItemFields` and its other consumers are untouched. - DRY: `git grep` finds no other drawing path; only `SimpleStarRating` (filter thresholds), subtitle-search rating and sort/filter by rating remain, as intended. Removed tests were for the deleted `withSourceRatings`/Jellyfin fallback; replaced by `SeerrRatingsItemsTest` and `SourceRatingsRowTest`. Captures show the row correct at TV scale. - Scope: only `app/src`; no build/version/signing change, no new screen/setting, no Conductarr client. **Non-blocking notes** 1. The pending per-episode TMDB badge commit is not part of this review. The PWA shows no per-episode rating, so that commit needs its own check (labelling next to the series' scores). 2. The 350 ms debounce also delays ratings on detail pages for uncached titles. 3. Stale KDoc "Seerr ratings, null until they load..." left above `chosenStreams` in `MovieViewModel.kt:308` and `SeriesViewModel.kt:898`. 4. Cached ratings are shown regardless of Seerr's active state (up to 6 h). 5. Library-grid/search items lack ProviderIds, so a rested focus costs one extra cached Jellyfin `getItem`. 6. No unit test drives `rememberSourceRatings` through an item change. Full report: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-26/report.md`
justinzeus added 1 commit 2026-10-29 21:35:15 +01:00
Show each episode's own TMDB score next to the series ratings
All checks were successful
review/code-claude Claude code review: pass, no blocking findings (episode TMDB badge)
85365aa3c6
Author
Owner

Episode TMDB commit 85365aa3: make test 462 tests, 0 failures; make apk-debug BUILD SUCCESSFUL. Captures in /root/.treehouse/firstmate-7bab20/2/firstmate/data/wolfskin-25-ratings-everywhere/capture : episode-tmdb-before-/episode-tmdb-after- for episode-page, home-episode and season-overview-episode (before = 2ccce98d, series RT only; after = TMDB badge with 'Ep 8.1' next to the series RT 63% / 71%). Same data, Robolectric 1920x1080.

Episode TMDB commit 85365aa3: make test 462 tests, 0 failures; make apk-debug BUILD SUCCESSFUL. Captures in /root/.treehouse/firstmate-7bab20/2/firstmate/data/wolfskin-25-ratings-everywhere/capture : episode-tmdb-before-/episode-tmdb-after- for episode-page, home-episode and season-overview-episode (before = 2ccce98d, series RT only; after = TMDB badge with 'Ep 8.1' next to the series RT 63% / 71%). Same data, Robolectric 1920x1080.
Author
Owner

Code review (Claude), follow-up for the per-episode TMDB badge: PASS, no blocking findings. Reviewed head 85365aa3c6c66ef10dffb8dfc91a8de54b21a382 (delta 2ccce98d..85365aa3; the earlier pass on 2ccce98d stands).

Checks

  • make test: 462 tests, 0 failures/errors/skipped (matches the author). Two earlier runs hit HTTP 429 from Maven Central while Robolectric downloaded its jars; I reran with the jars from a mirror in a scratch worktree (not part of the PR).
  • Labelling: captures episode-tmdb-after-* show ... tomato 63% / popcorn 71% / TMDB "Ep 8.1". The "Ep" label sits in front of the number; RT stays the series' scores and the series' own TMDB is dropped, so there is one TMDB number and it reads as the episode's.
  • One row, no second drawing path: new RatingEntry.EpisodeTmdb is drawn by SourceRatingsRow/ratingsText, and belongs to the community-rating toggle (RT still follows the critic toggle).
  • Data path: real Seerr GET /tv/:id/season/:n returns episodes with episodeNumber, voteAverage, voteCount; the yml models them nullable (decimal average, int64 count). One season request is cached per (series TMDB id, season) for 6 h and serves every episode of it. The key uses the series id from resolveTarget, never the episode's own Tmdb id. Failures are not cached (404 is), debounce/cancel are unchanged, null/zero votes or out-of-range scores mean unrated (never 0.0), same one-decimal format. Seasons and other kinds are unchanged; episode header, focused-episode header and home/grid headers all go through the same path.
  • Tests assert behaviour (season fetched once for several episodes, separate request per season, unrated episode, missing season/episode number or series id makes no call, failed request retried, 404 cached, no fetch while scrolling, toggles). Only the ratingsText calls were updated for the new label parameter; nothing weakened.

Non-blocking notes

  1. The stale KDoc left above chosenStreams in MovieViewModel.kt:308 and SeriesViewModel.kt:898 (earlier note) was not cleaned up.
  2. Seerr answers 500 (not 404) for a season TMDB does not know (e.g. specials), so each rested focus on such an episode repeats one failing request (debounced, no storm).
  3. Episodes are matched by Jellyfin season/episode numbers, so another ordering (DVD/absolute) can show a neighbouring episode's score.

Full report: /root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-26/report.md

**Code review (Claude), follow-up for the per-episode TMDB badge: PASS, no blocking findings.** Reviewed head `85365aa3c6c66ef10dffb8dfc91a8de54b21a382` (delta `2ccce98d..85365aa3`; the earlier pass on `2ccce98d` stands). **Checks** - `make test`: 462 tests, 0 failures/errors/skipped (matches the author). Two earlier runs hit HTTP 429 from Maven Central while Robolectric downloaded its jars; I reran with the jars from a mirror in a scratch worktree (not part of the PR). - Labelling: captures `episode-tmdb-after-*` show `... tomato 63% / popcorn 71% / TMDB "Ep 8.1"`. The "Ep" label sits in front of the number; RT stays the series' scores and the series' own TMDB is dropped, so there is one TMDB number and it reads as the episode's. - One row, no second drawing path: new `RatingEntry.EpisodeTmdb` is drawn by `SourceRatingsRow`/`ratingsText`, and belongs to the community-rating toggle (RT still follows the critic toggle). - Data path: real Seerr `GET /tv/:id/season/:n` returns episodes with `episodeNumber`, `voteAverage`, `voteCount`; the yml models them nullable (decimal average, int64 count). One season request is cached per (series TMDB id, season) for 6 h and serves every episode of it. The key uses the series id from `resolveTarget`, never the episode's own `Tmdb` id. Failures are not cached (404 is), debounce/cancel are unchanged, null/zero votes or out-of-range scores mean unrated (never 0.0), same one-decimal format. Seasons and other kinds are unchanged; episode header, focused-episode header and home/grid headers all go through the same path. - Tests assert behaviour (season fetched once for several episodes, separate request per season, unrated episode, missing season/episode number or series id makes no call, failed request retried, 404 cached, no fetch while scrolling, toggles). Only the `ratingsText` calls were updated for the new label parameter; nothing weakened. **Non-blocking notes** 1. The stale KDoc left above `chosenStreams` in `MovieViewModel.kt:308` and `SeriesViewModel.kt:898` (earlier note) was not cleaned up. 2. Seerr answers 500 (not 404) for a season TMDB does not know (e.g. specials), so each rested focus on such an episode repeats one failing request (debounced, no storm). 3. Episodes are matched by Jellyfin season/episode numbers, so another ordering (DVD/absolute) can show a neighbouring episode's score. Full report: `/root/.treehouse/firstmate-7bab20/2/firstmate/data/review-wolfskin-26/report.md`
justinzeus merged commit 6bb382a126 into main 2026-10-29 21:51:24 +01:00
Sign in to join this conversation.
No reviewers
No labels
exploring
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: justinzeus/wolfskin#26
No description provided.