Per-location graphics, and deleting the old overlay
Once graphics were composited into each guest's video, the next request was natural: let the same guest carry different graphics in different places. A guest's full-screen game capture might want only a ticker; their facecam only the name; a second facecam the whole set. This note covers how that's done, and the cleanup it let us finish.
Content is per-guest; visibility is per-source
The key split: the overlay content (the name text, frame image, ticker text) keeps arriving per-guest over the data channel and is identical everywhere. What's per-source is which of those elements actually render. Each Castalong Guest source — effectively one routing "bus" / location — got three checkboxes: show name, show frame, show ticker. They default on, so existing scenes are unchanged.
Each source already owns its own overlay store, so the cleanest place to apply visibility is at rebuild time: clone the active guest's def, null out the hidden elements, then rasterise. If every element is hidden the masked def is empty and the source publishes no snapshot at all — the pump's existing "nothing to draw" fast path. Nothing extra on the hot loop.
Toggling live without dropping the feed
The source's settings callback normally tears down and reconnects the room on any change — fine for the server or room, wrong for a cosmetic toggle. So visibility flows on its own tokio::sync::watch channel, mirroring the cancellation channel that already survives reconnects:
changed = vis_rx.changed() => {
if changed.is_err() { break; } // sender gone: shutting down
store.set_visibility(*vis_rx.borrow_and_update());
}
The settings handler diffs old vs new config: if only the visibility flags changed it pushes them over the watch and returns; if a room-affecting field changed it does the full reconnect. Toggling a checkbox re-rasterises and atomically swaps the snapshot — the video never even blinks.
Deleting the old overlay entirely
With graphics now fully native, the legacy browser-overlay system was pure dead weight, so it's gone: the server's /gfx + /gfx/state + /gfx/stream relay and its in-memory hub, the graphics.html page, the dashboard's "graphics URL" field, and the plugin step that auto-created a locked "Castalong Graphics" browser source. One graphics system instead of two overlapping ones.
That left one loose end: the native control dock still drove the old path. It used to POST a title/timer body to /gfx; now, on a take, it publishes the per-guest overlay over the data channel like everything else, and its panel sets the same name/frame/ticker content. Different code surface, one protocol underneath.
A version-hygiene aside
Shipping this surfaced an unrelated bug worth noting: the plugin's displayed version was a hand-maintained constant that had been forgotten for a few releases, so it under-reported itself and showed a permanent, bogus "update available". The fix was to stop hand-maintaining it: the Rust side reads CARGO_PKG_VERSION, and the Qt dock's version is injected by CMake reading the same Cargo.toml at configure time. Two sources of truth became zero.