--- key: graphics.channel-level-attachment title: 2026-07-22 — Channel-level graphics-element attachment + seeded On Now/Next text element (#74) status: active since: '2026-07-22' supersedes: none superseded-by: none rule: A channel can attach `GraphicsElement`s directly via a new `ChannelGraphicsElement` join table (a base layer under deco/playout-item elements), and a built-in text element (`on-now-next.yml`) is seeded once per database so the On Now/Next overlay works out of the box. signals: 'ChannelGraphicsElement, Channel graphics attachment, GraphicsElementSelector base layer, on-now-next seeded element, GraphicsElementDefaults.OnNowNextFileName, builtIn discriminator · paths: `ErsatzTV.Core/Domain/ChannelGraphicsElement.cs`, `ErsatzTV.Core/FFmpeg/GraphicsElementSelector.cs`, `ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsElementSeeder.cs`, `ConfigElementKey.GraphicsOnNowNextSeeded`, `GraphicsElementResponseModel.BuiltIn` · issues: #74' --- #74 asked for a transient "On Now / Next" text bug burned onto the transcoded stream at each program transition. The rendering and EPG-template-data infrastructure already existed (upstream graphics engine + our #502/#511 remote-image/graphics-engine work); the gap was that graphics elements had **no channel-level attachment** — only `PlayoutItem`/`ProgramScheduleItem`/`BlockItem`/ `Deco` joins existed — and there was **no seeded/built-in graphics element**, unlike watermarks. **Decision: add a 5th join table, `ChannelGraphicsElement`, rather than reuse the watermark FK.** Watermarks and graphics elements are separate parallel systems; a channel already has exactly one `WatermarkId`, already spent on the #67 logo bug, and multi-line EPG text is a poor fit for the single-image watermark model. `ChannelGraphicsElement` is structurally identical to the existing four joins (composite key `{ChannelId, GraphicsElementId}`), added via a dual-provider migration (`scripts/add-migration.sh Add_ChannelGraphicsElement`). - `GraphicsElementSelector.SelectGraphicsElements` appends channel-level elements at the **final fall-through**, alongside `playoutItem.PlayoutItemGraphicsElements` — a **base layer**. A deco in `Merge` mode composes with it; a deco in `Override`/`Disable` mode returns earlier and so suppresses it (decos are allowed to override channel defaults, a deliberate rule). One more suppression path: on a **filler** item, a deco whose graphics-elements section is not set to run during filler (`UseGraphicsElementsDuringFiller` false) clears the result and returns for `Merge` and `Override` alike, so the channel base layer is dropped there too. `HttpLiveStreamingDirect` continues to return empty (ErsatzTV isn't transcoding, so there is no frame pipeline to draw into). - **Seeded built-in element**, mirroring the `iptv.logo-drives-bug-preset` (#67) pattern: `GraphicsElementSeeder.SeedOnNowNext` writes `on-now-next.yml` into the graphics-elements templates folder (only if the file is absent — operator edits are never clobbered) and ensures a `GraphicsElement` row exists for it, guarded by the `graphics.on_now_next_seeded` `ConfigElement` marker so it runs once per database, not once per file-absence (the same reasoning as #67: the seeder runs at every startup, so a name/file-only guard would resurrect a deliberately deleted preset). - The API needed a way for the SPA to find the built-in element without a fragile name-match — the direct #67 lesson (`WatermarkResponseModel.imageSource`). `GraphicsElementResponseModel` gained a server-derived `BuiltIn` bool, computed by comparing the row's `Path` filename to `GraphicsElementDefaults.OnNowNextFileName` rather than trusting the element's editable `Name`. - The channel editor's Branding-tab "Show On Now / Next overlay" switch follows the exact pattern of the existing logo-bug toggle: on adds the built-in element's id to `graphicsElementIds`, off removes it; disabled (with an explanatory caption) when the channel is HLS-Direct. **Accepted trade-off:** all channels that enable the toggle share one seeded element's geometry/ content; per-channel customization means editing the shared YAML or attaching a different element (the join is general, not restricted to the seeded one).