From 1c0a3e8e9ace877f624ebccb2f7451362d04643f Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 28 Aug 2026 23:24:06 +0200 Subject: [PATCH] Keep one login's verdicts and content off another's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A feed failure recorded its verdict before checking whether anyone was still waiting for it, and the verdict resolves the *currently* selected account — so a 401 from a credential the app had already moved off could mark its replacement, or a different account, as rejected indefinitely. The instance browser drops its feed, subscriptions and playlists when the selected login changes. Nothing else there would notice: the instance, the tab and the "already has data" shortcut are all unchanged between two accounts on the same server. Caches are no longer discarded while the identity is unresolved. With iCloud Drive off the scope drops to unresolved on every activation and comes back the same, and the context reads as local in between — which looked like a move and threw the caches away several times a day. Local is always in the account picker again. Its feed needs a Yattee Server and reports that itself; with correction no longer moving away from local, leaving it out of the list made it unreachable once left. And the subscriptions view reloads when an instance is switched off, which moves reads to local without touching the context or any status. --- ...-45BF-4DD0-BB7E-A46C31531159.xcactivitylog | Bin 0 -> 615 bytes .../Logs/Package/LogStoreManifest.plist | 47 ++++++++++++++++++ Yattee/Core/AppEnvironment.swift | 7 ++- Yattee/Services/SubscriptionFeedCache.swift | 4 +- .../SubscriptionAccountValidator.swift | 12 ++--- .../Views/Instances/InstanceBrowseView.swift | 17 +++++++ .../Subscriptions/SubscriptionsView.swift | 14 +++++- 7 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 DerivedDataReview/Logs/Package/D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog create mode 100644 DerivedDataReview/Logs/Package/LogStoreManifest.plist diff --git a/DerivedDataReview/Logs/Package/D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog b/DerivedDataReview/Logs/Package/D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog new file mode 100644 index 0000000000000000000000000000000000000000..4d8d5af54d1767a53378055b7e279ab6e94e2315 GIT binary patch literal 615 zcmV-t0+{_DiwFP!000006V+7Bj@mE~euBP31X{tx#CH4_P9$-tR^63W-L~qfl4Bc) zHpD0iNW6U=V7EZq?Y6fjm+_2e=KJQyk9Tv3q9MjNw=&P%daY`6SJpP&mX*14OEiPS zC?0&(&e(~+lIX77mEGQ(q+6@9{K?QHO%^pjfJ_7^IRjnZO)ix^rh z2>OOuq)c(9Kx2$caw`r$gxGcNXwwP$Q4)?0- ztPSrUzI-UVi8uU{6USy@;2B3+aADY)0sw%9@bG&vVgs7Slx8^sP%sHdf^v{xBEUQo zBx1P~a83Y#V?RazNfC66tl?*z3%j5ueys>x#5CcM0v3}D5Sj`gsFVOEu}lPv6A9U| z2nY@qs#8_oG_JuvPx-xVThE!yu%LsvDl2P(&IP)1wGA4(ac$YTX8$H4(|3XYGVj{+ zFKZvCP|Zb{QTVyFO&fakQz-4uR&HaP=TJqz)#0PtHrhV#ZusI|`ol39P=*U`NimvC zCWoX4j?-XUtD>?!0j0HP7L4A4I~+}e%xxw z69Rd}p&v)Y5bb+|`}P1pW*1cl@O1|`zUnT+ghQ4?AmcOwgvB{X003z} BCprKC literal 0 HcmV?d00001 diff --git a/DerivedDataReview/Logs/Package/LogStoreManifest.plist b/DerivedDataReview/Logs/Package/LogStoreManifest.plist new file mode 100644 index 00000000..35ceb865 --- /dev/null +++ b/DerivedDataReview/Logs/Package/LogStoreManifest.plist @@ -0,0 +1,47 @@ + + + + + logFormatVersion + 13 + logs + + D50ED115-45BF-4DD0-BB7E-A46C31531159 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + E + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 2 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + signature + Resolve Packages + timeStartedRecording + 807236581.56917596 + timeStoppedRecording + 807236581.71271205 + title + Resolve Packages + uniqueIdentifier + D50ED115-45BF-4DD0-BB7E-A46C31531159 + + + + diff --git a/Yattee/Core/AppEnvironment.swift b/Yattee/Core/AppEnvironment.swift index d5f0675c..494b477c 100644 --- a/Yattee/Core/AppEnvironment.swift +++ b/Yattee/Core/AppEnvironment.swift @@ -629,8 +629,13 @@ final class AppEnvironment { // logins on the same instance collapse onto the same one; and not on // the stored context, because that does not move when a credential // arrives or disappears and the app falls back to local and back again. + // Never while the identity is unresolved. With iCloud Drive off the + // scope drops to unresolved on every activation and comes back + // unchanged; the context reads as local in between, and treating that + // as a move would throw the caches away several times a day. "Cannot + // tell yet" is not grounds for anything destructive. let effective = dataContextValidator.effectiveContext - if effective != lastCachedContext { + if effective != lastCachedContext, credentialIdentityScope.isResolved { lastCachedContext = effective clearAccountScopedCaches() } diff --git a/Yattee/Services/SubscriptionFeedCache.swift b/Yattee/Services/SubscriptionFeedCache.swift index 1a206638..510ceff4 100644 --- a/Yattee/Services/SubscriptionFeedCache.swift +++ b/Yattee/Services/SubscriptionFeedCache.swift @@ -433,8 +433,8 @@ final class SubscriptionFeedCache { feedLoadState = .ready } catch { LoggingService.shared.error("Failed to fetch Piped feed: \(error.localizedDescription)", category: .general) - recordOutcome(error, backend: .piped, using: appEnvironment) guard isCurrentAccountsResult(generation) else { return } + recordOutcome(error, backend: .piped, using: appEnvironment) feedLoadState = .error(.networkError(error.localizedDescription)) } @@ -508,8 +508,8 @@ final class SubscriptionFeedCache { "Failed to fetch Invidious feed: \(error.localizedDescription)", category: .general ) - recordOutcome(error, backend: .invidious, using: appEnvironment) guard isCurrentAccountsResult(generation) else { return } + recordOutcome(error, backend: .invidious, using: appEnvironment) feedLoadState = .error(.networkError(error.localizedDescription)) } diff --git a/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift b/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift index 95823446..e02a03dc 100644 --- a/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift +++ b/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift @@ -74,13 +74,11 @@ final class SubscriptionAccountValidator { var availableAccounts: [SubscriptionAccount] { var accounts: [SubscriptionAccount] = [] - // Add local if Yattee Server is configured and enabled - let hasYatteeServer = instancesManager.instances.contains { - $0.type == .yatteeServer && $0.isEnabled - } - if hasYatteeServer { - accounts.append(.local) - } + // Local is always offered. Its *feed* needs a Yattee Server and says so + // itself when there is none; that is not a reason to make the user's + // own data unselectable — and since correction no longer moves away + // from local, leaving it out would make it unreachable once left. + accounts.append(.local) // Add logged-in and enabled Invidious instances for instance in instancesManager.instances { diff --git a/Yattee/Views/Instances/InstanceBrowseView.swift b/Yattee/Views/Instances/InstanceBrowseView.swift index cdaf7f56..1349c86d 100644 --- a/Yattee/Views/Instances/InstanceBrowseView.swift +++ b/Yattee/Views/Instances/InstanceBrowseView.swift @@ -447,6 +447,14 @@ struct InstanceBrowseView: View { .onReceive(NotificationCenter.default.publisher(for: .accountsDidChange)) { _ in refreshLoginState() } + // Selecting the other login on this instance changes whose feed and + // playlists these are. Nothing else here would notice: the instance, + // the tab and the "already has data" shortcut are all unchanged. + .onReceive(NotificationCenter.default.publisher(for: .dataContextDidChange)) { _ in + discardAccountScopedContent() + refreshLoginState() + Task { await startContentLoad() } + } .onChange(of: selectedTab) { _, _ in isLoading = true errorMessage = nil @@ -1179,6 +1187,15 @@ struct InstanceBrowseView: View { // MARK: - Data Loading + /// Drops what belonged to the previously selected login. + /// + /// Public browsing — popular and trending — is unauthenticated and stays. + private func discardAccountScopedContent() { + feedVideos = [] + feedSubscriptions = [] + userPlaylists = [] + } + private func refreshLoginState() { guard instance.supportsAuthentication else { return } isLoggedIn = appEnvironment?.isSignedIn(to: instance) ?? false diff --git a/Yattee/Views/Subscriptions/SubscriptionsView.swift b/Yattee/Views/Subscriptions/SubscriptionsView.swift index c6e1dc72..194b0a25 100644 --- a/Yattee/Views/Subscriptions/SubscriptionsView.swift +++ b/Yattee/Views/Subscriptions/SubscriptionsView.swift @@ -518,9 +518,14 @@ struct SubscriptionsView: View { .onReceive(NotificationCenter.default.publisher(for: .dataContextDidChange)) { _ in reloadForContextChange() } + // Instances count for the same reason: switching one off + // moves reads to local without touching the stored context + // or any account status. .onReceive(NotificationCenter.default.publisher(for: .accountStatusesDidChange)) { _ in - guard appEnvironment?.dataContextValidator.effectiveContext != loadedContext else { return } - reloadForContextChange() + reloadIfEffectiveContextMoved() + } + .onReceive(NotificationCenter.default.publisher(for: .instancesDidChange)) { _ in + reloadIfEffectiveContextMoved() } .task(id: instanceConfigurationID) { LoggingService.shared.debug("SubscriptionsView task triggered, instanceConfigurationID: \(instanceConfigurationID)", category: .general) @@ -1338,6 +1343,11 @@ struct SubscriptionsView: View { /// Loads subscriptions asynchronously from the current provider. /// For Invidious, this fetches from the API and creates temporary Subscription objects for UI. + private func reloadIfEffectiveContextMoved() { + guard appEnvironment?.dataContextValidator.effectiveContext != loadedContext else { return } + reloadForContextChange() + } + /// Drops what is on screen and reloads it for whatever context is current. private func reloadForContextChange() { loadedContext = appEnvironment?.dataContextValidator.effectiveContext