diff options
| author | Christian Cleberg <[email protected]> | 2026-07-16 17:08:45 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-16 17:08:45 -0500 |
| commit | a5d1064ff798cda8a237f2a11877634cd81f7ac3 (patch) | |
| tree | 2a57a9f152a05ae61307ca8be1220c2a1ee2d708 | |
| parent | cd8742a0b932627401467bef8b7d5c376798382e (diff) | |
| parent | 7b817a1d2aed0e8b89f36b410f2779f4a6b35407 (diff) | |
| download | hutch-a5d1064ff798cda8a237f2a11877634cd81f7ac3.tar.gz hutch-a5d1064ff798cda8a237f2a11877634cd81f7ac3.tar.bz2 hutch-a5d1064ff798cda8a237f2a11877634cd81f7ac3.zip | |
Merge pull request #20 from zerolabsco/home-status-badge
Move Home system status into a title-bar status badge
| -rw-r--r-- | Hutch.xcodeproj/project.pbxproj | 12 | ||||
| -rw-r--r-- | Hutch/Views/Home/HomeView.swift | 73 | ||||
| -rw-r--r-- | Hutch/Views/Home/HomeViewModel.swift | 23 | ||||
| -rw-r--r-- | ROADMAP.md | 1 |
4 files changed, 72 insertions, 37 deletions
diff --git a/Hutch.xcodeproj/project.pbxproj b/Hutch.xcodeproj/project.pbxproj index eba1ba7..409df4b 100644 --- a/Hutch.xcodeproj/project.pbxproj +++ b/Hutch.xcodeproj/project.pbxproj @@ -614,7 +614,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.8.1; + MARKETING_VERSION = 3.8.2; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -651,7 +651,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.8.1; + MARKETING_VERSION = 3.8.2; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -724,7 +724,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.8.1; + MARKETING_VERSION = 3.8.2; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -753,7 +753,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.8.1; + MARKETING_VERSION = 3.8.2; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -782,7 +782,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.8.1; + MARKETING_VERSION = 3.8.2; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -811,7 +811,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.8.1; + MARKETING_VERSION = 3.8.2; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift index 3e95f0b..f0ec9ff 100644 --- a/Hutch/Views/Home/HomeView.swift +++ b/Hutch/Views/Home/HomeView.swift @@ -10,6 +10,7 @@ struct HomeView: View { @State private var isOpeningRecentItem = false @State private var selectedPinnedProject: Project? @State private var selectedPinnedUser: User? + @State private var isShowingSystemStatus = false var body: some View { Group { @@ -20,6 +21,19 @@ struct HomeView: View { } } .navigationTitle("Home") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + isShowingSystemStatus = true + } label: { + HomeSystemStatusIndicator( + snapshot: viewModel?.systemStatusSnapshot, + isLoading: viewModel?.isLoadingSystemStatus ?? false + ) + } + .accessibilityLabel("System status") + } + } .navigationDestination(isPresented: Binding( get: { selectedPinnedProject != nil }, set: { isPresented in @@ -44,6 +58,9 @@ struct HomeView: View { UserProfileView(user: selectedPinnedUser) } } + .navigationDestination(isPresented: $isShowingSystemStatus) { + SystemStatusView() + } .task { guard let currentUser = appState.currentUser else { return } await ensureViewModel(currentUser: currentUser).loadDashboard() @@ -64,7 +81,6 @@ struct HomeView: View { @ViewBuilder private func content(_ viewModel: HomeViewModel) -> some View { List { - systemStatusSection(viewModel) workSection(viewModel) recentSection buildsSection(viewModel) @@ -84,26 +100,6 @@ struct HomeView: View { } } - @ViewBuilder - private func systemStatusSection(_ viewModel: HomeViewModel) -> some View { - if viewModel.systemStatusSnapshot?.hasDisruption == true { - Section { - NavigationLink { - SystemStatusView() - } label: { - SystemStatusSummaryRow( - snapshot: viewModel.systemStatusSnapshot, - isLoading: viewModel.isLoadingSystemStatus, - errorMessage: viewModel.systemStatusErrorMessage, - isShowingStaleData: viewModel.isShowingStaleSystemStatus - ) - } - .buttonStyle(.plain) - .themedRow() - } - } - } - private func workSection(_ viewModel: HomeViewModel) -> some View { Section("Work") { NavigationLink(value: HomeRoute.work(scope: .all)) { @@ -490,6 +486,41 @@ private struct HomeSummaryRow: View { } } +private struct HomeSystemStatusIndicator: View { + let snapshot: SystemStatusSnapshot? + let isLoading: Bool + + var body: some View { + if isLoading && snapshot == nil { + ProgressView() + .controlSize(.small) + } else { + Circle() + .fill(color) + .frame(width: 22, height: 22) + .overlay { + Image(systemName: glyph) + .font(.system(size: 11, weight: .bold)) + .foregroundStyle(.white) + } + } + } + + private var color: Color { + if let snapshot { + return snapshot.hasDisruption ? .orange : .green + } + return .secondary + } + + private var glyph: String { + if let snapshot { + return snapshot.hasDisruption ? "exclamationmark" : "checkmark" + } + return "questionmark" + } +} + private struct HomeRecentRow: View { let item: RecentActivityEntry diff --git a/Hutch/Views/Home/HomeViewModel.swift b/Hutch/Views/Home/HomeViewModel.swift index 3789c11..7779bea 100644 --- a/Hutch/Views/Home/HomeViewModel.swift +++ b/Hutch/Views/Home/HomeViewModel.swift @@ -365,6 +365,19 @@ final class HomeViewModel { async let inboxUnreadTask = loadInboxUnreadSnapshot(forceRefresh: forceRefresh) async let systemStatusTask = loadSystemStatusSnapshot(forceRefresh: forceRefresh) + // Resolve system status first so the Home title-bar status badge can + // settle from cache without waiting on the slower list data below. + 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 + let projectsResult = await projectsTask switch projectsResult { case .success(let projects): @@ -404,16 +417,6 @@ final class HomeViewModel { unreadInboxThreadCount = inboxUnreadSnapshot?.unreadCount unreadInboxThreads = inboxUnreadSnapshot?.threads ?? [] hasUnreadInboxThreads = (unreadInboxThreadCount ?? 0) > 0 - 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 lastRefreshed = Date() persistNeedsAttentionSnapshot() persistSystemStatusWidgetSnapshot() @@ -142,6 +142,7 @@ so "breaking change" does not apply. These buckets track *user-visible scale*. | Version | Contents | Why here | | --- | --- | --- | | v3.8.1 | SonarCloud triage; housekeeping | No behaviour change at all | +| v3.8.2 | Home system status moved to a title-bar status badge | Small UI relocation, no new surface | | v3.9.0 | "What's cooking" ingest; doc truth-up; deploy keys | Ships one feature, corrects the map | | v3.10.0 | hub.sr.ht writes: projects, discovery, `mailingListSubscribe` | Provisional — gated on what v3.9.0 finds | | v3.11.0 | Accessibility | Independent, device-verified | |
