krz/hutch

an ios client for sourcehut

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

312e535f400f31714b3750de5222a1f6a8e5bcda

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-04-14T01:07:53Z

fix: sonarqube code smell fixes
 Hutch.xcodeproj/project.pbxproj                    | 16 ++--
 Hutch/Models/Git.swift                             |  4 +-
 Hutch/Models/Project.swift                         |  4 +-
 Hutch/Models/RepositorySummary.swift               | 63 +++++++-------
 Hutch/Networking/ProjectService.swift              | 10 +--
 Hutch/Networking/SRHTClient.swift                  | 48 +++++------
 Hutch/Views/Builds/BuildDetailView.swift           |  6 +-
 Hutch/Views/Builds/BuildListView.swift             |  6 +-
 Hutch/Views/Lookup/LookupView.swift                | 20 +++--
 Hutch/Views/Lookup/UserProfileViewModel.swift      | 20 +++--
 Hutch/Views/Pastes/PasteDetailView.swift           | 10 +--
 Hutch/Views/Pastes/PasteListView.swift             | 16 ++--
 Hutch/Views/Pastes/PasteListViewModel.swift        |  8 +-
 Hutch/Views/Repositories/FileTreeViewModel.swift   |  5 +-
 .../Repositories/HgRepositorySettingsView.swift    |  4 +-
 Hutch/Views/Repositories/ReadmeView.swift          |  2 +-
 Hutch/Views/Repositories/RepositoryListView.swift  |  6 +-
 .../Repositories/RepositoryListViewModel.swift     | 95 +++++++++-------------
 Hutch/Views/Repositories/RepositoryRowView.swift   |  8 +-
 .../Repositories/RepositorySettingsView.swift      |  8 +-
 .../Repositories/RepositorySettingsViewModel.swift | 20 +++--
 .../Repositories/RepositorySummarySupport.swift    |  4 +-
 Hutch/Views/Settings/SettingsViewModel.swift       | 10 ++-
 Hutch/Views/Tickets/TicketListView.swift           |  2 +
 Hutch/Views/Tickets/TrackerListView.swift          |  2 +-
 Hutch/Views/Tickets/TrackerManagementView.swift    |  4 +-
 HutchTests/AppStateTests.swift                     | 22 ++---
 HutchTests/ProjectTests.swift                      | 12 +--
 HutchTests/RepositoryACLViewModelTests.swift       | 20 +++--
 HutchTests/RepositoryListViewModelTests.swift      | 20 +++--
 HutchTests/RepositorySettingsViewModelTests.swift  | 20 +++--
 HutchTests/SRHTWebURLTests.swift                   | 22 ++---
 HutchTests/TrackerListViewModelTests.swift         |  2 +-
 33 files changed, 257 insertions(+), 262 deletions(-)

diff --git a/Hutch.xcodeproj/project.pbxproj b/Hutch.xcodeproj/project.pbxproj
index bbdb9e3..3162b32 100644
--- a/Hutch.xcodeproj/project.pbxproj
+++ b/Hutch.xcodeproj/project.pbxproj
@@ -515,7 +515,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 71;
+				CURRENT_PROJECT_VERSION = 72;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -532,7 +532,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.1.3;
+				MARKETING_VERSION = 3.1.4;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -552,7 +552,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 71;
+				CURRENT_PROJECT_VERSION = 72;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -569,7 +569,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.1.3;
+				MARKETING_VERSION = 3.1.4;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -632,7 +632,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 71;
+				CURRENT_PROJECT_VERSION = 72;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -642,7 +642,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 3.1.3;
+				MARKETING_VERSION = 3.1.4;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -661,7 +661,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 71;
+				CURRENT_PROJECT_VERSION = 72;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -671,7 +671,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 3.1.3;
+				MARKETING_VERSION = 3.1.4;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
diff --git a/Hutch/Models/Git.swift b/Hutch/Models/Git.swift
index 3629ce4..26a5e2d 100644
--- a/Hutch/Models/Git.swift
+++ b/Hutch/Models/Git.swift
@@ -4,9 +4,9 @@ import Foundation
 
 /// Repository visibility level.
 enum Visibility: String, Codable, Sendable {
-    case `public` = "PUBLIC"
+    case publicVisibility = "PUBLIC"
     case unlisted = "UNLISTED"
-    case `private` = "PRIVATE"
+    case privateVisibility = "PRIVATE"
 }
 
 /// Repository access mode.
