krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

65412ee9818bf251fd5caac231746b04c7eca23f

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-07-16T15:13:46Z

fix: honor forceRefresh for projects and system status on the dashboard

loadDashboard(forceRefresh:) fanned the flag out to five loaders, but
loadProjects and loadSystemStatusSnapshot dropped it: they called
fetchProjects() and snapshotResult() with no policy, so pull-to-refresh
returned cached projects and status while the other three sections
refreshed. SonarCloud flagged both params as unused (swift:S1172).

Thread forceRefresh through ProjectService.fetchProjects into the page
policy (refreshIgnoringCache when forced), and pass it to
snapshotResult, which already accepted it. ProjectsListView carried the
same latent bug via its own .refreshable — fixed there too now that
fetchProjects can force.
 Hutch/Networking/ProjectService.swift       | 8 ++++----
 Hutch/Views/Home/HomeViewModel.swift        | 4 ++--
 Hutch/Views/Projects/ProjectsListView.swift | 6 +++---
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/Hutch/Networking/ProjectService.swift b/Hutch/Networking/ProjectService.swift
index 44320ca..91dc339 100644
--- a/Hutch/Networking/ProjectService.swift
+++ b/Hutch/Networking/ProjectService.swift
@@ -269,15 +269,15 @@ struct ProjectService: Sendable {
         self.client = client
     }
 
-    func fetchProjects() async throws -> [Project] {
-        try await fetchProjectSummaries().map(Self.makeSummaryProject)
+    func fetchProjects(forceRefresh: Bool = false) async throws -> [Project] {
+        try await fetchProjectSummaries(forceRefresh: forceRefresh).map(Self.makeSummaryProject)
     }
 
     func fetchProjectDetail(rid: String) async throws -> Project {
         try await fetchProjectDetailPayload(rid: rid)
     }
 
-    private func fetchProjectSummaries() async throws -> [ProjectSummaryPayload] {
+    private func fetchProjectSummaries(forceRefresh: Bool) async throws -> [ProjectSummaryPayload] {
         var results: [ProjectSummaryPayload] = []
         var cursor: String?
 
@@ -295,7 +295,7 @@ struct ProjectService: Sendable {
                 cacheKey: APICacheKeys.projects(cursor: cursor),
                 resourceType: .userProfile,
                 ttl: APICacheTTLs.projectList,
-                policy: .cacheFirstThenRefresh
+                policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh
             )
             let response = cached.value
 
diff --git a/Hutch/Views/Home/HomeViewModel.swift b/Hutch/Views/Home/HomeViewModel.swift
index a3aed30..3789c11 100644
--- a/Hutch/Views/Home/HomeViewModel.swift
+++ b/Hutch/Views/Home/HomeViewModel.swift
@@ -683,7 +683,7 @@ final class HomeViewModel {
 
     private func loadProjects(forceRefresh: Bool) async -> Result<[Project], Error> {
         do {
-            return .success(try await projectService.fetchProjects())
+            return .success(try await projectService.fetchProjects(forceRefresh: forceRefresh))
         } catch {
             return .failure(error)
         }
@@ -716,7 +716,7 @@ final class HomeViewModel {
 
     private func loadSystemStatusSnapshot(forceRefresh: Bool) async -> Result<CachedSystemStatusValue<SystemStatusSnapshot>, Error> {
         do {
-            return .success(try await systemStatusRepository.snapshotResult())
+            return .success(try await systemStatusRepository.snapshotResult(forceRefresh: forceRefresh))
         } catch {
             return .failure(error)
         }
diff --git a/Hutch/Views/Projects/ProjectsListView.swift b/Hutch/Views/Projects/ProjectsListView.swift
index f6d5766..783417c 100644
--- a/Hutch/Views/Projects/ProjectsListView.swift
+++ b/Hutch/Views/Projects/ProjectsListView.swift
@@ -25,14 +25,14 @@ final class ProjectsListViewModel {
         }
     }
 
-    func loadProjects() async {
+    func loadProjects(forceRefresh: Bool = false) async {
         guard !isLoading else { return }
         isLoading = true
         error = nil
         defer { isLoading = false }
 
         do {
-            projects = try await service.fetchProjects()
+            projects = try await service.fetchProjects(forceRefresh: forceRefresh)
         } catch {
             if projects.isEmpty {
                 self.error = error.userFacingMessage
@@ -115,7 +115,7 @@ struct ProjectsListView: View {
             )
         )
         .refreshable {
-            await viewModel.loadProjects()
+            await viewModel.loadProjects(forceRefresh: true)
         }
         .connectivityOverlay(hasContent: !viewModel.projects.isEmpty) {
             await viewModel.loadProjects()