fix(464): render real channel logos in guide grid + channels list #467

Merged
timothy merged 1 commits from fix/464-guide-logos into main 2026-07-19 14:10:29 +02:00
Owner

Fixes #464.

Problem

The guide/EPG grid (/app/guide) and the channels list (/app/channels) always drew the generated initials "bug" — never a channel's real uploaded logo. The logo data existed (it round-trips through the channel editor) but the browse DTOs never carried a logo URL, so both screens rendered <ChannelLogo> with no src (→ always the fallback).

Fix

  • Add a rooted, directly-usable Logo URL to ChannelGuideChannelResponseModel (guide) and ChannelResponseModel (channels list), populated by a single new Channels.Mapper.GetLogoUrl helper following the #181 artwork convention: /iptv/logos/{file} for an uploaded logo, the absolute URL passed through for an external one, null when unset (SPA keeps its generated-initials fallback).
  • Guide query now .Include(c => c.Artwork); the channels-list repo already did.
  • SPA: pass src={channel.logo} in GuideScreen + ChannelsScreen.
  • Regenerated OpenAPI (v1.json + endpoint-index.md) and v1.d.ts; updated docs/api-conventions.md §4 and docs/domain-model.md.

Verification

  • Backend unit tests: rooted uploaded URL, external passthrough, null-when-absent — for both the list handler and the guide handler. All green (306 in the filtered set).
  • web suite 862/862, typecheck, lint green.
  • Live-E2E (local instance, curl): uploaded a logo → attached to a channel → GET /api/v1/channels and GET /api/v1/guide both return logo = /iptv/logos/{hash}, and that URL serves the real image (200 image/png, unauthenticated — so a browser <img src> renders it).

Read-only change (two GET DTOs), so the write-path live-E2E mandate doesn't strictly apply, but the issue asked for a guide render check and it passes end-to-end.

🤖 Generated with Claude Code

Fixes #464. ## Problem The guide/EPG grid (`/app/guide`) and the channels list (`/app/channels`) always drew the generated initials "bug" — never a channel's real uploaded logo. The logo data existed (it round-trips through the channel editor) but the browse DTOs never carried a logo URL, so both screens rendered `<ChannelLogo>` with no `src` (→ always the fallback). ## Fix - Add a rooted, directly-usable `Logo` URL to `ChannelGuideChannelResponseModel` (guide) and `ChannelResponseModel` (channels list), populated by a single new `Channels.Mapper.GetLogoUrl` helper following the #181 artwork convention: `/iptv/logos/{file}` for an uploaded logo, the absolute URL passed through for an external one, `null` when unset (SPA keeps its generated-initials fallback). - Guide query now `.Include(c => c.Artwork)`; the channels-list repo already did. - SPA: pass `src={channel.logo}` in `GuideScreen` + `ChannelsScreen`. - Regenerated OpenAPI (`v1.json` + `endpoint-index.md`) and `v1.d.ts`; updated `docs/api-conventions.md` §4 and `docs/domain-model.md`. ## Verification - Backend unit tests: rooted uploaded URL, external passthrough, null-when-absent — for **both** the list handler and the guide handler. All green (306 in the filtered set). - `web` suite 862/862, typecheck, lint green. - **Live-E2E** (local instance, curl): uploaded a logo → attached to a channel → `GET /api/v1/channels` and `GET /api/v1/guide` both return `logo = /iptv/logos/{hash}`, and that URL serves the real image (`200 image/png`, unauthenticated — so a browser `<img src>` renders it). Read-only change (two GET DTOs), so the write-path live-E2E mandate doesn't strictly apply, but the issue asked for a guide render check and it passes end-to-end. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 1 commit 2026-07-19 13:31:53 +02:00
fix(464): render real channel logos in guide grid + channels list
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 8s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m33s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13m11s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m45s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 18m31s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 36m5s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 6s
1aa1f0cf5b
The guide/EPG grid (/app/guide) and the channels list (/app/channels) always
drew the generated initials "bug" because the browse DTOs never carried a logo
URL — GuideScreen/ChannelsScreen rendered <ChannelLogo> with no src. The logo
data existed (it round-trips through the channel editor) but never reached these
views.

Add a rooted, directly-usable Logo URL to ChannelGuideChannelResponseModel and
ChannelResponseModel, populated by a single Channels.Mapper.GetLogoUrl helper
(#181 artwork convention): /iptv/logos/{file} for an uploaded logo, the absolute
URL passed through for an external one, null when unset so the SPA keeps its
generated-initials fallback. The guide query now includes Channel.Artwork.

Regenerated OpenAPI + v1.d.ts; updated api-conventions.md + domain-model.md.

fixes #464

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Independent cold review (fresh-context adversarial pass over the full diff) — no substantive defects. Verified: Mapper.GetLogoUrl rooting (uploaded → /iptv/logos/{file}, external passthrough, null-when-unset), the channel.Artwork is null guard (warranted — Channel.Artwork has no initializer), positional-record arg alignment at every construction + test site for both ChannelResponseModel and ChannelGuideChannelResponseModel, the needed guide-query .Include(c => c.Artwork), and the SPA src={channel.logo} field name against the regenerated types.

One non-defect observation (out of scope, pre-existing): logos read the channel's own Artwork even for Mirror channels (whereas PlayoutCount uses the mirror source) — consistent with the existing GetLogo used by the channel-detail DTO, and a mirror channel legitimately presents its own branding.

Review-verdict: MERGEABLE @ 1aa1f0cf5b

Independent cold review (fresh-context adversarial pass over the full diff) — **no substantive defects**. Verified: `Mapper.GetLogoUrl` rooting (uploaded → `/iptv/logos/{file}`, external passthrough, null-when-unset), the `channel.Artwork is null` guard (warranted — `Channel.Artwork` has no initializer), positional-record arg alignment at every construction + test site for both `ChannelResponseModel` and `ChannelGuideChannelResponseModel`, the needed guide-query `.Include(c => c.Artwork)`, and the SPA `src={channel.logo}` field name against the regenerated types. One non-defect observation (out of scope, pre-existing): logos read the channel's own `Artwork` even for Mirror channels (whereas `PlayoutCount` uses the mirror source) — consistent with the existing `GetLogo` used by the channel-detail DTO, and a mirror channel legitimately presents its own branding. Review-verdict: MERGEABLE @ 1aa1f0cf5b77dd0006d5b773e67510015bfa4f8c
timothy merged commit 67dc9ca4c0 into main 2026-07-19 14:10:29 +02:00
timothy deleted branch fix/464-guide-logos 2026-07-19 14:10:29 +02:00
Sign in to join this conversation.