krz/hutch

an ios client for sourcehut

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

e93972f39150e5e590e49aaf46a369c463277c30

verified · cmc

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

chore: clear the actionable SonarCloud code smells

- S1871: merge the identical .home / .recentActivity deep-link cases in
  RootView — recent activity is a section of Home, not its own screen.
- S1186: comment the two intentionally-empty Cancel buttons
  (PatchsetDetailView, TicketDetailView) and the empty URLProtocol
  stopLoading override in APICacheTests.
- S108: comment the expected-miss catch block in APICacheTests.
- S1172: rename the unused url parameter in mimeType(for:) to _.
- S4624: extract the nested template literal in the deep-link builders
  (background.js, content.js) to a pathSegment variable.

Left as Won't Fix, with reasons: the 35 hardcoded-URI warnings (a
one-forge client and its literal-URL tests), executeCached's 8 params
(38 call sites, no benefit), the forceRefresh S1172 pair (fixed as a
real bug instead), S1481 on ArtifactsView (false positive — $vm.error
is used), and S7785 (top-level await would break a classic content
script).
 Hutch/App/RootView.swift                                 | 8 +++-----
 Hutch/Views/Patchsets/PatchsetDetailView.swift           | 2 +-
 Hutch/Views/Repositories/RepositoryDetailViewModel.swift | 2 +-
 Hutch/Views/Tickets/TicketDetailView.swift               | 2 +-
 HutchSafariExtension/Resources/background.js             | 3 ++-
 HutchSafariExtension/Resources/content.js                | 3 ++-
 HutchTests/APICacheTests.swift                           | 3 ++-
 7 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/Hutch/App/RootView.swift b/Hutch/App/RootView.swift
index 10720ad..2e16718 100644
--- a/Hutch/App/RootView.swift
+++ b/Hutch/App/RootView.swift
@@ -198,11 +198,9 @@ struct RootView: View {
         }
 
         switch link {
-        case .home:
-            homePath = NavigationPath()
-            appState.selectedTab = .home
-
-        case .recentActivity:
+        // Recent activity is a section of the Home tab, not a screen of its
+        // own, so its intent/widget deep link lands on Home like .home does.
+        case .home, .recentActivity:
             homePath = NavigationPath()
             appState.selectedTab = .home
 
diff --git a/Hutch/Views/Patchsets/PatchsetDetailView.swift b/Hutch/Views/Patchsets/PatchsetDetailView.swift
index f560326..91aa417 100644
--- a/Hutch/Views/Patchsets/PatchsetDetailView.swift
+++ b/Hutch/Views/Patchsets/PatchsetDetailView.swift
@@ -71,7 +71,7 @@ struct PatchsetDetailView: View {
                         Task { await viewModel.updateStatus(to: status) }
                     }
                 }
-                Button("Cancel", role: .cancel) {}
+                Button("Cancel", role: .cancel) {} // dismisses the dialog; no action needed
             }
             .alert(
                 "Couldn't Update Patchset",
diff --git a/Hutch/Views/Repositories/RepositoryDetailViewModel.swift b/Hutch/Views/Repositories/RepositoryDetailViewModel.swift
index 9b6b942..82e1592 100644
--- a/Hutch/Views/Repositories/RepositoryDetailViewModel.swift
+++ b/Hutch/Views/Repositories/RepositoryDetailViewModel.swift
@@ -627,7 +627,7 @@ final class RepositoryDetailViewModel {
 
     /// Artifacts are release tarballs and signatures rather than media, so a
     /// generic binary type is honest more often than guessing from the extension.
-    private nonisolated static func mimeType(for url: URL) -> String {
+    private nonisolated static func mimeType(for _: URL) -> String {
         "application/octet-stream"
     }
 
diff --git a/Hutch/Views/Tickets/TicketDetailView.swift b/Hutch/Views/Tickets/TicketDetailView.swift
index b114fa6..1705a73 100644
--- a/Hutch/Views/Tickets/TicketDetailView.swift
+++ b/Hutch/Views/Tickets/TicketDetailView.swift
@@ -204,7 +204,7 @@ struct TicketDetailView: View {
                     }
                 }
             }
-            Button("Cancel", role: .cancel) {}
+            Button("Cancel", role: .cancel) {} // dismisses the dialog; no action needed
         } message: {
             Text("This permanently deletes the ticket and its comments. This cannot be undone.")
         }
diff --git a/HutchSafariExtension/Resources/background.js b/HutchSafariExtension/Resources/background.js
index 81bf8b6..0147de0 100644
--- a/HutchSafariExtension/Resources/background.js
+++ b/HutchSafariExtension/Resources/background.js
@@ -68,7 +68,8 @@ function hutchDeepLinkFor(rawURL) {
 
   const path = deepLinkPath(url.hostname, normalizedPath(url.pathname));
   const service = deepLinkService(url.hostname, path);
-  return `hutch://${service}${path ? `/${path}` : ""}${url.search}${url.hash}`;
+  const pathSegment = path ? `/${path}` : "";
+  return `hutch://${service}${pathSegment}${url.search}${url.hash}`;
 }
 
 function showUnsupportedMessage(tabId) {
diff --git a/HutchSafariExtension/Resources/content.js b/HutchSafariExtension/Resources/content.js
index dba7c5d..e5cb087 100644
--- a/HutchSafariExtension/Resources/content.js
+++ b/HutchSafariExtension/Resources/content.js
@@ -63,7 +63,8 @@ function hutchDeepLinkForLocation() {
 
   const path = hutchDeepLinkPath(location.hostname, hutchNormalizedPath(location.pathname));
   const service = hutchDeepLinkService(location.hostname, path);
-  return `hutch://${service}${path ? `/${path}` : ""}${location.search}${location.hash}`;
+  const pathSegment = path ? `/${path}` : "";
+  return `hutch://${service}${pathSegment}${location.search}${location.hash}`;
 }
 
 function storageGet(defaults) {
diff --git a/HutchTests/APICacheTests.swift b/HutchTests/APICacheTests.swift
index 89c6c53..a03e6f9 100644
--- a/HutchTests/APICacheTests.swift
+++ b/HutchTests/APICacheTests.swift
@@ -256,6 +256,7 @@ struct APICacheTests {
             _ = try await cache.read(cacheKey: key)
             Issue.record("Expected cache miss for \(key).")
         } catch APICacheError.miss {
+            // expected: a miss is the success path here
         } catch {
             Issue.record("Unexpected error for \(key): \(error).")
         }
@@ -293,7 +294,7 @@ private final class CachedURLProtocol: URLProtocol, @unchecked Sendable {
         }
     }
 
-    override func stopLoading() {}
+    override func stopLoading() {} // required override; nothing to tear down
 
     static func reset(responses: [CachedURLProtocolResponse], responseDelay: TimeInterval = 0) {
         Self.responses = responses