## Summary
- Confirmed live: a bus error on one overlay's own branch (an `image_url` source whose fetch/decode failed) can cascade into the pipeline's permanent placeholder branch failing moments later, even though the placeholder was never touched by that removal. `_source_id_from_error_src` only ever recognized `overlay-*`/`source-*` element names, so this fell through as an unattributable pipeline-level error and tore down the *entire* composition -- including whatever else was actually working (a scoreboard interactive overlay confirmed running seconds earlier in this same session).
- Adds a third `'placeholder'` kind: isolated the same shape as a real overlay/source error, except the branch is torn down and immediately rebuilt (`recreate_placeholder_branch`) rather than left removed, since the pipeline can never be left without it.
- Separately: `remove_overlay`/`remove_source` only ever touched the live GStreamer pipeline, never the DB -- the `CompositionSource` row kept looking like a healthy, attached source after a real bus-error isolation, with no way for an operator to know it silently stopped contributing to the composited output. `_notify_source_auto_removed` now soft-removes the row (`removed_at`, matching `getCompositionSources`' own `?eq(removed_at,null())` filter) and broadcasts a new `composition_source_error` WS event, without touching `need_update` (the pipeline already reflects the removal directly, nothing is pending to apply).
- For INTERACTIVE sources this cascades to the whole copy-sharing group (mirroring `remove_composition_source`'s own existing precedent) -- every copy shares one `CaptureSession`/browser, so a dead session is equally fatal to every copy regardless of which scene it's in; each live copy's own independent branch is torn down too, not just soft-removed, or it's left orphaned in the running pipeline.

frontend-admin's own follow-up wires the WS event into a warning dialog that refreshes the canvas on dismiss.
