Poster artwork and service logos - #52
Conversation
Now Playing rendered a monogram placeholder because no endpoint ever returned an image. Posters now come from Plex through Tautulli's pms_image_proxy, so artwork needs no outbound internet — matching the self-hosted assumption that keeps Inter and Phosphor vendored. The browser cannot call Tautulli directly: it may have no route to it, and the API key must not leave the server. A new /api/poster route proxies the bytes instead. The `img` value round-trips through the client, so it is re-validated against Plex's metadata path shape on the way back in rather than trusted because we emitted it — that check is the whole trust boundary, and it keeps the route from being usable as a general-purpose proxy. Every failure is a 404, not a 5xx: an item with no poster, a booting Tautulli, or an undiscovered key all fall back to the monogram tile the design already specifies, which is also what renders before the image loads. Episodes use the show's poster rather than the episode still. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe dashboard adds validated poster URL generation and proxying for Plex, Sonarr, Radarr, and TMDb, propagates nullable poster fields through server and web models, renders poster fallbacks, and loads vendored service SVG logos with monogram fallback behavior. ChangesPoster artwork pipeline
Service logo rendering
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant SourceLoader
participant CommandCenter
participant Poster
participant PosterAPI
participant MediaSource
SourceLoader->>CommandCenter: provide nullable poster URL
CommandCenter->>Poster: render poster
Poster->>PosterAPI: request validated poster URL
PosterAPI->>MediaSource: fetch image bytes
MediaSource-->>PosterAPI: image response or failure
PosterAPI-->>Poster: cached image or 404
Poster-->>CommandCenter: image or monogram fallback
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Now Playing was the only panel with real art. The rest kept their placeholders, which reads as broken rather than intentional once one panel has covers. Each panel's art comes from whichever service already holds it, so the route generalises from `?img=` to `?src=…&ref=…`, and posters.ts becomes the single place that knows how to turn a reference into bytes. Doing this now rather than as a follow-up keeps the URL shape from changing under anyone twice. plex Tautulli's pms_image_proxy (Now Playing) sonarr the *arr's own cached cover (Upcoming, downloads it grabbed) radarr likewise (downloads it grabbed) tmdb image.tmdb.org (Requests) The *arrs matter more than they look: their `images` arrays only carry `remoteUrl` pointing at thetvdb, but each keeps a local copy under `mediacover`, so Upcoming and most downloads stay offline-safe. TMDb is the one outbound dependency and is confined to Requests, where there is no alternative — Seerr holds no local copy and its /imageproxy/ is absent on some builds, and a pending request is in no *arr yet. It fails to the placeholder, so an install with no route out sees exactly what it saw before. Every `ref` still round-trips through the browser, so each source re-validates its own shape on the way in. Refs are not interchangeable: a Plex path offered as a TMDb one is refused, since it would otherwise reach a different host entirely. Download art rides on the existing queue match, which turned out to be capped at one page. Both queues here run past 300 records, so anything beyond the first 100 silently fell back to OTHER — indistinguishable from a torrent no *arr tracks. queue() now pages to the end, bounded so a queue that misreports its total can't spin it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
dashboard/server/src/sources/seerr.ts (1)
85-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering
describe()'s poster branch in tests.Every other poster source in this PR (
posters.isValidRef/url,tautulli.toStream,transmission.attribute/indexQueues) has dedicated test coverage for its poster derivation, butdescribe()— which is the only placetmdbPosteris exercised against real Seerr detail data — doesn't appear to have an equivalent test for the happy path, missingtmdbId, or detail-fetch failure.🤖 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 `@dashboard/server/src/sources/seerr.ts` around lines 85 - 121, Add focused tests for the Seerr describe function covering successful detail data with a poster, requests without tmdbId returning UNKNOWN, and detail-fetch failures returning UNKNOWN. Mock the Seerr detail request and verify title/poster derivation, including tmdbPoster behavior, without changing describe’s existing error handling.
🤖 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.
Nitpick comments:
In `@dashboard/server/src/sources/seerr.ts`:
- Around line 85-121: Add focused tests for the Seerr describe function covering
successful detail data with a poster, requests without tmdbId returning UNKNOWN,
and detail-fetch failures returning UNKNOWN. Mock the Seerr detail request and
verify title/poster derivation, including tmdbPoster behavior, without changing
describe’s existing error handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3c7730eb-3719-4861-bbe0-791b0dee10ae
📒 Files selected for processing (11)
dashboard/server/src/config.tsdashboard/server/src/index.tsdashboard/server/src/sources/arr.tsdashboard/server/src/sources/posters.tsdashboard/server/src/sources/seerr.tsdashboard/server/src/sources/sources.test.tsdashboard/server/src/sources/tautulli.tsdashboard/server/src/sources/transmission.tsdashboard/web/src/components/Poster.tsxdashboard/web/src/types.tsdashboard/web/src/views/CommandCenter.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- dashboard/web/src/components/Poster.tsx
The sidebar and Launcher stood in two-letter monograms for every service. Now that panels carry real poster art, uniform monograms read as unfinished rather than as a deliberate style. Logos are vendored rather than fetched, for the same reason Inter and Phosphor are: an icon set that only renders with internet access is worse than none on a stack that may have no route out. Pulling each service's own favicon at runtime was tried first and rejected on evidence — of the 18 services with a web UI, only 6 return a usable image. Radarr, Prowlarr and Grafana redirect to a login page and would render 10KB of HTML as a broken tile; Sonarr's is behind form auth and 401s even with its API key; six 404; two were unreachable. The result would have looked worse than the monograms it replaced. So: 20 SVGs from homarr-labs/dashboard-icons (Apache 2.0), with provenance and the trademark position recorded in a NOTICE beside them. They are globbed by filename rather than listed, so adding a logo is just dropping `<service id>.svg` in the folder. The set has nothing for checkrr, watchlistarr, decluttarr, cleanarr, cadvisor or node-exporter, and those keep their monograms — the fallback already existed, so the gap needs no handling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@dashboard/web/src/components/ServiceIcon.tsx`:
- Around line 39-54: Update the logo branch in ServiceIcon so the monogram
remains mounted behind the image while it loads or if loading fails. Track image
load state, reveal the image only after its load event, and hide it on error
while preserving the existing sizing and styling.
🪄 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 Plus
Run ID: 302c4ec2-c53b-4e3b-9a97-808d2c46537f
⛔ Files ignored due to path filters (20)
dashboard/web/src/assets/services/bazarr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/docker-socket-proxy.svgis excluded by!**/*.svgdashboard/web/src/assets/services/flaresolverr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/grafana.svgis excluded by!**/*.svgdashboard/web/src/assets/services/maintainerr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/plex.svgis excluded by!**/*.svgdashboard/web/src/assets/services/portainer.svgis excluded by!**/*.svgdashboard/web/src/assets/services/prometheus.svgis excluded by!**/*.svgdashboard/web/src/assets/services/prowlarr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/radarr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/redis.svgis excluded by!**/*.svgdashboard/web/src/assets/services/requestrr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/seerr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/sonarr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/tautulli.svgis excluded by!**/*.svgdashboard/web/src/assets/services/telegraf.svgis excluded by!**/*.svgdashboard/web/src/assets/services/timescale.svgis excluded by!**/*.svgdashboard/web/src/assets/services/tracearr.svgis excluded by!**/*.svgdashboard/web/src/assets/services/transmission.svgis excluded by!**/*.svgdashboard/web/src/assets/services/watchtower.svgis excluded by!**/*.svg
📒 Files selected for processing (5)
dashboard/web/src/app/Sidebar.tsxdashboard/web/src/assets/services/NOTICE.mddashboard/web/src/components/CommandSearch.tsxdashboard/web/src/components/ServiceIcon.tsxdashboard/web/src/views/Launcher.tsx
ServiceIcon rendered only <img> for a matched logo, with no fallback for the fetch itself failing or being slow — unlike Poster, which already handles this for media artwork. The monogram now stays mounted underneath until the logo's onLoad fires, and takes over again on onError, rather than a blank tile appearing in the meantime. Also adds test coverage for seerr.ts's describe(), the one poster source that had none: the happy path pairing a title with its TMDb poster, a request with no tmdbId yet (must not even attempt the lookup), and a failed detail fetch — all mirroring the failure-mode coverage the other three poster sources already have. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every image in the dashboard was a placeholder: media panels drew striped rectangles, and the sidebar and Launcher drew two-letter monograms. This replaces both, using only sources the stack already has.
Poster artwork
Each panel's art comes from whichever service already holds it. The route is
/api/poster?src=…&ref=…, andsources/posters.tsis the only place that turns a reference into bytes.pms_image_proxymediacovermediacover, via the queue matchThe *arrs matter more than they look: their
imagesarrays only carryremoteUrlpointing at thetvdb, but each keeps a local copy undermediacover— which is what keeps Upcoming and most downloads offline-safe.TMDb is the single outbound dependency, confined to Requests, where there is no alternative: Seerr keeps no local copy, its
/imageproxy/is absent on some builds, and a pending request is in no *arr yet. It fails to the placeholder, so an install with no route out sees exactly what it saw before.Why a proxy at all
The browser can't fetch any of these itself — three sit behind API keys that must not leave the server, and the fourth is off-box. Every
refis produced server-side, travels to the client, and comes back, so each source re-validates its own shape on the way in. Refs are deliberately not interchangeable: a Plex path offered as a TMDb one is refused, since it would otherwise reach a different host entirely.Every failure is a 404, never a 5xx — the tile falls back to the monogram it already shows before an image loads.
Service logos
Vendored, for the same reason Inter and Phosphor are: an icon set that only renders with internet access is worse than none.
Runtime favicons were tried first and rejected on evidence. Of the 18 services with a web UI, only 6 return a usable image:
Radarr and Prowlarr hand back 10 KB of login HTML, which renders as a broken tile. That would have looked worse than the monograms it replaced.
So 20 SVGs from homarr-labs/dashboard-icons (Apache 2.0), with provenance and the trademark position in
NOTICE.mdbeside the assets. They're globbed by filename, so adding one is just dropping<service id>.svgin the folder. Six services have no upstream icon and keep their monograms — the fallback already existed.Queue pagination fix
Download artwork rides on the existing torrent→*arr queue match, which turned out to request
pageSize: 100. Real queues here run to 314 and 638 records, so anything past the first page silently fell back toOTHER— indistinguishable from a torrent no *arr tracks.queue()now pages to the end, bounded so a queue misreporting its total can't spin it.Verification
Against a live stack:
naturalWidth > 0; the six uncovered services render monograms47/47 tests, typecheck, lint and build clean.
Known, not addressed
Transmission's
torrent-getacross ~9,100 torrents measured 34s against a 6s upstream timeout, so/api/downloadsintermittently reports unavailable. Environmental rather than caused here, and it degrades correctly — but note the hint it produces (check LOCAL_NETWORK) names the wrong fix for a slow-but-reachable daemon. Worth a follow-up.🤖 Generated with Claude Code
Summary by CodeRabbit
GET /api/poster) with browser caching.