From f5b86effd3bc7eefe8b35d072ab9b5ff4c0b75ce Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sat, 4 Jul 2026 17:04:09 +0200 Subject: [PATCH] Fix layout jump when subscriptions sidebar appears after load on macOS Load local-account subscriptions synchronously on appear so the channels sidebar is present in the first layout pass, and reserve the sidebar column with a spinner while async loads are in flight (gated by a persisted flag so users without enough channels never see a phantom column). --- .../Subscriptions/SubscriptionsView.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Yattee/Views/Subscriptions/SubscriptionsView.swift b/Yattee/Views/Subscriptions/SubscriptionsView.swift index 2e05999b..ddc4a42e 100644 --- a/Yattee/Views/Subscriptions/SubscriptionsView.swift +++ b/Yattee/Views/Subscriptions/SubscriptionsView.swift @@ -38,6 +38,9 @@ struct SubscriptionsView: View { @AppStorage("subscriptionsHideWatched") private var hideWatched = false @AppStorage("subscriptionsChannelStripSize") private var channelStripSize: ChannelStripSize = .normal @AppStorage("subscriptionsShowSidebar") private var showSidebar = true + /// Whether the last completed load produced enough channels for the sidebar, + /// so its space can be reserved before the next load finishes. + @AppStorage("subscriptionsSidebarExpected") private var sidebarExpected = false #if os(macOS) @AppStorage("subscriptionsMacOSSidebarWidth") private var macOSSidebarWidth = 240.0 @State private var macOSSidebarDragStartWidth: Double? @@ -364,6 +367,11 @@ struct SubscriptionsView: View { macOSChannelsSidebar .frame(width: clampedMacOSSidebarWidth) + macOSSidebarResizeHandle + } else if showSidebar && !subscriptionsLoaded && sidebarExpected { + macOSSidebarPlaceholder + .frame(width: clampedMacOSSidebarWidth) + macOSSidebarResizeHandle } @@ -476,6 +484,11 @@ struct SubscriptionsView: View { .liquidGlassSheetContent(sourceID: "subscriptionsViewOptions", in: sheetTransition) } #endif + .onAppear { + // Synchronous load for local accounts so the sidebar is + // present in the first layout pass (avoids layout jump). + loadSubscriptions() + } .task { await loadSubscriptionsAsync() loadWatchEntries() @@ -934,6 +947,12 @@ struct SubscriptionsView: View { .scrollContentBackground(.hidden) } + private var macOSSidebarPlaceholder: some View { + ProgressView() + .controlSize(.small) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + private var macOSSidebarResizeHandle: some View { ZStack { Rectangle() @@ -1299,6 +1318,7 @@ struct SubscriptionsView: View { if appEnvironment?.settingsManager.subscriptionAccount.type == .local { subscriptions = dataManager?.subscriptions() ?? [] subscriptionsLoaded = true + sidebarExpected = subscriptions.count > 1 } if let selectedID = selectedChannelID, @@ -1316,6 +1336,7 @@ struct SubscriptionsView: View { if appEnvironment.settingsManager.subscriptionAccount.type == .local { subscriptions = dataManager?.subscriptions() ?? [] subscriptionsLoaded = true + sidebarExpected = subscriptions.count > 1 return } @@ -1325,6 +1346,7 @@ struct SubscriptionsView: View { // Convert channels to Subscription objects for UI (not persisted) subscriptions = channels.map { Subscription.from(channel: $0) } subscriptionsLoaded = true + sidebarExpected = subscriptions.count > 1 } catch { LoggingService.shared.error( "Failed to load subscriptions: \(error.localizedDescription)", @@ -1332,6 +1354,7 @@ struct SubscriptionsView: View { ) subscriptions = [] subscriptionsLoaded = true + sidebarExpected = false } }