---
key: iptv.logo-drives-bug-preset
title: 2026-07-20 — One logo drives the bug via a shared ChannelLogo preset, not new schema (#67)
status: active
since: '2026-07-20'
supersedes: none
superseded-by: none
rule: One uploaded channel logo drives both the listing logo and the on-screen bug via a shared, seeded `ChannelLogo`-sourced watermark preset (`Channel Bug`), not new per-channel schema.
signals: 'channel logo, watermark bug preset, ChannelWatermark seeding, ChannelLogo imageSource, seed/adopt semantics, quick-add · paths: `ChannelWatermarkImageSource.ChannelLogo`, `DbInitializer.Initialize`, `watermark.channel_bug_seeded`, `WatermarkResponseModel` · issues: #67, #286, #502'
mechanics: '`DbInitializerChannelBugWatermarkTests`; `WatermarkResponseModel.imageSource`; `DbInitializer` seeded `Channel Bug` watermark + `watermark.channel_bug_seeded` ConfigElement marker'
---

#67 asked that one uploaded image drive both the listing logo and the on-screen bug, separably
overridable, with preview. Most of it already existed: `ChannelWatermarkImageSource.ChannelLogo`
resolves the channel's own logo artwork at render time at all three watermark precedence levels, and
`Custom` already provides the independent override. Production had already been running exactly this
pattern by hand — 43 channels pointing at one hand-made `Channel Bug` preset.

**Decision: seed that preset rather than add per-channel bug columns.** A watermark is a shared named
entity, so per-channel geometry would need either a dual-provider migration or one watermark row per
channel (under a unique-name index). Since `ChannelLogo` resolves per channel at render time, a single
shared row already delivers the user-visible behavior with no schema change.

- The seed **adopts** an existing `Channel Bug` row untouched, so an operator's tuned geometry is never
  overwritten, and a `ConfigElement` marker (`watermark.channel_bug_seeded`) makes it run once per
  database rather than once per name-absence — `ChannelWatermark` has no `IsSystem` flag and
  `DbInitializer.Initialize` runs at every startup, so a name-only guard would resurrect a deliberately
  deleted preset forever. Covered by `DbInitializerChannelBugWatermarkTests`.
- `WatermarkResponseModel` gained `imageSource` (additive under the frozen `/api/v1`, #286) so clients
  identify logo-driven presets generically instead of matching a user-editable name. Note the limit:
  once a *second* logo-driven preset exists, `imageSource` identifies the class but not *the* default,
  so `findLogoBugWatermark` prefers the seeded name as a deterministic tiebreak.
- The default is applied by the SPA's quick-add **creation** path, not by inferring "newness" in the
  editor — quick-add creates through the API and then navigates to the editor, so the editor only ever
  loads an existing row. The editor toggle reflects the **referenced** watermark's `imageSource`; an
  earlier draft searched the list instead, which (because `getWatermarks()` sorts by name) would have
  silently repointed channels bound to a non-first logo-driven preset. Caught in independent review
  and pinned by a regression test.
- The builder flow is covered on **fresh installs only**, by stamping the preset onto the system
  channel templates the seed itself creates; existing installs' templates are never mutated.
- Server-side create defaulting was rejected: making an omitted `watermarkId` mean "give me a
  watermark" would surprise machine clients of the frozen API. `POST /api/v1/channels/auto-tune` is
  therefore unchanged.
- **Not fixed here:** external-URL logos never render a bug (`WatermarkSelector` `File.Exists`-checks a
  URL). Pre-existing, lands in the FFmpeg render path, tracked as **#502**. This change only stops the
  preview from promising it.

**Accepted trade-off:** every channel on the shared preset shares one geometry; per-channel tweaks mean
creating a second preset on the Watermarks screen.
