docs(67): record the shared-preset decision, watermark seeding, and the additive DTO field

This commit is contained in:
2026-07-20 20:48:14 +02:00
parent 520b113d66
commit bb7f57d04d
3 changed files with 72 additions and 2 deletions
+4
View File
@@ -289,6 +289,10 @@ returns `null` when the channel has no logo so the SPA falls back to its generat
raw un-rooted `{path, contentType}` form is still used only by the channel **editor** DTO
(`ChannelDetailResponseModel.Logo`), which round-trips it back on save.
`GET /api/v1/watermarks` returns picker-grade rows that carry `imageSource` alongside `id`/`name`
(#67), so a client can find the seeded logo-driven `Channel Bug` preset without matching its
user-editable name. The full geometry still requires `GET /api/v1/watermarks/{id}`.
### 4a. Artwork content type is sniffed, never client-supplied (issue #283)
The uploaded-artwork surfaces (channel logo, watermark) must never trust a client-declared content
+28 -2
View File
@@ -139,6 +139,32 @@ The scheduling engine (`ErsatzTV.Core/Scheduling/`) resolves schedule items into
## Watermarks
`ChannelWatermark` supports modes: Permanent, Intermittent, OpacityExpression. Image sources: custom upload, channel logo, or built-in resource. Positioned with percentage-based margins and z-index.
`ChannelWatermark` supports modes: Permanent, Intermittent, OpacityExpression. Image sources: custom
upload, channel logo, or built-in resource. Positioned with percentage-based margins and z-index.
Note: `ChannelLogoGenerator.GenerateChannelLogoUrl()` hardcodes `localhost` for watermark logo fetching — see issue #1 for details.
A watermark is a **shared, named entity** (unique `Name`), referenced by playout items, schedule
items, block items and decos; a channel points at one via `Channel.WatermarkId`. It is not
per-channel state.
`DbInitializer` seeds one shared preset named **`Channel Bug`** (`ImageSource = ChannelLogo`,
Permanent, TopLeft, Scaled 5% width, 1%/1% margins, 80% opacity). Because `ChannelLogo` resolves
each channel's own `ArtworkKind.Logo` artwork at render time, this single row makes every channel
that points at it use its own logo as its on-screen bug — one uploaded image drives both the guide
listing and the bug (#67). An existing `Channel Bug` row is adopted untouched, never overwritten, and
a `ConfigElement` marker (`watermark.channel_bug_seeded`) makes the seed run once per database, so a
deliberately deleted preset is not resurrected on the next restart.
Quick-add channel creation defaults to the preset, and the channel editor's Branding tab exposes it
as a "Use logo as on-screen bug" toggle. On a **fresh** install the seed also stamps the preset onto
the system channel templates it creates, so the library-to-lineup builder (which inherits
`WatermarkId` from the selected template) gets the default too. On an existing install the templates
are left alone, so builder-created and auto-tuned channels there inherit whatever the template
already specifies.
**Limitation:** a logo set via **External logo URL** cannot drive the bug. `WatermarkSelector`
resolves it to the URL and then `File.Exists`-checks it, which is never true, so the watermark is
silently dropped — the URL wins for the guide listing but disables the on-screen bug. Tracked as
**#502**; the editor does not offer a bug preview in that case.
Note: `ChannelLogoGenerator.GenerateChannelLogoUrl()` hardcodes `localhost` for watermark logo
fetching — see issue #1 for details.
+40
View File
@@ -2361,3 +2361,43 @@ fixed here:
sweep. This is *why* it cannot cross-delete in a mixed library, but it also means a music video removed
from Jellyfin is never removed from ErsatzTV. Pre-existing and orthogonal to this change; it belongs with
that scanner's other gaps (no `ItemId`/`Etag`, path-keyed identity — see #488).
## 2026-07-20 — One logo drives the bug via a shared ChannelLogo preset, not new schema (#67)
#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.