737201e0d65b

auto-retry failed thumbnail loads with backoff instead of manual Retry button

time
2026-07-15 22:27 UTC · 3 months ago
machine
koh2
agent
claude-opus-4
session
haven cleanup
parent
7b7a808a210c
1 file Sources/Haven/UI/Views/Components/WallpaperItem.swift+23 −15
Sources/Haven/UI/Views/Components/WallpaperItem.swift+23 −15
@@ -5,8 +5,13 @@55     let action: () -> Void66     @ObservedObject var favoritesManager = FavoritesManager.shared77     @State private var retryID = UUID()8+    @State private var attempt = 089     @State private var isHovering = false9-    10+11+    // Transient thumbnail failures (flaky network) are common; retry a few12+    // times with growing backoff before giving up quietly.13+    private let maxAttempts = 414+1015     var body: some View {1116         Button(action: action) {1217             AsyncImage(url: URL(string: wallpaper.thumbs.small)) { phase in@@ -20,20 +25,23 @@2025                         .resizable()2126                         .aspectRatio(contentMode: .fill)2227                 case .failure:23-                    Button {24-                        retryID = UUID()25-                    } label: {26-                        Rectangle()27-                            .fill(Color.red.opacity(0.1))28-                            .overlay(29-                                VStack {30-                                    Image(systemName: "exclamationmark.triangle")31-                                    Text("Retry")32-                                        .font(.caption)33-                                }34-                            )35-                    }36-                    .buttonStyle(.plain)28+                    Rectangle()29+                        .fill(Color.gray.opacity(0.2))30+                        .overlay(31+                            attempt < maxAttempts32+                                ? AnyView(ProgressView())33+                                : AnyView(Image(systemName: "photo")34+                                    .foregroundColor(.gray.opacity(0.6)))35+                        )36+                        .task {37+                            guard attempt < maxAttempts else { return }38+                            // 0.8s, 1.6s, 2.4s, 3.2s39+                            let delay = UInt64(attempt + 1) * 800_000_00040+                            try? await Task.sleep(nanoseconds: delay)41+                            guard !Task.isCancelled else { return }42+                            attempt += 143+                            retryID = UUID()44+                        }3745                 @unknown default:3846                     EmptyView()3947                 }

view file