fix(ui): a picture that fails to load shows the card's own frame, then retries

A bare <img> sent a Referer that Google's image CDNs refuse and had no retry and no fallback, so a
Home shelf card showed the browser's broken-image mark. ui/RemoteImage gathers what Avatar, the
channel banner and the download rows had each hand-rolled: no Referer, up to three retries past the
browser's negative cache, the caller's fallback (by default the card's empty frame) instead of the
broken mark, the failure remembered by URL. Every picture now goes through it; a lint rule refuses
a bare <img>.
This commit is contained in:
2026-10-14 20:51:08 +02:00
parent 06e8291896
commit 383d394e32
31 changed files with 435 additions and 235 deletions
+9 -3
View File
@@ -56,6 +56,7 @@ import { PageHeading } from "./ui/Headings";
import { MEDIA_BADGE } from "./ui/mediaCardStyles";
import { seasonTitle } from "../lib/plexDetail";
import { cardImage } from "../lib/plexImage";
import RemoteImage from "./ui/RemoteImage";
// A playlist's items, grouped for readability: movies stay standalone, while a run of episodes from the
// same show collapses into a season/show group so a whole series doesn't sprawl into an endless flat
@@ -158,7 +159,12 @@ function Poster({ src, className }: { src: string; className: string }) {
<div
className={`relative shrink-0 overflow-hidden rounded-sm border border-border bg-surface ${className}`}
>
<img {...cardImage(src)} alt="" loading="lazy" className="h-full w-full object-cover" />
<RemoteImage
{...cardImage(src)}
alt=""
loading="lazy"
className="h-full w-full object-cover"
/>
</div>
);
}
@@ -201,7 +207,7 @@ function EpisodeRow({ ep, ctx, dense }: { ep: PlexCard; ctx: Ctx; dense: boolean
<div
className={`relative shrink-0 overflow-hidden rounded-sm border border-border bg-surface ${dense ? "aspect-video w-14" : "aspect-video w-20"}`}
>
<img
<RemoteImage
{...cardImage(ep.thumb)}
alt=""
loading="lazy"
@@ -323,7 +329,7 @@ function ShowGroup({ block, ctx }: { block: ShowBlock; ctx: Ctx }) {
aria-hidden
>
{block.items.slice(0, 3).map((ep, i) => (
<img
<RemoteImage
key={ep.id}
{...cardImage(ep.thumb)}
alt=""