Commit 3787cde3da

3787cde3dad82f80acce5059b8ffe8a65ed2bd9b

parent: d5dec7be8a

Unsigned

cmc <hello@cleberg.net> · 2026-07-16 15:13 UTC

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.

Layout: unified · split

Hutch/Networking/ProjectService.swift +4 −4
@@ -269,15 +269,15 @@ struct ProjectService: Sendable {
269269 self.client = client
270270 }
271271
272 func fetchProjects() async throws -> [Project] {
273 try await fetchProjectSummaries().map(Self.makeSummaryProject)
272 func fetchProjects(forceRefresh: Bool = false) async throws -> [Project] {
273 try await fetchProjectSummaries(forceRefresh: forceRefresh).map(Self.makeSummaryProject)
274274 }
275275
276276 func fetchProjectDetail(rid: String) async throws -> Project {
277277 try await fetchProjectDetailPayload(rid: rid)
278278 }
279279
280 private func fetchProjectSummaries() async throws -> [ProjectSummaryPayload] {
280 private func fetchProjectSummaries(forceRefresh: Bool) async throws -> [ProjectSummaryPayload] {
281281 var results: [ProjectSummaryPayload] = []
282282 var cursor: String?
283283
@@ -295,7 +295,7 @@ struct ProjectService: Sendable {
295295 cacheKey: APICacheKeys.projects(cursor: cursor),
296296 resourceType: .userProfile,
297297 ttl: APICacheTTLs.projectList,
298 policy: .cacheFirstThenRefresh
298 policy: forceRefresh ? .refreshIgnoringCache : .cacheFirstThenRefresh
299299 )
300300 let response = cached.value
301301
Hutch/Views/Home/HomeViewModel.swift +2 −2
@@ -683,7 +683,7 @@ final class HomeViewModel {
683683
684684 private func loadProjects(forceRefresh: Bool) async -> Result<[Project], Error> {
685685 do {
686 return .success(try await projectService.fetchProjects())
686 return .success(try await projectService.fetchProjects(forceRefresh: forceRefresh))
687687 } catch {
688688 return .failure(error)
689689 }
@@ -716,7 +716,7 @@ final class HomeViewModel {
716716
717717 private func loadSystemStatusSnapshot(forceRefresh: Bool) async -> Result<CachedSystemStatusValue<SystemStatusSnapshot>, Error> {
718718 do {
719 return .success(try await systemStatusRepository.snapshotResult())
719 return .success(try await systemStatusRepository.snapshotResult(forceRefresh: forceRefresh))
720720 } catch {
721721 return .failure(error)
722722 }
Hutch/Views/Projects/ProjectsListView.swift +3 −3
@@ -25,14 +25,14 @@ final class ProjectsListViewModel {
2525 }
2626 }
2727
28 func loadProjects() async {
28 func loadProjects(forceRefresh: Bool = false) async {
2929 guard !isLoading else { return }
3030 isLoading = true
3131 error = nil
3232 defer { isLoading = false }
3333
3434 do {
35 projects = try await service.fetchProjects()
35 projects = try await service.fetchProjects(forceRefresh: forceRefresh)
3636 } catch {
3737 if projects.isEmpty {
3838 self.error = error.userFacingMessage
@@ -115,7 +115,7 @@ struct ProjectsListView: View {
115115 )
116116 )
117117 .refreshable {
118 await viewModel.loadProjects()
118 await viewModel.loadProjects(forceRefresh: true)
119119 }
120120 .connectivityOverlay(hasContent: !viewModel.projects.isEmpty) {
121121 await viewModel.loadProjects()