summaryrefslogtreecommitdiff
path: root/Hutch/Views/Home
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-12 00:15:13 -0500
committerChristian Cleberg <[email protected]>2026-04-12 00:15:13 -0500
commitcb1ea5ea4f163d87053285d3fb7999b12a3558b9 (patch)
tree84b562d12e642ff494194387b1b4e1d15b065eb0 /Hutch/Views/Home
parent1c5a417277d986ee6fb7dc9dff0332338bdf6f3e (diff)
downloadhutch-2.13.1.tar.gz
hutch-2.13.1.tar.bz2
hutch-2.13.1.zip
harden system status and app reliabilityv2.13.1
Diffstat (limited to 'Hutch/Views/Home')
-rw-r--r--Hutch/Views/Home/HomeView.swift48
-rw-r--r--Hutch/Views/Home/HomeViewModel.swift28
2 files changed, 35 insertions, 41 deletions
diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift
index ab0cdf8..632e804 100644
--- a/Hutch/Views/Home/HomeView.swift
+++ b/Hutch/Views/Home/HomeView.swift
@@ -78,19 +78,25 @@ struct HomeView: View {
.refreshable {
await viewModel.loadDashboard()
}
+ .connectivityOverlay(hasContent: viewModel.hasDashboardContent) {
+ await viewModel.loadDashboard()
+ }
}
@ViewBuilder
private func systemStatusBannerSection(_ viewModel: HomeViewModel) -> some View {
- if let bannerTitle = viewModel.systemStatusBannerTitle {
- Section {
- NavigationLink {
- SystemStatusView()
- } label: {
- HomeSystemStatusBanner(title: bannerTitle)
- }
- .buttonStyle(.plain)
+ Section {
+ NavigationLink {
+ SystemStatusView()
+ } label: {
+ SystemStatusSummaryRow(
+ snapshot: viewModel.systemStatusSnapshot,
+ isLoading: viewModel.isLoadingSystemStatus,
+ errorMessage: viewModel.systemStatusErrorMessage,
+ isShowingStaleData: viewModel.isShowingStaleSystemStatus
+ )
}
+ .buttonStyle(.plain)
}
}
@@ -252,32 +258,6 @@ private struct HomeInboxToolbarIcon: View {
}
}
-private struct HomeSystemStatusBanner: View {
- let title: String
-
- var body: some View {
- HStack(spacing: 12) {
- Image(systemName: "exclamationmark.triangle.fill")
- .foregroundStyle(.orange)
- VStack(alignment: .leading, spacing: 2) {
- Text("SourceHut service disruption")
- .font(.subheadline.weight(.semibold))
- .foregroundStyle(.primary)
- Text(title)
- .font(.caption)
- .foregroundStyle(.secondary)
- .lineLimit(1)
- }
- Spacer()
- Image(systemName: "chevron.right")
- .font(.caption.weight(.semibold))
- .foregroundStyle(.tertiary)
- }
- .padding(.vertical, 4)
- .contentShape(Rectangle())
- }
-}
-
private struct HomeProjectRow: View {
let project: Project
diff --git a/Hutch/Views/Home/HomeViewModel.swift b/Hutch/Views/Home/HomeViewModel.swift
index a06df20..ba00a36 100644
--- a/Hutch/Views/Home/HomeViewModel.swift
+++ b/Hutch/Views/Home/HomeViewModel.swift
@@ -144,6 +144,9 @@ final class HomeViewModel {
var assignedTickets: [HomeAssignedTicket] = []
var recentBuilds: [HomeBuildItem] = []
private(set) var systemStatusSnapshot: SystemStatusSnapshot?
+ private(set) var isLoadingSystemStatus = false
+ private(set) var isShowingStaleSystemStatus = false
+ private(set) var systemStatusErrorMessage: String?
private(set) var hasUnreadInboxThreads = false
private(set) var unreadInboxThreadCount: Int?
private(set) var isLoadingProjects = false
@@ -279,8 +282,11 @@ final class HomeViewModel {
isLoadingProjects = true
isLoadingAssignedTickets = true
isLoadingRecentBuilds = true
+ isLoadingSystemStatus = true
assignedTicketsError = nil
recentBuildsError = nil
+ isShowingStaleSystemStatus = false
+ systemStatusErrorMessage = nil
async let projectsTask = loadProjects()
async let jobsTask = loadRecentJobs()
@@ -324,13 +330,21 @@ final class HomeViewModel {
unreadInboxThreadCount = await inboxUnreadTask
hasUnreadInboxThreads = (unreadInboxThreadCount ?? 0) > 0
- systemStatusSnapshot = await systemStatusTask
+ let systemStatusResult = await systemStatusTask
+ switch systemStatusResult {
+ case .success(let result):
+ systemStatusSnapshot = result.value
+ isShowingStaleSystemStatus = result.isStale
+ systemStatusErrorMessage = result.isStale ? result.refreshErrorMessage : nil
+ case .failure(let error):
+ systemStatusErrorMessage = error.userFacingMessage
+ }
+ isLoadingSystemStatus = false
persistNeedsAttentionSnapshot()
}
- var systemStatusBannerTitle: String? {
- guard let systemStatusSnapshot, systemStatusSnapshot.hasDisruption else { return nil }
- return systemStatusSnapshot.bannerSummary
+ var hasDashboardContent: Bool {
+ !projects.isEmpty || !assignedTickets.isEmpty || !recentBuilds.isEmpty || systemStatusSnapshot != nil
}
func resolveTicket(_ ticket: HomeAssignedTicket) async {
@@ -430,11 +444,11 @@ final class HomeViewModel {
}
}
- private func loadSystemStatusSnapshot() async -> SystemStatusSnapshot? {
+ private func loadSystemStatusSnapshot() async -> Result<CachedSystemStatusValue<SystemStatusSnapshot>, Error> {
do {
- return try await systemStatusRepository.snapshot()
+ return .success(try await systemStatusRepository.snapshotResult())
} catch {
- return systemStatusSnapshot
+ return .failure(error)
}
}