Playlist fetches threw away the only handle a row could be removed by:
Invidious hands out an index/indexId pair per entry and both were dropped
on conversion, so nothing downstream could say which row it meant.
Both authenticated fetches now also return a snapshot — the playlist plus
its addressable entries. Invidious entries carry the server's indexId and
survive refetches; Piped has no such token, so its entries are addressed
by position and are only valid inside the snapshot that minted them. The
entry id carries the playlist id in both cases, so position 3 of one
playlist can never be mistaken for position 3 of another.
A row needs both an indexId and an index to be a target: rows missing
either are still displayed but never appear in entries, so no removal can
be aimed at a row the server refuses to name. Page-overlap dedupe stays
keyed on index and never on videoId — the same video may sit in a
playlist twice, and each copy is its own removable row.
Replace the YatteeServerAPI setAuthHeader/buildHeaders pattern (which was
race-prone on the shared actor across multiple instances) with a generic
mechanism: HTTPClient now supports a defaultHeaders dictionary applied to
every request, and ContentService builds a per-instance HTTPClient with the
basic-auth Authorization header baked in whenever credentials are configured.
The same code path now works uniformly for Invidious, Piped, PeerTube, and
Yattee Server, so any instance sitting behind a reverse proxy that requires
HTTP Basic Auth can be authenticated regardless of backend type. Cached
default API actors are still reused when no basic-auth header is needed.