Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
6.4 KiB
key, title, status, since, supersedes, superseded-by, rule, signals
| key | title | status | since | supersedes | superseded-by | rule | signals |
|---|---|---|---|---|---|---|---|
| graphics.on-now-next-on-by-default | 2026-08-26 — The On Now / Next overlay is on by default, backfilled exactly once (#732) | active | 2026-08-26 | none | none | The built-in On Now / Next element is attached to new channels by `ChannelGraphicsDefaults.Attach`, called from BOTH create paths, and to pre-existing channels by a one-time `AttachOnNowNextByDefault` backfill guarded by `graphics.on_now_next_default_attached`. The marker is written only once the built-in element RESOLVES, so an install whose element row does not exist yet is retried rather than stranded; the cost is that while the backfill is still armed it cannot tell a deliberately cleared channel from an untouched one. HLS Direct is excluded at both sites. | AttachOnNowNextByDefault, GraphicsOnNowNextDefaultAttached, ChannelGraphicsDefaults.Attach, ChannelGraphicsElement default, On Now Next default on, create-time graphics default, CreateChannelFromLineup graphics · paths: `ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsElementSeeder.cs`, `ErsatzTV.Application/Channels/ChannelGraphicsDefaults.cs`, `ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs`, `ErsatzTV.Application/Channels/Commands/CreateChannelFromLineupHandler.cs` · issues: #732, #74 |
#74 shipped the overlay as a per-channel opt-in toggle. #732 made it a default. The binding level is
ChannelGraphicsElement — the base layer added at GraphicsElementSelector's final fall-through, so a
deco in Override/Disable mode still suppresses it exactly as before (graphics.channel-level-attachment).
Two mechanisms, because they answer different questions:
- New channels —
ChannelGraphicsDefaults.Attachis called as the channel is persisted. The create requests carry no graphics field and the SPA's channel editor is update-only, so the server is the only place a create-time default can live. - Pre-existing channels —
AttachOnNowNextByDefaultruns once at startup, after the seeder.
The create-time mechanism has more than one call site, and enumerating them from the source is the
only reliable way to find them. There are three places that persist a Channel:
CreateChannelHandler (the SPA's "New blank channel"), CreateChannelFromLineupHandler (the SPA's
primary "Add Channel" flow, and what Auto-Tune bulk-creates through), and DbInitializer's default
channel. The first two both call the shared helper — they diverged once, with only the first covered,
which silently excluded the busier path. The third needs no call because it runs before
AttachOnNowNextByDefault in the same startup, so the backfill picks it up. A new persisting site
must call the helper; grep for Channels.Add rather than trusting this list to stay complete.
The marker is written only once the element resolves, and the built-in GraphicsElement row is
created by the seeder (EnsureBuiltInElementRow) rather than waited for. Those two go together.
Writing the marker unconditionally is the tempting simplification, and it is wrong: on an install upgrading from before #74 the template is seeded on the same boot, so a marker written while the row is merely undiscovered strands every pre-existing channel permanently — the exact population the backfill exists for. Creating the row in the seeder removes that ordering dependency for every normal path, and skipping the marker when nothing resolves covers the rest.
This is a real trade, not a free win, and a single global marker cannot represent both halves. The two properties wanted here are "never re-add to a channel the operator cleared" and "never strand a channel that had no element to attach". A boolean that means the backfill has run can express one or the other, never both:
- While the backfill is armed (nothing has resolved yet — the template file is absent, e.g. an
operator deleted it and
RefreshGraphicsElementsreaped the row), a channel cleared by the operator is indistinguishable from one never considered. If the element is later restored, the next boot attaches it to every eligible channel, including that one. - Once the marker is written, no channel is ever re-attached, on any restart.
The armed window is narrow (it requires the built-in template to be absent at startup) and the failure is visible and reversible — an overlay reappears — whereas stranding is silent and permanent. That is why it is resolved this way. Closing it properly needs per-channel "considered / opted-out" state rather than one global flag, which is a schema change and is tracked separately.
It also cannot reconstruct pre-upgrade operator intent. On an install that predates #732, an operator who enabled the overlay and later turned it off left no record that survives — the join row is simply absent, indistinguishable from never having enabled it — so the one-time backfill re-attaches it. That is inherent to "enable it on all channels by default" rather than a defect. The never-re-attach guarantee therefore holds from the marker onwards, not across the upgrade boundary.
Identity is the element's filename (GraphicsElementDefaults.OnNowNextFileName), never the
user-editable Name — the #67 lesson carried through #74.
HLS Direct is excluded at both sites. ErsatzTV is not transcoding there, so GraphicsElementSelector
returns empty and the editor disables the toggle; an attachment would be inert while still reading as
"on". Excluding it keeps the stored state honest rather than merely harmless.
HLS Direct is not the only inert case: FFmpegLibraryProcessService gates the graphics engine on
videoFormat != VideoFormat.Copy, so a channel whose FFmpeg profile is set to Copy also renders
nothing while showing the toggle on. That one is deliberately not excluded here — the profile is
mutable and shared, so the streaming-mode exclusion is a property of the channel while the Copy
gate is a property of a setting that can change under it. The render-site gate is the only correct
place for it.
Residual risk, stated rather than reassured away. On 2026-08-26 all 43 channels on the live
install already carried the element (measured by reading graphicsElementIds from
GET /api/v1/channels/{id} for every id in GET /api/v1/channels). The backfill is therefore a no-op
in the only place it has ever run, and its behaviour against real pre-existing data is covered by
tests and a local live run, not by production evidence.