diff --git a/Hutch/Models/Project.swift b/Hutch/Models/Project.swift
index bbca7d6..8f5aa68 100644
--- a/Hutch/Models/Project.swift
+++ b/Hutch/Models/Project.swift
@@ -230,11 +230,11 @@ extension String {
 extension Visibility {
     var displayName: String {
         switch self {
-        case .public:
+        case .publicVisibility:
             "Public"
         case .unlisted:
             "Unlisted"
-        case .private:
+        case .privateVisibility:
             "Private"
         }
     }
diff --git a/Hutch/Models/RepositorySummary.swift b/Hutch/Models/RepositorySummary.swift
index 3821aa2..b41589b 100644
--- a/Hutch/Models/RepositorySummary.swift
+++ b/Hutch/Models/RepositorySummary.swift
@@ -20,26 +20,29 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable {
         case head = "HEAD"
     }
 
-    init(
-        id: Int,
-        rid: String,
-        service: SRHTService,
-        name: String,
-        description: String?,
-        visibility: Visibility,
-        updated: Date,
-        owner: Entity,
-        head: Reference?
-    ) {
-        self.id = id
-        self.rid = rid
-        self.service = service
-        self.name = name
-        self.description = description
-        self.visibility = visibility
-        self.updated = updated
-        self.owner = owner
-        self.head = head
+    /// Grouped initializer fields (single parameter keeps APIs explicit without exceeding parameter-count limits).
+    struct Fields: Sendable, Hashable {
+        let id: Int
+        let rid: String
+        let service: SRHTService
+        let name: String
+        let description: String?
+        let visibility: Visibility
+        let updated: Date
+        let owner: Entity
+        let head: Reference?
+    }
+
+    init(fields: Fields) {
+        id = fields.id
+        rid = fields.rid
+        service = fields.service
+        name = fields.name
+        description = fields.description
+        visibility = fields.visibility
+        updated = fields.updated
+        owner = fields.owner
+        head = fields.head
     }
 
     init(from decoder: any Decoder) throws {
@@ -69,15 +72,17 @@ extension RepositorySummary {
         head: Reference? = nil
     ) -> RepositorySummary {
         RepositorySummary(
-            id: id,
-            rid: rid,
-            service: service,
-            name: name ?? self.name,
-            description: description ?? self.description,
-            visibility: visibility ?? self.visibility,
-            updated: updated ?? self.updated,
-            owner: owner,
-            head: head ?? self.head
+            fields: .init(
+                id: id,
+                rid: rid,
+                service: service,
+                name: name ?? self.name,
+                description: description ?? self.description,
+                visibility: visibility ?? self.visibility,
+                updated: updated ?? self.updated,
+                owner: owner,
+                head: head ?? self.head
+            )
         )
     }
 
diff --git a/Hutch/Networking/ProjectService.swift b/Hutch/Networking/ProjectService.swift
index bd8ccdc..5e0e061 100644
--- a/Hutch/Networking/ProjectService.swift
+++ b/Hutch/Networking/ProjectService.swift
@@ -49,7 +49,7 @@ private struct ProjectSummaryPayload: Decodable, Sendable {
         name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
         description = try container.decodeIfPresent(String.self, forKey: .description)
         website = try container.decodeIfPresent(String.self, forKey: .website)
-        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .public
+        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .publicVisibility
         tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
         updated = try container.decodeIfPresent(Date.self, forKey: .updated) ?? .distantPast
     }
@@ -90,7 +90,7 @@ private struct ProjectDetailPayload: Decodable, Sendable {
         name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
         description = try container.decodeIfPresent(String.self, forKey: .description)
         website = try container.decodeIfPresent(String.self, forKey: .website)
-        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .public
+        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .publicVisibility
         tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
         updated = try container.decodeIfPresent(Date.self, forKey: .updated) ?? .distantPast
         mailingLists = try container.decodeIfPresent(ProjectMailingListPage.self, forKey: .mailingLists) ?? .empty
@@ -126,7 +126,7 @@ private struct ProjectMailingListPayload: Decodable, Sendable {
         rid = try container.decode(String.self, forKey: .rid)
         name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
         description = try container.decodeIfPresent(String.self, forKey: .description)
-        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .public
+        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .publicVisibility
         owner = try container.decodeIfPresent(Entity.self, forKey: .owner) ?? Entity(canonicalName: "~unknown")
     }
 }
@@ -160,7 +160,7 @@ private struct ProjectSourcePayload: Decodable, Sendable {
         rid = try container.decode(String.self, forKey: .rid)
         name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
         description = try container.decodeIfPresent(String.self, forKey: .description)
-        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .public
+        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .publicVisibility
         owner = try container.decodeIfPresent(Entity.self, forKey: .owner) ?? Entity(canonicalName: "~unknown")
         repoType = try container.decodeIfPresent(Project.SourceRepo.RepoType.self, forKey: .repoType) ?? .git
     }
@@ -193,7 +193,7 @@ private struct ProjectTrackerPayload: Decodable, Sendable {
         rid = try container.decode(String.self, forKey: .rid)
         name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
         description = try container.decodeIfPresent(String.self, forKey: .description)
-        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .public
+        visibility = try container.decodeIfPresent(Visibility.self, forKey: .visibility) ?? .publicVisibility
         owner = try container.decodeIfPresent(Entity.self, forKey: .owner) ?? Entity(canonicalName: "~unknown")
     }
 }
diff --git a/Hutch/Networking/SRHTClient.swift b/Hutch/Networking/SRHTClient.swift
index 7e1f619..fa7b26b 100644
--- a/Hutch/Networking/SRHTClient.swift
+++ b/Hutch/Networking/SRHTClient.swift
@@ -57,7 +57,7 @@ final class SRHTClient: Sendable {
         service: SRHTService,
         query: String,
         variables: [String: any Sendable]? = nil,
-        responseType: T.Type
+        responseType _: T.Type
     ) async throws -> T {
         guard let token = _token.withLock({ $0 }), !token.isEmpty else {
             throw SRHTError.unauthorized
@@ -164,20 +164,14 @@ final class SRHTClient: Sendable {
     ///   - service: The target SourceHut service.
     ///   - query: The GraphQL mutation string.
     ///   - variables: Variables dict; the file variable should be set to `nil`.
-    ///   - fileVariablePath: The dot-separated path to the file variable (e.g. "input.avatar").
-    ///   - fileData: The raw file data (e.g. JPEG).
-    ///   - fileName: The file name to send (e.g. "avatar.jpg").
-    ///   - mimeType: The MIME type (e.g. "image/jpeg").
+    ///   - file: Multipart file payload (`variablePath` is the dot-separated GraphQL variable, e.g. `input.avatar`).
     ///   - responseType: The expected `Decodable` type nested under `data`.
     func executeMultipart<T: Decodable>(
         service: SRHTService,
         query: String,
         variables: [String: any Sendable],
-        fileVariablePath: String,
-        fileData: Data,
-        fileName: String,
-        mimeType: String,
-        responseType: T.Type
+        file: MultipartUploadFile,
+        responseType _: T.Type
     ) async throws -> T {
         guard let token = _token.withLock({ $0 }), !token.isEmpty else {
             throw SRHTError.unauthorized
@@ -197,8 +191,8 @@ final class SRHTClient: Sendable {
         )
         let operationsData = try encoder.encode(operationsBody)
 
-        // Build the map JSON: { "0": ["variables.<fileVariablePath>"] }
-        let mapDict = ["0": ["variables.\(fileVariablePath)"]]
+        // Build the map JSON: { "0": ["variables.<variablePath>"] }
+        let mapDict = ["0": ["variables.\(file.variablePath)"]]
         let mapData = try encoder.encode(mapDict)
 
         // Assemble multipart body
@@ -220,9 +214,9 @@ final class SRHTClient: Sendable {
 
         // Part: file
         body.append("--\(boundary)\r\n")
-        body.append("Content-Disposition: form-data; name=\"0\"; filename=\"\(fileName)\"\r\n")
-        body.append("Content-Type: \(mimeType)\r\n\r\n")
-        body.append(fileData)
+        body.append("Content-Disposition: form-data; name=\"0\"; filename=\"\(file.fileName)\"\r\n")
+        body.append("Content-Type: \(file.mimeType)\r\n\r\n")
+        body.append(file.fileData)
         body.append("\r\n")
 
         // Closing boundary
@@ -311,7 +305,7 @@ final class SRHTClient: Sendable {
         query: String,
         variables: [String: any Sendable],
         files: [MultipartUploadFile],
-        responseType: T.Type
+        responseType _: T.Type
     ) async throws -> T {
         guard let token = _token.withLock({ $0 }), !token.isEmpty else {
             throw SRHTError.unauthorized
@@ -408,15 +402,14 @@ final class SRHTClient: Sendable {
         service: SRHTService,
         query: String,
         variables: [String: any Sendable]? = nil,
-        responseType: T.Type,
+        responseType _: T.Type,
         cacheKey: String
     ) async throws -> T {
         // Try cache first
-        if let cachedData = responseCache.get(forKey: cacheKey) {
-            if let cached = try? decoder.decode(GraphQLResponse<T>.self, from: cachedData),
-               let data = cached.data {
-                return data
-            }
+        if let cachedData = responseCache.get(forKey: cacheKey),
+           let cached = try? decoder.decode(GraphQLResponse<T>.self, from: cachedData),
+           let data = cached.data {
+            return data
         }
 
         // No cache hit — fetch normally
@@ -434,7 +427,7 @@ final class SRHTClient: Sendable {
         service: SRHTService,
         query: String,
         variables: [String: any Sendable]? = nil,
-        responseType: T.Type,
+        responseType _: T.Type,
         cacheKey: String
     ) async throws -> T {
         guard let token = _token.withLock({ $0 }), !token.isEmpty else {
@@ -580,8 +573,7 @@ final class SRHTClient: Sendable {
         service: SRHTService,
         query: String,
         variables: [String: any Sendable]? = nil,
-        resultKeyPath: String,
-        type: T.Type
+        resultKeyPath: String
     ) -> SRHTPaginatedSequence<T> {
         SRHTPaginatedSequence(
             client: self,
@@ -598,16 +590,14 @@ final class SRHTClient: Sendable {
         service: SRHTService,
         query: String,
         variables: [String: any Sendable]? = nil,
-        resultKeyPath: String,
-        type: T.Type
+        resultKeyPath: String
     ) async throws -> [T] {
         var all: [T] = []
         for try await element in paginated(
             service: service,
             query: query,
             variables: variables,
-            resultKeyPath: resultKeyPath,
-            type: type
+            resultKeyPath: resultKeyPath
         ) {
             all.append(element)
         }
diff --git a/Hutch/Views/Builds/BuildDetailView.swift b/Hutch/Views/Builds/BuildDetailView.swift
index 46fb42d..334dff7 100644
--- a/Hutch/Views/Builds/BuildDetailView.swift
+++ b/Hutch/Views/Builds/BuildDetailView.swift
@@ -468,7 +468,7 @@ private struct EditResubmitBuildSheet: View {
         _manifest = State(initialValue: job.manifest ?? "")
         _tagsText = State(initialValue: job.tags.joined(separator: ", "))
         _note = State(initialValue: job.note ?? "")
-        _visibility = State(initialValue: job.visibility ?? .public)
+        _visibility = State(initialValue: job.visibility ?? .publicVisibility)
     }
 
     var body: some View {
@@ -491,9 +491,9 @@ private struct EditResubmitBuildSheet: View {
                         .autocorrectionDisabled()
                         .themedRow()
                     Picker("Visibility", selection: $visibility) {
-                        Text("Public").tag(Visibility.public)
+                        Text("Public").tag(Visibility.publicVisibility)
                         Text("Unlisted").tag(Visibility.unlisted)
-                        Text("Private").tag(Visibility.private)
+                        Text("Private").tag(Visibility.privateVisibility)
                     }
                     .themedRow()
                     Toggle("Start build now", isOn: $execute)
diff --git a/Hutch/Views/Builds/BuildListView.swift b/Hutch/Views/Builds/BuildListView.swift
index fc91ea1..8de2220 100644
--- a/Hutch/Views/Builds/BuildListView.swift
+++ b/Hutch/Views/Builds/BuildListView.swift
@@ -265,7 +265,7 @@ private struct SubmitBuildSheet: View {
     @State private var note = ""
     @State private var secrets = false
     @State private var execute = true
-    @State private var visibility: Visibility = .public
+    @State private var visibility: Visibility = .publicVisibility
 
     init(viewModel: BuildListViewModel, onSubmitted: @escaping (Int) -> Void) {
         self.viewModel = viewModel
@@ -293,9 +293,9 @@ private struct SubmitBuildSheet: View {
                         .autocorrectionDisabled()
                         .themedRow()
                     Picker("Visibility", selection: $visibility) {
-                        Text("Public").tag(Visibility.public)
+                        Text("Public").tag(Visibility.publicVisibility)
                         Text("Unlisted").tag(Visibility.unlisted)
-                        Text("Private").tag(Visibility.private)
+                        Text("Private").tag(Visibility.privateVisibility)
                     }
                     .themedRow()
                     Toggle("Start build now", isOn: $execute)
diff --git a/Hutch/Views/Lookup/LookupView.swift b/Hutch/Views/Lookup/LookupView.swift
index f45082d..64ca39d 100644
--- a/Hutch/Views/Lookup/LookupView.swift
+++ b/Hutch/Views/Lookup/LookupView.swift
@@ -233,15 +233,17 @@ final class LookupViewModel {
 
         let repository = try await appState.resolveRepository(owner: owner, name: name, service: service)
         let resolvedRepository = RepositorySummary(
-            id: repository.id,
-            rid: repository.rid,
-            service: service,
-            name: repository.name,
-            description: repository.description,
-            visibility: repository.visibility,
-            updated: repository.updated,
-            owner: repository.owner,
-            head: repository.head
+            fields: .init(
+                id: repository.id,
+                rid: repository.rid,
+                service: service,
+                name: repository.name,
+                description: repository.description,
+                visibility: repository.visibility,
+                updated: repository.updated,
+                owner: repository.owner,
+                head: repository.head
+            )
         )
 
         return .repository(resolvedRepository)
diff --git a/Hutch/Views/Lookup/UserProfileViewModel.swift b/Hutch/Views/Lookup/UserProfileViewModel.swift
index b88d36a..0e503ce 100644
--- a/Hutch/Views/Lookup/UserProfileViewModel.swift
+++ b/Hutch/Views/Lookup/UserProfileViewModel.swift
@@ -179,15 +179,17 @@ final class UserProfileViewModel {
 
         func repositorySummary(service: SRHTService) -> RepositorySummary {
             RepositorySummary(
-                id: id,
-                rid: rid,
-                service: service,
-                name: name,
-                description: description,
-                visibility: visibility,
-                updated: updated,
-                owner: owner,
-                head: head
+                fields: .init(
+                    id: id,
+                    rid: rid,
+                    service: service,
+                    name: name,
+                    description: description,
+                    visibility: visibility,
+                    updated: updated,
+                    owner: owner,
+                    head: head
+                )
             )
         }
     }
diff --git a/Hutch/Views/Pastes/PasteDetailView.swift b/Hutch/Views/Pastes/PasteDetailView.swift
index c6fe11a..49d4fa6 100644
--- a/Hutch/Views/Pastes/PasteDetailView.swift
+++ b/Hutch/Views/Pastes/PasteDetailView.swift
@@ -442,27 +442,27 @@ private struct PasteVisibilitySheet: View {
     }
 
     private var visibilityOptions: [Visibility] {
-        [.public, .unlisted, .private]
+        [.publicVisibility, .unlisted, .privateVisibility]
     }
 
     private func title(for visibility: Visibility) -> String {
         switch visibility {
-        case .public:
+        case .publicVisibility:
             "Public"
         case .unlisted:
             "Unlisted"
-        case .private:
+        case .privateVisibility:
             "Private"
         }
     }
 
     private func description(for visibility: Visibility) -> String {
         switch visibility {
-        case .public:
+        case .publicVisibility:
             "Visible to everyone and listed on your profile."
         case .unlisted:
             "Visible to anyone with the URL, but not listed on your profile."
-        case .private:
+        case .privateVisibility:
             "Visible only to explicitly allowed viewers."
         }
     }
diff --git a/Hutch/Views/Pastes/PasteListView.swift b/Hutch/Views/Pastes/PasteListView.swift
index dfd472b..17bb8ac 100644
--- a/Hutch/Views/Pastes/PasteListView.swift
+++ b/Hutch/Views/Pastes/PasteListView.swift
@@ -179,33 +179,33 @@ struct PasteListView: View {
 
     private func nextVisibilityLabel(for visibility: Visibility) -> String {
         switch visibility {
-        case .public:
+        case .publicVisibility:
             return "Make Unlisted"
         case .unlisted:
             return "Make Private"
-        case .private:
+        case .privateVisibility:
             return "Make Public"
         }
     }
 
     private func nextVisibilityIcon(for visibility: Visibility) -> String {
         switch visibility {
-        case .public:
+        case .publicVisibility:
             return "eye.slash"
         case .unlisted:
             return "lock"
-        case .private:
+        case .privateVisibility:
             return "globe"
         }
     }
 
     private func nextVisibilityColor(for visibility: Visibility) -> Color {
         switch visibility {
-        case .public:
+        case .publicVisibility:
             return .orange
         case .unlisted:
             return .red
-        case .private:
+        case .privateVisibility:
             return .green
         }
     }
@@ -316,9 +316,9 @@ private struct CreatePasteSheet: View {
 
                 Section("Visibility") {
                     Picker("Visibility", selection: $visibility) {
-                        Text("Public").tag(Visibility.public)
+                        Text("Public").tag(Visibility.publicVisibility)
                         Text("Unlisted").tag(Visibility.unlisted)
-                        Text("Private").tag(Visibility.private)
+                        Text("Private").tag(Visibility.privateVisibility)
                     }
                     .themedRow()
                 }
diff --git a/Hutch/Views/Pastes/PasteListViewModel.swift b/Hutch/Views/Pastes/PasteListViewModel.swift
index 75f2f4b..640e31b 100644
--- a/Hutch/Views/Pastes/PasteListViewModel.swift
+++ b/Hutch/Views/Pastes/PasteListViewModel.swift
@@ -131,12 +131,12 @@ final class PasteListViewModel {
     func cycleVisibility(for paste: Paste) async {
         let next: Visibility
         switch paste.visibility {
-        case .public:
+        case .publicVisibility:
             next = .unlisted
         case .unlisted:
-            next = .private
-        case .private:
-            next = .public
+            next = .privateVisibility
+        case .privateVisibility:
+            next = .publicVisibility
         }
 
         let original = pastes
diff --git a/Hutch/Views/Repositories/FileTreeViewModel.swift b/Hutch/Views/Repositories/FileTreeViewModel.swift
index c6fecbe..98211bf 100644
--- a/Hutch/Views/Repositories/FileTreeViewModel.swift
+++ b/Hutch/Views/Repositories/FileTreeViewModel.swift
@@ -327,14 +327,11 @@ final class FileTreeViewModel {
             }
 
         case .binaryBlob(let blob):
-            if blob.content != nil {
+            if blob.content != nil || blob.id == nil {
                 viewingEntry = entry
                 viewingObject = object
             } else if let blobId = blob.id {
                 await loadBlob(entry: entry, blobId: blobId)
-            } else {
-                viewingEntry = entry
-                viewingObject = object
             }
 
         case .unknown:
diff --git a/Hutch/Views/Repositories/HgRepositorySettingsView.swift b/Hutch/Views/Repositories/HgRepositorySettingsView.swift
index e3c97e0..7b57505 100644
--- a/Hutch/Views/Repositories/HgRepositorySettingsView.swift
+++ b/Hutch/Views/Repositories/HgRepositorySettingsView.swift
@@ -121,9 +121,9 @@ struct HgRepositorySettingsView: View {
                 .themedRow()
 
             Picker("Visibility", selection: Bindable(viewModel).editedVisibility) {
-                Text("Public").tag(Visibility.public)
+                Text("Public").tag(Visibility.publicVisibility)
                 Text("Unlisted").tag(Visibility.unlisted)
-                Text("Private").tag(Visibility.private)
+                Text("Private").tag(Visibility.privateVisibility)
             }
             .themedRow()
 
diff --git a/Hutch/Views/Repositories/ReadmeView.swift b/Hutch/Views/Repositories/ReadmeView.swift
index 21d7a74..1a72b70 100644
--- a/Hutch/Views/Repositories/ReadmeView.swift
+++ b/Hutch/Views/Repositories/ReadmeView.swift
@@ -1828,7 +1828,7 @@ private final class HTMLWebViewCoordinator: NSObject, WKNavigationDelegate, @unc
     }
 
     func webView(
-        _ webView: WKWebView,
+        _: WKWebView,
         decidePolicyFor navigationAction: WKNavigationAction,
         decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void
     ) {
diff --git a/Hutch/Views/Repositories/RepositoryListView.swift b/Hutch/Views/Repositories/RepositoryListView.swift
index 08ebb5c..04a7084 100644
--- a/Hutch/Views/Repositories/RepositoryListView.swift
+++ b/Hutch/Views/Repositories/RepositoryListView.swift
@@ -214,7 +214,7 @@ private struct CreateRepositorySheet: View {
     @State private var name = ""
     @State private var description = ""
     @State private var cloneURL = ""
-    @State private var visibility: Visibility = .public
+    @State private var visibility: Visibility = .publicVisibility
     @State private var service: RepositoryCreationService = .git
 
     var body: some View {
@@ -235,9 +235,9 @@ private struct CreateRepositorySheet: View {
                         .lineLimit(2...4)
                         .themedRow()
                     Picker("Visibility", selection: $visibility) {
-                        Text("Public").tag(Visibility.public)
+                        Text("Public").tag(Visibility.publicVisibility)
                         Text("Unlisted").tag(Visibility.unlisted)
-                        Text("Private").tag(Visibility.private)
+                        Text("Private").tag(Visibility.privateVisibility)
                     }
                     .themedRow()
                 }
diff --git a/Hutch/Views/Repositories/RepositoryListViewModel.swift b/Hutch/Views/Repositories/RepositoryListViewModel.swift
index 1e830bb..61053a0 100644
--- a/Hutch/Views/Repositories/RepositoryListViewModel.swift
+++ b/Hutch/Views/Repositories/RepositoryListViewModel.swift
@@ -397,15 +397,17 @@ final class RepositoryListViewModel {
 
         func repositorySummary(service: SRHTService) -> RepositorySummary {
             RepositorySummary(
-                id: id,
-                rid: rid,
-                service: service,
-                name: name,
-                description: description,
-                visibility: visibility,
-                updated: updated,
-                owner: owner,
-                head: head
+                fields: .init(
+                    id: id,
+                    rid: rid,
+                    service: service,
+                    name: name,
+                    description: description,
+                    visibility: visibility,
+                    updated: updated,
+                    owner: owner,
+                    head: head
+                )
             )
         }
     }
@@ -422,15 +424,17 @@ final class RepositoryListViewModel {
 
         func repositorySummary(service: SRHTService) -> RepositorySummary {
             RepositorySummary(
-                id: id,
-                rid: rid,
-                service: service,
-                name: name,
-                description: description,
-                visibility: visibility,
-                updated: updated,
-                owner: owner,
-                head: tip.map { Reference(name: $0.branch, target: nil) }
+                fields: .init(
+                    id: id,
+                    rid: rid,
+                    service: service,
+                    name: name,
+                    description: description,
+                    visibility: visibility,
+                    updated: updated,
+                    owner: owner,
+                    head: tip.map { Reference(name: $0.branch, target: nil) }
+                )
             )
         }
     }
@@ -463,17 +467,7 @@ final class RepositoryListViewModel {
         }
 
         if useCache && cursor == nil {
-            switch service {
-            case .git:
-                let result = try await client.executeAndCache(
-                    service: service,
-                    query: Self.gitQuery,
-                    variables: variables.isEmpty ? nil : variables,
-                    responseType: RepositoriesResponse.self,
-                    cacheKey: cacheKey(for: service)
-                )
-                return result.repositories ?? Self.emptyPage
-            case .hg:
+            if service == .hg {
                 let hgVariables = cursor.map { ["cursor": $0 as any Sendable] }
                 let result = try await client.executeAndCache(
                     service: service,
@@ -497,27 +491,17 @@ final class RepositoryListViewModel {
                     } ?? [],
                     cursor: result.repositories?.cursor
                 )
-            default:
-                let result = try await client.executeAndCache(
-                    service: service,
-                    query: Self.gitQuery,
-                    variables: variables.isEmpty ? nil : variables,
-                    responseType: RepositoriesResponse.self,
-                    cacheKey: cacheKey(for: service)
-                )
-                return result.repositories ?? Self.emptyPage
             }
+            let result = try await client.executeAndCache(
+                service: service,
+                query: Self.gitQuery,
+                variables: variables.isEmpty ? nil : variables,
+                responseType: RepositoriesResponse.self,
+                cacheKey: cacheKey(for: service)
+            )
+            return result.repositories ?? Self.emptyPage
         } else {
-            switch service {
-            case .git:
-                let result = try await client.execute(
-                    service: service,
-                    query: Self.gitQuery,
-                    variables: variables.isEmpty ? nil : variables,
-                    responseType: RepositoriesResponse.self
-                )
-                return result.repositories ?? Self.emptyPage
-            case .hg:
+            if service == .hg {
                 let hgVariables = cursor.map { ["cursor": $0 as any Sendable] }
                 let result = try await client.execute(
                     service: service,
@@ -540,15 +524,14 @@ final class RepositoryListViewModel {
                     } ?? [],
                     cursor: result.repositories?.cursor
                 )
-            default:
-                let result = try await client.execute(
-                    service: service,
-                    query: Self.gitQuery,
-                    variables: variables.isEmpty ? nil : variables,
-                    responseType: RepositoriesResponse.self
-                )
-                return result.repositories ?? Self.emptyPage
             }
+            let result = try await client.execute(
+                service: service,
+                query: Self.gitQuery,
+                variables: variables.isEmpty ? nil : variables,
+                responseType: RepositoriesResponse.self
+            )
+            return result.repositories ?? Self.emptyPage
         }
     }
 
diff --git a/Hutch/Views/Repositories/RepositoryRowView.swift b/Hutch/Views/Repositories/RepositoryRowView.swift
index a2d120b..86c8889 100644
--- a/Hutch/Views/Repositories/RepositoryRowView.swift
+++ b/Hutch/Views/Repositories/RepositoryRowView.swift
@@ -154,17 +154,17 @@ struct VisibilityBadge: View {
 
     private var label: String {
         switch visibility {
-        case .public:   "PUBLIC"
+        case .publicVisibility:   "PUBLIC"
         case .unlisted: "UNLISTED"
-        case .private:  "PRIVATE"
+        case .privateVisibility:  "PRIVATE"
         }
     }
 
     private var color: Color {
         switch visibility {
-        case .public:   .green
+        case .publicVisibility:   .green
         case .unlisted: .orange
-        case .private:  .red
+        case .privateVisibility:  .red
         }
     }
 }
diff --git a/Hutch/Views/Repositories/RepositorySettingsView.swift b/Hutch/Views/Repositories/RepositorySettingsView.swift
index 44b50ae..9f172a8 100644
--- a/Hutch/Views/Repositories/RepositorySettingsView.swift
+++ b/Hutch/Views/Repositories/RepositorySettingsView.swift
@@ -227,9 +227,9 @@ struct RepositorySettingsView: View {
     private func visibilitySection(_ viewModel: RepositorySettingsViewModel) -> some View {
         Section {
             Picker("Visibility", selection: Bindable(viewModel).editedVisibility) {
-                Text("Public").tag(Visibility.public)
+                Text("Public").tag(Visibility.publicVisibility)
                 Text("Unlisted").tag(Visibility.unlisted)
-                Text("Private").tag(Visibility.private)
+                Text("Private").tag(Visibility.privateVisibility)
             }
             .themedRow()
 
@@ -283,11 +283,11 @@ struct RepositorySettingsView: View {
 
     private func visibilityConfirmationMessage(for viewModel: RepositorySettingsViewModel) -> String {
         switch viewModel.editedVisibility {
-        case .public:
+        case .publicVisibility:
             "Anyone will be able to find and view this repository."
         case .unlisted:
             "People with the link can view this repository, but it won't appear in public listings."
-        case .private:
+        case .privateVisibility:
             "Only people with explicit access will be able to view this repository."
         }
     }
diff --git a/Hutch/Views/Repositories/RepositorySettingsViewModel.swift b/Hutch/Views/Repositories/RepositorySettingsViewModel.swift
index 8bd80a1..6233bec 100644
--- a/Hutch/Views/Repositories/RepositorySettingsViewModel.swift
+++ b/Hutch/Views/Repositories/RepositorySettingsViewModel.swift
@@ -20,15 +20,17 @@ private struct UpdatedRepositoryPayload: Decodable, Sendable {
 
     func repositorySummary(using owner: Entity, service: SRHTService) -> RepositorySummary {
         RepositorySummary(
-            id: id,
-            rid: rid,
-            service: service,
-            name: name,
-            description: description,
-            visibility: visibility,
-            updated: updated,
-            owner: owner,
-            head: head
+            fields: .init(
+                id: id,
+                rid: rid,
+                service: service,
+                name: name,
+                description: description,
+                visibility: visibility,
+                updated: updated,
+                owner: owner,
+                head: head
+            )
         )
     }
 }
diff --git a/Hutch/Views/Repositories/RepositorySummarySupport.swift b/Hutch/Views/Repositories/RepositorySummarySupport.swift
index 741ff39..393e3ff 100644
--- a/Hutch/Views/Repositories/RepositorySummarySupport.swift
+++ b/Hutch/Views/Repositories/RepositorySummarySupport.swift
@@ -37,11 +37,11 @@ func repositoryCloneURLs(for repository: RepositorySummary) -> RepositoryCloneUR
 
 func repositoryVisibilityLabel(_ visibility: Visibility) -> String {
     switch visibility {
-    case .public:
+    case .publicVisibility:
         return "Public"
     case .unlisted:
         return "Unlisted"
-    case .private:
+    case .privateVisibility:
         return "Private"
     }
 }
diff --git a/Hutch/Views/Settings/SettingsViewModel.swift b/Hutch/Views/Settings/SettingsViewModel.swift
index 29103f6..0b55e6a 100644
--- a/Hutch/Views/Settings/SettingsViewModel.swift
+++ b/Hutch/Views/Settings/SettingsViewModel.swift
@@ -228,10 +228,12 @@ final class SettingsViewModel {
                 service: .meta,
                 query: Self.updateUserMutation,
                 variables: ["input": input],
-                fileVariablePath: "input.avatar",
-                fileData: jpegData,
-                fileName: "avatar.jpg",
-                mimeType: "image/jpeg",
+                file: MultipartUploadFile(
+                    variablePath: "input.avatar",
+                    fileData: jpegData,
+                    fileName: "avatar.jpg",
+                    mimeType: "image/jpeg"
+                ),
                 responseType: UpdateUserResponse.self
             )
             let updated = result.updateUser
diff --git a/Hutch/Views/Tickets/TicketListView.swift b/Hutch/Views/Tickets/TicketListView.swift
index 940a282..afee96d 100644
--- a/Hutch/Views/Tickets/TicketListView.swift
+++ b/Hutch/Views/Tickets/TicketListView.swift
@@ -1400,11 +1400,13 @@ struct FlowLayout: Layout {
     var spacing: CGFloat = 4
 
     func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
+        _ = cache
         let result = layoutSubviews(proposal: proposal, subviews: subviews)
         return result.size
     }
 
     func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
+        _ = cache
         let result = layoutSubviews(proposal: proposal, subviews: subviews)
         for (index, position) in result.positions.enumerated() {
             subviews[index].place(
diff --git a/Hutch/Views/Tickets/TrackerListView.swift b/Hutch/Views/Tickets/TrackerListView.swift
index 9ca3340..5deb513 100644
--- a/Hutch/Views/Tickets/TrackerListView.swift
+++ b/Hutch/Views/Tickets/TrackerListView.swift
@@ -37,7 +37,7 @@ struct TrackerListView: View {
                     error: viewModel.error,
                     initialName: "",
                     initialDescription: "",
-                    initialVisibility: .public
+                    initialVisibility: .publicVisibility
                 ) { name, description, visibility in
                     if let tracker = await viewModel.createTracker(
                         name: name,
diff --git a/Hutch/Views/Tickets/TrackerManagementView.swift b/Hutch/Views/Tickets/TrackerManagementView.swift
index 4e04bbd..fad1ae8 100644
--- a/Hutch/Views/Tickets/TrackerManagementView.swift
+++ b/Hutch/Views/Tickets/TrackerManagementView.swift
@@ -659,9 +659,9 @@ struct TrackerEditorSheet: View {
                         .lineLimit(2...4)
                         .themedRow()
                     Picker("Visibility", selection: $visibility) {
-                        Text("Public").tag(Visibility.public)
+                        Text("Public").tag(Visibility.publicVisibility)
                         Text("Unlisted").tag(Visibility.unlisted)
-                        Text("Private").tag(Visibility.private)
+                        Text("Private").tag(Visibility.privateVisibility)
                     }
                     .themedRow()
                 }
diff --git a/HutchTests/AppStateTests.swift b/HutchTests/AppStateTests.swift
index fe357cb..a5661ef 100644
--- a/HutchTests/AppStateTests.swift
+++ b/HutchTests/AppStateTests.swift
@@ -39,22 +39,24 @@ struct AppStateTests {
     func navigationHelpersQueueExpectedTargets() {
         let appState = AppState()
         let repository = RepositorySummary(
-            id: 1,
-            rid: "repo",
-            service: .git,
-            name: "hutch",
-            description: nil,
-            visibility: .public,
-            updated: .distantPast,
-            owner: Entity(canonicalName: "~owner"),
-            head: nil
+            fields: .init(
+                id: 1,
+                rid: "repo",
+                service: .git,
+                name: "hutch",
+                description: nil,
+                visibility: .publicVisibility,
+                updated: .distantPast,
+                owner: Entity(canonicalName: "~owner"),
+                head: nil
+            )
         )
         let tracker = TrackerSummary(
             id: 2,
             rid: "tracker",
             name: "todo",
             description: nil,
-            visibility: .public,
+            visibility: .publicVisibility,
             updated: .distantPast,
             owner: Entity(canonicalName: "~owner")
         )
diff --git a/HutchTests/ProjectTests.swift b/HutchTests/ProjectTests.swift
index 0323f25..23faa0d 100644
--- a/HutchTests/ProjectTests.swift
+++ b/HutchTests/ProjectTests.swift
@@ -15,7 +15,7 @@ struct ProjectTests {
                 name: "Hutch",
                 description: nil,
                 website: nil,
-                visibility: .public,
+                visibility: .publicVisibility,
                 tags: [],
                 updated: Date(timeIntervalSince1970: 0)
             ),
@@ -25,7 +25,7 @@ struct ProjectTests {
                         id: "list-1",
                         name: "hutch-devel",
                         description: nil,
-                        visibility: .public,
+                        visibility: .publicVisibility,
                         owner: Entity(canonicalName: "~owner")
                     )
                 ],
@@ -34,7 +34,7 @@ struct ProjectTests {
                         id: "repo-1",
                         name: "hutch",
                         description: nil,
-                        visibility: .public,
+                        visibility: .publicVisibility,
                         owner: Entity(canonicalName: "~owner"),
                         repoType: .git
                     ),
@@ -42,7 +42,7 @@ struct ProjectTests {
                         id: "repo-2",
                         name: "hutch-web",
                         description: nil,
-                        visibility: .public,
+                        visibility: .publicVisibility,
                         owner: Entity(canonicalName: "~owner"),
                         repoType: .git
                     )
@@ -52,7 +52,7 @@ struct ProjectTests {
                         id: "tracker-1",
                         name: "bugs",
                         description: nil,
-                        visibility: .public,
+                        visibility: .publicVisibility,
                         owner: Entity(canonicalName: "~owner")
                     )
                 ],
@@ -71,7 +71,7 @@ struct ProjectTests {
                 name: "Docs",
                 description: nil,
                 website: Fixture.exampleWebsite,
-                visibility: .public,
+                visibility: .publicVisibility,
                 tags: [],
                 updated: Date(timeIntervalSince1970: 0)
             ),
diff --git a/HutchTests/RepositoryACLViewModelTests.swift b/HutchTests/RepositoryACLViewModelTests.swift
index 9c3f929..9640b93 100644
--- a/HutchTests/RepositoryACLViewModelTests.swift
+++ b/HutchTests/RepositoryACLViewModelTests.swift
@@ -110,15 +110,17 @@ struct RepositoryACLViewModelTests {
     @MainActor
     private func makeRepository() -> RepositorySummary {
         RepositorySummary(
-            id: 1,
-            rid: "rid-1",
-            service: .git,
-            name: "repo",
-            description: nil,
-            visibility: .public,
-            updated: .now,
-            owner: Entity(canonicalName: "~owner"),
-            head: nil
+            fields: .init(
+                id: 1,
+                rid: "rid-1",
+                service: .git,
+                name: "repo",
+                description: nil,
+                visibility: .publicVisibility,
+                updated: .now,
+                owner: Entity(canonicalName: "~owner"),
+                head: nil
+            )
         )
     }
 }
diff --git a/HutchTests/RepositoryListViewModelTests.swift b/HutchTests/RepositoryListViewModelTests.swift
index 07f66aa..5fe3974 100644
--- a/HutchTests/RepositoryListViewModelTests.swift
+++ b/HutchTests/RepositoryListViewModelTests.swift
@@ -79,15 +79,17 @@ struct RepositoryListViewModelTests {
         branch: String = "main"
     ) -> RepositorySummary {
         RepositorySummary(
-            id: id,
-            rid: "rid-\(id)",
-            service: service,
-            name: name,
-            description: description,
-            visibility: .public,
-            updated: Date(timeIntervalSince1970: TimeInterval(id)),
-            owner: Entity(canonicalName: owner),
-            head: Reference(name: branch, target: nil)
+            fields: .init(
+                id: id,
+                rid: "rid-\(id)",
+                service: service,
+                name: name,
+                description: description,
+                visibility: .publicVisibility,
+                updated: Date(timeIntervalSince1970: TimeInterval(id)),
+                owner: Entity(canonicalName: owner),
+                head: Reference(name: branch, target: nil)
+            )
         )
     }
 }
diff --git a/HutchTests/RepositorySettingsViewModelTests.swift b/HutchTests/RepositorySettingsViewModelTests.swift
index 3b76da1..e64e08f 100644
--- a/HutchTests/RepositorySettingsViewModelTests.swift
+++ b/HutchTests/RepositorySettingsViewModelTests.swift
@@ -118,15 +118,17 @@ struct RepositorySettingsViewModelTests {
     @MainActor
     private func makeRepository(headName: String?) -> RepositorySummary {
         RepositorySummary(
-            id: 1,
-            rid: "rid-1",
-            service: .git,
-            name: "repo",
-            description: "desc",
-            visibility: .public,
-            updated: .now,
-            owner: Entity(canonicalName: "~owner"),
-            head: headName.map { Reference(name: $0, target: nil) }
+            fields: .init(
+                id: 1,
+                rid: "rid-1",
+                service: .git,
+                name: "repo",
+                description: "desc",
+                visibility: .publicVisibility,
+                updated: .now,
+                owner: Entity(canonicalName: "~owner"),
+                head: headName.map { Reference(name: $0, target: nil) }
+            )
         )
     }
 }
diff --git a/HutchTests/SRHTWebURLTests.swift b/HutchTests/SRHTWebURLTests.swift
index c1f0dc0..d1053e6 100644
--- a/HutchTests/SRHTWebURLTests.swift
+++ b/HutchTests/SRHTWebURLTests.swift
@@ -14,22 +14,24 @@ struct SRHTWebURLTests {
     }
 
     private let repository = RepositorySummary(
-        id: 1,
-        rid: "repo-1",
-        service: .git,
-        name: "hutch",
-        description: nil,
-        visibility: .public,
-        updated: .distantPast,
-        owner: Entity(canonicalName: "~ccleberg"),
-        head: nil
+        fields: .init(
+            id: 1,
+            rid: "repo-1",
+            service: .git,
+            name: "hutch",
+            description: nil,
+            visibility: .publicVisibility,
+            updated: .distantPast,
+            owner: Entity(canonicalName: "~ccleberg"),
+            head: nil
+        )
     )
     private let tracker = TrackerSummary(
         id: 2,
         rid: "tracker-1",
         name: "todo",
         description: nil,
-        visibility: .public,
+        visibility: .publicVisibility,
         updated: .distantPast,
         owner: Entity(canonicalName: "~ccleberg")
     )
diff --git a/HutchTests/TrackerListViewModelTests.swift b/HutchTests/TrackerListViewModelTests.swift
index 7e927af..5c57e04 100644
--- a/HutchTests/TrackerListViewModelTests.swift
+++ b/HutchTests/TrackerListViewModelTests.swift
@@ -66,7 +66,7 @@ struct TrackerListViewModelTests {
             rid: "rid-\(id)",
             name: name,
             description: description,
-            visibility: .public,
+            visibility: .publicVisibility,
             updated: Date(),
             owner: Entity(canonicalName: owner)
         )