From 2ba9f57d4c5a424d281fbca9af172415b1ed4f40 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 3 Sep 2026 18:27:39 +0200 Subject: [PATCH] Let the account name the server its playlist lives on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An account opened over Handoff arrives without an instance, and a Piped playlist id looks like any other, so the instance was guessed from the id and the guess landed on Invidious — where the ownership check then threw away the one account that could read the playlist. Leaving the detail view is also the honest answer when the reconciling refetch finds nothing left: the playlist is gone, whether a delete that reported a failure landed anyway or the account was cleared. --- .../Playlist/UnifiedPlaylistDetailView.swift | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift index c83a343a..7719d176 100644 --- a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift +++ b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift @@ -860,6 +860,13 @@ struct UnifiedPlaylistDetailView: View { let targetInstance: Instance? if let instance { targetInstance = instance + } else if let accountInstance = instanceForOpeningAccount() { + // The account the playlist was opened as names the server it lives + // on, and it says so with more authority than any guess from the + // id: a Piped playlist is `.global` like any other, so the guess + // below would land on an Invidious instance and the ownership check + // would then throw away the very account that can read it. + targetInstance = accountInstance } else if let playlistSource = playlistID.source { targetInstance = instanceForSource(playlistSource, instancesManager: appEnvironment.instancesManager) } else { @@ -938,6 +945,17 @@ struct UnifiedPlaylistDetailView: View { isLoading = false } + /// The instance of the account the playlist was opened as, when the place + /// it was opened from named one and this device still has it. + private func instanceForOpeningAccount() -> Instance? { + guard let appEnvironment, + let accountID = source.accountID, + let account = appEnvironment.accountsManager.account(id: accountID) + else { return nil } + + return appEnvironment.accountsManager.instance(for: account) + } + /// The account whose playlist this is, or `nil` when it is nobody's. /// /// The id passed in with the source is the reliable answer — the place the @@ -1212,13 +1230,22 @@ struct UnifiedPlaylistDetailView: View { /// Nothing here is edited optimistically: what the view shows after a change /// is only ever what the server answered with — including after a failure, /// because a write that reported one may still have landed. + /// + /// No snapshot left is an answer too: the refetch found the playlist gone + /// (a delete that reported a failure but landed), or the account it belonged + /// to was cleared. Either way there is nothing left to show or to edit, so + /// the view leaves exactly as it does after a delete that reported success. private func applyRefetchedSnapshot() { guard let appEnvironment, let account = owningAccount, - let playlistID = remotePlaylistID, - let snapshot = appEnvironment.remotePlaylistsCache.snapshot(id: playlistID, for: account.id) + let playlistID = remotePlaylistID else { return } + guard let snapshot = appEnvironment.remotePlaylistsCache.snapshot(id: playlistID, for: account.id) else { + dismiss() + return + } + apply(snapshot) }