From e5cea1b6c804dadd7ad6bb1f51006f085950e3ab Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 14 Oct 2026 09:24:56 +0200 Subject: [PATCH] Fix a crash opening Search when a video is in history under several sources watchEntriesMap() built its dictionary with uniqueKeysWithValues, but the same video ID can be recorded under more than one source scope, which trapped on the duplicate key (3 crash logs from build 270, all in SearchView.loadWatchEntries). Keep the most recent entry instead. The subscription metadata map in Manage Channels had the same shape (a channel subscribed under several account scopes), as did the server history hydration map; both now tolerate duplicates. --- Yattee/Data/DataManager+WatchHistory.swift | 4 +++- Yattee/Views/Home/HomeView.swift | 5 +++-- Yattee/Views/Subscriptions/ManageChannelsView.swift | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Yattee/Data/DataManager+WatchHistory.swift b/Yattee/Data/DataManager+WatchHistory.swift index 05659c3c..cf7633e8 100644 --- a/Yattee/Data/DataManager+WatchHistory.swift +++ b/Yattee/Data/DataManager+WatchHistory.swift @@ -136,7 +136,9 @@ extension DataManager { /// Returns a dictionary of video ID to WatchEntry for efficient bulk lookup. func watchEntriesMap() -> [String: WatchEntry] { let entries = watchHistory(limit: 10000) - return Dictionary(uniqueKeysWithValues: entries.map { ($0.videoID, $0) }) + // The same video ID can be recorded under several source scopes; the + // history is most recent first, so the first entry is the one to keep. + return Dictionary(entries.map { ($0.videoID, $0) }, uniquingKeysWith: { first, _ in first }) } /// Gets the total count of watch history entries. diff --git a/Yattee/Views/Home/HomeView.swift b/Yattee/Views/Home/HomeView.swift index cf089b33..3614cc0a 100644 --- a/Yattee/Views/Home/HomeView.swift +++ b/Yattee/Views/Home/HomeView.swift @@ -1990,9 +1990,10 @@ struct HomeView: View { } let known: [String: Video] = Dictionary( - uniqueKeysWithValues: ids.compactMap { id in + ids.compactMap { id in dataManager?.watchEntry(for: id).map { (id, $0.toVideo()) } - } + }, + uniquingKeysWith: { first, _ in first } ) return await withTaskGroup(of: (Int, Video).self) { group in diff --git a/Yattee/Views/Subscriptions/ManageChannelsView.swift b/Yattee/Views/Subscriptions/ManageChannelsView.swift index a7941119..5bdde826 100644 --- a/Yattee/Views/Subscriptions/ManageChannelsView.swift +++ b/Yattee/Views/Subscriptions/ManageChannelsView.swift @@ -263,8 +263,10 @@ struct ManageChannelsView: View { if let syncChannels = subscriptionService?.fetchSubscriptionsSync() { channels = syncChannels } + // A channel can be subscribed under several account scopes. subscriptionMetadata = Dictionary( - uniqueKeysWithValues: (dataManager?.subscriptions() ?? []).map { ($0.channelID, $0) } + (dataManager?.subscriptions() ?? []).map { ($0.channelID, $0) }, + uniquingKeysWith: { first, _ in first } ) } .task {