Read a playlist back before writing over it

Ten review findings in the remote-playlist work, all of them the same
shape: something acted on what it remembered instead of what the server
says now.

- A confirmed-gone playlist is forgotten whole — snapshot, list entry,
  and both ordering fences — so a refresh that started before the delete
  can no longer republish it, and a list refetch that fails offline no
  longer leaves it on screen.
- `.accountsDidChange` discards account-scoped content when the account
  it belonged to is no longer the effective one, as the status
  notification already did.
- A tab that stops being offered no longer stays selected: the browse
  view moves to the first available one, which reloads by itself.
- A Handoff account id is accepted only when that account is on the
  instance the playlist lives on.
- Composite writes moved up into `AccountService`, over a transport whose
  every method is exactly one request: create-with-privacy, rename, and
  set-description. The credential is rechecked between each pair, so a
  sign-out between the two halves of a create or an edit can no longer
  send the second half under the previous user's secret.
- Editing a playlist no longer forces it private. Invidious serialises
  visibility as `isListed` — `privacy.public?`, so unlisted and private
  are indistinguishable — which the snapshot now carries; the picker
  starts there, and sends nothing at all unless it was moved. Invidious
  keeps the stored visibility when the PATCH omits it (measured), so an
  untouched picker leaves it exactly as it was.
- Tapping Edit on a server-side playlist awaits an authenticated refetch
  first and populates the form from it; a failed refetch shows an error
  and no sheet.
- Playlist rows are keyed by row rather than by video id, which two rows
  of the same video share.
- The add-to-playlist sheet offers only accounts whose instance is
  enabled and that hold a credential, alongside the write capability.
This commit is contained in:
Arkadiusz Fal
2026-09-02 23:59:31 +02:00
parent 24b43ac866
commit b4855f5be9
12 changed files with 599 additions and 132 deletions

View File

@@ -44,10 +44,19 @@ struct PlaylistSelectorSheet: View {
/// Every account that can be added to, on every instance — not just the one
/// currently selected as the data context. Adding a video to a playlist is
/// not a browsing decision, and the user may well mean the other login.
///
/// The same three things the write layer insists on, asked here so an
/// account that could only ever fail is not offered as somewhere to put a
/// video: the backend must do playlist writes, the instance must be one
/// this app still talks to, and there must be a credential to talk with.
/// Account *status* deliberately is not among them — a stale "rejected"
/// would hide a login that works, and a failed add says so with a toast.
private var remoteDestinations: [Account] {
guard let appEnvironment else { return [] }
return appEnvironment.accountsManager.accounts.filter {
appEnvironment.accountsManager.capabilities(for: $0)?.supportsPlaylistWrites == true
return appEnvironment.accountsManager.accounts.filter { account in
appEnvironment.accountsManager.capabilities(for: account)?.supportsPlaylistWrites == true
&& appEnvironment.accountsManager.instance(for: account)?.isEnabled == true
&& appEnvironment.accountCredentialsManager(for: account)?.isLoggedIn(for: account) == true
}
}
@@ -302,7 +311,7 @@ struct PlaylistSelectorSheet: View {
on account: Account,
title: String,
description: String?,
privacy: PlaylistPrivacy
privacy: PlaylistPrivacy?
) async {
guard let appEnvironment else { return }