From 45b0b11724f02669d9313776a0a34b48107ed321 Mon Sep 17 00:00:00 2001 From: Henry Chu Date: Wed, 11 Mar 2026 19:31:09 -0700 Subject: [PATCH] Fixes to address continuous spinner on api call failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UsageData.swift — Added ExtraUsage struct to match the updated API response shape (nullable utilization, is_enabled, etc.) * UsageViewModel.swift — Updated highestUtilization and displayLimits to handle the new ExtraUsage type * ContentView.swift — Fixed the network error state being hidden behind the toolbar (now shows an error screen with Retry + Open Settings buttons when there's no data) --- ClaudeUsage/Models/UsageData.swift | 12 +++++++++++- ClaudeUsage/Models/UsageViewModel.swift | 8 ++++++-- ClaudeUsage/Views/ContentView.swift | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/ClaudeUsage/Models/UsageData.swift b/ClaudeUsage/Models/UsageData.swift index e5bf01d..b7805a8 100644 --- a/ClaudeUsage/Models/UsageData.swift +++ b/ClaudeUsage/Models/UsageData.swift @@ -10,6 +10,16 @@ struct UsageLimit: Codable, Equatable { } } +struct ExtraUsage: Codable, Equatable { + let isEnabled: Bool + let utilization: Double? + + enum CodingKeys: String, CodingKey { + case isEnabled = "is_enabled" + case utilization + } +} + struct UsageData: Codable, Equatable { let fiveHour: UsageLimit let sevenDay: UsageLimit @@ -18,7 +28,7 @@ struct UsageData: Codable, Equatable { let sevenDayOauthApps: UsageLimit? let sevenDayCowork: UsageLimit? let iguanaNecktie: UsageLimit? - let extraUsage: UsageLimit? + let extraUsage: ExtraUsage? enum CodingKeys: String, CodingKey { case fiveHour = "five_hour" diff --git a/ClaudeUsage/Models/UsageViewModel.swift b/ClaudeUsage/Models/UsageViewModel.swift index 093a743..8e1170a 100644 --- a/ClaudeUsage/Models/UsageViewModel.swift +++ b/ClaudeUsage/Models/UsageViewModel.swift @@ -209,7 +209,9 @@ class UsageViewModel: ObservableObject { if let oa = data.sevenDayOauthApps { limits.append(oa) } if let c = data.sevenDayCowork { limits.append(c) } if let i = data.iguanaNecktie { limits.append(i) } - if let e = data.extraUsage { limits.append(e) } + if let e = data.extraUsage, let util = e.utilization { + limits.append(UsageLimit(utilization: util, resetsAt: nil)) + } return limits.map(\.utilization).max() ?? 0 } @@ -240,7 +242,9 @@ class UsageViewModel: ObservableObject { if let oa = data.sevenDayOauthApps { results.append(("OAuth Apps", oa)) } if let c = data.sevenDayCowork { results.append(("Cowork", c)) } if let i = data.iguanaNecktie { results.append(("Other", i)) } - if let e = data.extraUsage { results.append(("Extra Usage", e)) } + if let e = data.extraUsage, let util = e.utilization { + results.append(("Extra Usage", UsageLimit(utilization: util, resetsAt: nil))) + } return results } diff --git a/ClaudeUsage/Views/ContentView.swift b/ClaudeUsage/Views/ContentView.swift index 336467c..5bd4ba1 100644 --- a/ClaudeUsage/Views/ContentView.swift +++ b/ClaudeUsage/Views/ContentView.swift @@ -160,6 +160,27 @@ struct ContentView: View { Divider() toolbarButtons + } else if viewModel.errorState == .networkError { + VStack(spacing: 12) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 32)) + .foregroundColor(.orange) + Text("Network Error") + .font(.headline) + Text("Could not load usage data. Check your credentials and network connection.") + .font(.system(size: 12)) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + HStack(spacing: 8) { + Button("Retry") { + viewModel.fetchNow() + } + Button("Open Settings") { + openSettings() + } + } + } + .padding() } else if viewModel.authStatus == .connected { VStack(spacing: 12) { ProgressView()