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.
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)
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>
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.
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 main2026-07-19 14:10:29 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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 nosrc(→ always the fallback).Fix
LogoURL toChannelGuideChannelResponseModel(guide) andChannelResponseModel(channels list), populated by a single newChannels.Mapper.GetLogoUrlhelper following the #181 artwork convention:/iptv/logos/{file}for an uploaded logo, the absolute URL passed through for an external one,nullwhen unset (SPA keeps its generated-initials fallback)..Include(c => c.Artwork); the channels-list repo already did.src={channel.logo}inGuideScreen+ChannelsScreen.v1.json+endpoint-index.md) andv1.d.ts; updateddocs/api-conventions.md§4 anddocs/domain-model.md.Verification
websuite 862/862, typecheck, lint green.GET /api/v1/channelsandGET /api/v1/guideboth returnlogo = /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
Independent cold review (fresh-context adversarial pass over the full diff) — no substantive defects. Verified:
Mapper.GetLogoUrlrooting (uploaded →/iptv/logos/{file}, external passthrough, null-when-unset), thechannel.Artwork is nullguard (warranted —Channel.Artworkhas no initializer), positional-record arg alignment at every construction + test site for bothChannelResponseModelandChannelGuideChannelResponseModel, the needed guide-query.Include(c => c.Artwork), and the SPAsrc={channel.logo}field name against the regenerated types.One non-defect observation (out of scope, pre-existing): logos read the channel's own
Artworkeven for Mirror channels (whereasPlayoutCountuses the mirror source) — consistent with the existingGetLogoused by the channel-detail DTO, and a mirror channel legitimately presents its own branding.Review-verdict: MERGEABLE @
1aa1f0cf5b