Commit 81520e73a1

81520e73a16455cb269b916e0866741ace2bc8fc

parent: c3d0407d15

Unsigned

cmc <hello@cleberg.net> · 2026-08-07 22:33 UTC

Adopt Swift 6 language mode

Flip SWIFT_VERSION to 6.0 (keeping SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor)
and resolve every resulting diagnostic. Mechanical, no behaviour change:

- Models: pure value types in Models/ and utility extensions (Date, DateFormatter,
  SRHTWebURL) marked nonisolated so the nonisolated networking layer can use them.
- App Intents: stored 'static var' (title/description/openAppWhenRun/
  typeDisplayRepresentation/defaultQuery/caseDisplayRepresentations) -> 'static let';
  drop the invalid @retroactive on the in-module LookupType: AppEnum.
- Sendable dicts: 'nil as String? as Any' -> 'nil as String? as any Sendable'.
- UserDefaults: retroactive @unchecked Sendable (documented thread-safe).
- WidgetKit completion handlers rebound nonisolated(unsafe) to cross into Task {};
  TipStoreViewModel task handle is @ObservationIgnored nonisolated(unsafe) for
  its nonisolated deinit.
- Tests run on @MainActor; a few constant fixtures marked nonisolated for use in
  @Sendable stub closures.

Builds and the full suite are clean in Swift 6 mode. Roadmap updated.

Layout: unified · split

Hutch.xcodeproj/project.pbxproj +8 −8
@@ -622,7 +622,7 @@
622622 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
623623 SWIFT_EMIT_LOC_STRINGS = YES;
624624 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
625 SWIFT_VERSION = 5.0;
625 SWIFT_VERSION = 6.0;
626626 TARGETED_DEVICE_FAMILY = "1,2";
627627 };
628628 name = Debug;
@@ -659,7 +659,7 @@
659659 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
660660 SWIFT_EMIT_LOC_STRINGS = YES;
661661 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
662 SWIFT_VERSION = 5.0;
662 SWIFT_VERSION = 6.0;
663663 TARGETED_DEVICE_FAMILY = "1,2";
664664 };
665665 name = Release;
@@ -680,7 +680,7 @@
680680 SWIFT_APPROACHABLE_CONCURRENCY = YES;
681681 SWIFT_EMIT_LOC_STRINGS = NO;
682682 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
683 SWIFT_VERSION = 5.0;
683 SWIFT_VERSION = 6.0;
684684 TARGETED_DEVICE_FAMILY = "1,2";
685685 TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Hutch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Hutch";
686686 };
@@ -702,7 +702,7 @@
702702 SWIFT_APPROACHABLE_CONCURRENCY = YES;
703703 SWIFT_EMIT_LOC_STRINGS = NO;
704704 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
705 SWIFT_VERSION = 5.0;
705 SWIFT_VERSION = 6.0;
706706 TARGETED_DEVICE_FAMILY = "1,2";
707707 TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Hutch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Hutch";
708708 };
@@ -732,7 +732,7 @@
732732 SWIFT_APPROACHABLE_CONCURRENCY = YES;
733733 SWIFT_EMIT_LOC_STRINGS = YES;
734734 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
735 SWIFT_VERSION = 5.0;
735 SWIFT_VERSION = 6.0;
736736 TARGETED_DEVICE_FAMILY = "1,2";
737737 };
738738 name = Debug;
@@ -761,7 +761,7 @@
761761 SWIFT_APPROACHABLE_CONCURRENCY = YES;
762762 SWIFT_EMIT_LOC_STRINGS = YES;
763763 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
764 SWIFT_VERSION = 5.0;
764 SWIFT_VERSION = 6.0;
765765 TARGETED_DEVICE_FAMILY = "1,2";
766766 };
767767 name = Release;
@@ -790,7 +790,7 @@
790790 SWIFT_APPROACHABLE_CONCURRENCY = YES;
791791 SWIFT_EMIT_LOC_STRINGS = YES;
792792 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
793 SWIFT_VERSION = 5.0;
793 SWIFT_VERSION = 6.0;
794794 TARGETED_DEVICE_FAMILY = "1,2";
795795 };
796796 name = Debug;
@@ -819,7 +819,7 @@
819819 SWIFT_APPROACHABLE_CONCURRENCY = YES;
820820 SWIFT_EMIT_LOC_STRINGS = YES;
821821 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
822 SWIFT_VERSION = 5.0;
822 SWIFT_VERSION = 6.0;
823823 TARGETED_DEVICE_FAMILY = "1,2";
824824 };
825825 name = Release;
Hutch/App/HutchIntents.swift +38 −38
@@ -4,9 +4,9 @@ import Foundation
44// MARK: - Navigation Intents
55
66struct OpenWorkQueueIntent: AppIntent {
7 static var title: LocalizedStringResource = "Open Work Queue"
8 static var description = IntentDescription("Opens Hutch to your Work Queue.")
9 static var openAppWhenRun = true
7 static let title: LocalizedStringResource = "Open Work Queue"
8 static let description = IntentDescription("Opens Hutch to your Work Queue.")
9 static let openAppWhenRun = true
1010
1111 var route: HutchRoute { .workQueue(scope: .all) }
1212
@@ -18,9 +18,9 @@ struct OpenWorkQueueIntent: AppIntent {
1818}
1919
2020struct OpenRecentActivityIntent: AppIntent {
21 static var title: LocalizedStringResource = "Open Recent Activity"
22 static var description = IntentDescription("Opens Hutch to recent activity.")
23 static var openAppWhenRun = true
21 static let title: LocalizedStringResource = "Open Recent Activity"
22 static let description = IntentDescription("Opens Hutch to recent activity.")
23 static let openAppWhenRun = true
2424
2525 var route: HutchRoute { .recentActivity }
2626
@@ -32,9 +32,9 @@ struct OpenRecentActivityIntent: AppIntent {
3232}
3333
3434struct OpenSystemStatusIntent: AppIntent {
35 static var title: LocalizedStringResource = "Open System Status"
36 static var description = IntentDescription("Opens Hutch to SourceHut system status.")
37 static var openAppWhenRun = true
35 static let title: LocalizedStringResource = "Open System Status"
36 static let description = IntentDescription("Opens Hutch to SourceHut system status.")
37 static let openAppWhenRun = true
3838
3939 var route: HutchRoute { .systemStatus }
4040
@@ -46,9 +46,9 @@ struct OpenSystemStatusIntent: AppIntent {
4646}
4747
4848struct OpenPinnedResourceIntent: AppIntent {
49 static var title: LocalizedStringResource = "Open Pinned Resource"
50 static var description = IntentDescription("Opens a pinned Hutch resource.")
51 static var openAppWhenRun = true
49 static let title: LocalizedStringResource = "Open Pinned Resource"
50 static let description = IntentDescription("Opens a pinned Hutch resource.")
51 static let openAppWhenRun = true
5252
5353 @Parameter(title: "Pinned Resource")
5454 var pinnedResource: PinnedResourceEntity
@@ -63,9 +63,9 @@ struct OpenPinnedResourceIntent: AppIntent {
6363}
6464
6565struct OpenProjectDashboardIntent: AppIntent {
66 static var title: LocalizedStringResource = "Open Project Dashboard"
67 static var description = IntentDescription("Opens a pinned project dashboard in Hutch.")
68 static var openAppWhenRun = true
66 static let title: LocalizedStringResource = "Open Project Dashboard"
67 static let description = IntentDescription("Opens a pinned project dashboard in Hutch.")
68 static let openAppWhenRun = true
6969
7070 @Parameter(title: "Project")
7171 var project: ProjectEntity
@@ -84,16 +84,16 @@ struct OpenProjectDashboardIntent: AppIntent {
8484enum HutchShortcutScope: String, AppEnum {
8585 case all
8686
87 static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Scope")
88 static var caseDisplayRepresentations: [HutchShortcutScope: DisplayRepresentation] = [
87 static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Scope")
88 static let caseDisplayRepresentations: [HutchShortcutScope: DisplayRepresentation] = [
8989 .all: "All"
9090 ]
9191}
9292
9393struct OpenFailedBuildsIntent: AppIntent {
94 static var title: LocalizedStringResource = "Open Failed Builds"
95 static var description = IntentDescription("Opens Hutch to failed builds.")
96 static var openAppWhenRun = true
94 static let title: LocalizedStringResource = "Open Failed Builds"
95 static let description = IntentDescription("Opens Hutch to failed builds.")
96 static let openAppWhenRun = true
9797
9898 @Parameter(title: "Scope", default: .all)
9999 var scope: HutchShortcutScope
@@ -108,9 +108,9 @@ struct OpenFailedBuildsIntent: AppIntent {
108108}
109109
110110struct OpenAssignedTicketsIntent: AppIntent {
111 static var title: LocalizedStringResource = "Open Assigned Tickets"
112 static var description = IntentDescription("Opens Hutch to tickets assigned to you.")
113 static var openAppWhenRun = true
111 static let title: LocalizedStringResource = "Open Assigned Tickets"
112 static let description = IntentDescription("Opens Hutch to tickets assigned to you.")
113 static let openAppWhenRun = true
114114
115115 @Parameter(title: "Scope", default: .all)
116116 var scope: HutchShortcutScope
@@ -125,9 +125,9 @@ struct OpenAssignedTicketsIntent: AppIntent {
125125}
126126
127127struct SearchHutchIntent: AppIntent {
128 static var title: LocalizedStringResource = "Search Hutch"
129 static var description = IntentDescription("Opens Hutch lookup with a search query.")
130 static var openAppWhenRun = true
128 static let title: LocalizedStringResource = "Search Hutch"
129 static let description = IntentDescription("Opens Hutch lookup with a search query.")
130 static let openAppWhenRun = true
131131
132132 @Parameter(title: "Query")
133133 var query: String
@@ -155,8 +155,8 @@ struct SearchHutchIntent: AppIntent {
155155// MARK: - App Entities
156156
157157struct PinnedResourceEntity: AppEntity, Identifiable {
158 static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Pinned Resource")
159 static var defaultQuery = PinnedResourceQuery()
158 static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Pinned Resource")
159 static let defaultQuery = PinnedResourceQuery()
160160
161161 let id: String
162162 let name: String
@@ -181,8 +181,8 @@ struct PinnedResourceQuery: EntityQuery {
181181}
182182
183183struct ProjectEntity: AppEntity, Identifiable {
184 static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Project")
185 static var defaultQuery = ProjectEntityQuery()
184 static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Project")
185 static let defaultQuery = ProjectEntityQuery()
186186
187187 let id: String
188188 let name: String
@@ -278,8 +278,8 @@ enum HutchIntentEntityStore {
278278// MARK: - Existing Read-Only Summary Intents
279279
280280struct CheckSystemStatusIntent: AppIntent {
281 static var title: LocalizedStringResource = "Check SourceHut Status"
282 static var description = IntentDescription("Returns the current SourceHut system status.")
281 static let title: LocalizedStringResource = "Check SourceHut Status"
282 static let description = IntentDescription("Returns the current SourceHut system status.")
283283
284284 @MainActor
285285 func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
@@ -302,8 +302,8 @@ struct CheckSystemStatusIntent: AppIntent {
302302}
303303
304304struct CheckBuildsIntent: AppIntent {
305 static var title: LocalizedStringResource = "Check Hutch Builds"
306 static var description = IntentDescription("Returns a summary of your recent build status.")
305 static let title: LocalizedStringResource = "Check Hutch Builds"
306 static let description = IntentDescription("Returns a summary of your recent build status.")
307307
308308 @MainActor
309309 func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
@@ -357,8 +357,8 @@ extension LookupType: AppEnum {
357357// MARK: - Mutating Intents
358358
359359struct ClearRecentActivityIntent: AppIntent {
360 static var title: LocalizedStringResource = "Clear Recent Activity"
361 static var description = IntentDescription("Clears the Recent list on the Hutch Home tab.")
360 static let title: LocalizedStringResource = "Clear Recent Activity"
361 static let description = IntentDescription("Clears the Recent list on the Hutch Home tab.")
362362
363363 @MainActor
364364 func perform() async throws -> some IntentResult & ProvidesDialog {
@@ -368,8 +368,8 @@ struct ClearRecentActivityIntent: AppIntent {
368368}
369369
370370struct UnpinResourceIntent: AppIntent {
371 static var title: LocalizedStringResource = "Unpin Resource"
372 static var description = IntentDescription("Removes a pinned resource from the Hutch Home tab.")
371 static let title: LocalizedStringResource = "Unpin Resource"
372 static let description = IntentDescription("Removes a pinned resource from the Hutch Home tab.")
373373
374374 @Parameter(title: "Pinned Resource")
375375 var pinnedResource: PinnedResourceEntity
Hutch/Extensions/Date+Relative.swift +1 −1
@@ -1,6 +1,6 @@
11import Foundation
22
3extension Date {
3nonisolated extension Date {
44 /// A short relative description like "2h ago", "3d ago", or "Jan 5, 2025".
55 var relativeDescription: String {
66 let formatter = RelativeDateTimeFormatter()
Hutch/Extensions/DateFormatter+SRHT.swift +1 −1
@@ -1,6 +1,6 @@
11import Foundation
22
3extension DateFormatter {
3nonisolated extension DateFormatter {
44 /// Formatter for the sr.ht `Time` scalar: `%Y-%m-%dT%H:%M:%SZ` (UTC).
55 static let srht: DateFormatter = {
66 let formatter = DateFormatter()
Hutch/Extensions/Foundation+Sendable.swift added +7
@@ -0,0 +1,7 @@
1import Foundation
2
3// `UserDefaults` is documented as thread-safe but is not marked `Sendable` by
4// Foundation. Hutch passes it into account sessions and the various per-account
5// stores, all of which need to cross concurrency boundaries. The retroactive
6// unchecked conformance reflects the documented thread-safety.
7extension UserDefaults: @retroactive @unchecked Sendable {}
Hutch/Extensions/SRHTWebURL.swift +1 −1
@@ -1,6 +1,6 @@
11import Foundation
22
3enum SRHTWebURL {
3nonisolated enum SRHTWebURL {
44 static let chat = URL(string: "https://chat.sr.ht")!
55 static let status = URL(string: "https://status.sr.ht")!
66 /// RSS/Atom feed for status incidents (HTML snapshot uses `status`).
Hutch/Models/AccountEntry.swift +1 −1
@@ -1,7 +1,7 @@
11import Foundation
22
33/// A stored sr.ht account (token + resolved username).
4struct AccountEntry: Codable, Identifiable, Equatable, Sendable {
4nonisolated struct AccountEntry: Codable, Identifiable, Equatable, Sendable {
55 let id: String
66 var username: String
77 var token: String
Hutch/Models/ArtifactInfo.swift +3 −3
@@ -1,7 +1,7 @@
11import Foundation
22
33/// An artifact with its parent reference name, used in the artifacts tab.
4struct ArtifactInfo: Codable, Sendable, Identifiable {
4nonisolated struct ArtifactInfo: Codable, Sendable, Identifiable {
55 let id: Int
66 let filename: String
77 let checksum: String
@@ -9,13 +9,13 @@ struct ArtifactInfo: Codable, Sendable, Identifiable {
99 let url: URL
1010}
1111
12struct ArtifactPage: Codable, Sendable {
12nonisolated struct ArtifactPage: Codable, Sendable {
1313 let results: [ArtifactInfo]
1414 let cursor: String?
1515}
1616
1717/// A reference (tag) that has associated artifacts.
18struct ReferenceWithArtifacts: Codable, Sendable, Identifiable {
18nonisolated struct ReferenceWithArtifacts: Codable, Sendable, Identifiable {
1919 var id: String { name }
2020 let name: String
2121 let artifacts: [ArtifactInfo]
Hutch/Models/Builds.swift +9 −9
@@ -3,7 +3,7 @@ import Foundation
33// MARK: - Enums
44
55/// Status of a build job.
6enum JobStatus: String, Codable, Sendable {
6nonisolated enum JobStatus: String, Codable, Sendable {
77 case pending = "PENDING"
88 case queued = "QUEUED"
99 case running = "RUNNING"
@@ -30,7 +30,7 @@ enum JobStatus: String, Codable, Sendable {
3030}
3131
3232/// Status of a single build task within a job.
33enum TaskStatus: String, Codable, Sendable {
33nonisolated enum TaskStatus: String, Codable, Sendable {
3434 case pending = "PENDING"
3535 case running = "RUNNING"
3636 case success = "SUCCESS"
@@ -41,7 +41,7 @@ enum TaskStatus: String, Codable, Sendable {
4141// MARK: - Build Task
4242
4343/// A single task within a build job.
44struct BuildTask: Codable, Sendable, Identifiable, Equatable {
44nonisolated struct BuildTask: Codable, Sendable, Identifiable, Equatable {
4545 private(set) var ordinal: Int?
4646 let name: String
4747 let status: TaskStatus
@@ -74,7 +74,7 @@ struct BuildTask: Codable, Sendable, Identifiable, Equatable {
7474// MARK: - Job Summary (for list view)
7575
7676/// Lightweight job model matching the fields returned by the jobs list query.
77struct JobSummary: Codable, Sendable, Identifiable, Hashable {
77nonisolated struct JobSummary: Codable, Sendable, Identifiable, Hashable {
7878 let id: Int
7979 let created: Date
8080 let updated: Date
@@ -103,7 +103,7 @@ struct JobSummary: Codable, Sendable, Identifiable, Hashable {
103103}
104104
105105/// Minimal task info for the list query.
106struct JobTaskSummary: Codable, Sendable, Hashable {
106nonisolated struct JobTaskSummary: Codable, Sendable, Hashable {
107107 let name: String
108108 let status: TaskStatus
109109}
@@ -111,7 +111,7 @@ struct JobTaskSummary: Codable, Sendable, Hashable {
111111// MARK: - Job Detail (for detail view)
112112
113113/// Full job model with all fields for the detail view.
114struct JobDetail: Codable, Sendable, Equatable {
114nonisolated struct JobDetail: Codable, Sendable, Equatable {
115115 let id: Int
116116 let created: Date
117117 let updated: Date
@@ -128,7 +128,7 @@ struct JobDetail: Codable, Sendable, Equatable {
128128}
129129
130130/// A downloadable artifact emitted by a build job.
131struct BuildArtifact: Codable, Sendable, Identifiable, Equatable {
131nonisolated struct BuildArtifact: Codable, Sendable, Identifiable, Equatable {
132132 let id: Int
133133 let created: Date
134134 let path: String
@@ -145,14 +145,14 @@ struct BuildArtifact: Codable, Sendable, Identifiable, Equatable {
145145}
146146
147147/// The log associated with a build job.
148struct BuildLog: Codable, Sendable, Equatable {
148nonisolated struct BuildLog: Codable, Sendable, Equatable {
149149 let fullURL: String
150150}
151151
152152// MARK: - Job Group
153153
154154/// A group of related build jobs.
155struct JobGroup: Codable, Sendable, Identifiable {
155nonisolated struct JobGroup: Codable, Sendable, Identifiable {
156156 let id: Int
157157 let created: Date
158158 let note: String?
Hutch/Models/CommitDetail.swift +8 −8
@@ -1,7 +1,7 @@
11import Foundation
22
33/// Full commit detail returned by the revparse_single query.
4struct CommitDetail: Codable, Sendable, Identifiable {
4nonisolated struct CommitDetail: Codable, Sendable, Identifiable {
55 let id: String
66 let shortId: String
77 let author: CommitAuthor
@@ -26,39 +26,39 @@ struct CommitDetail: Codable, Sendable, Identifiable {
2626 }
2727}
2828
29struct CommitTrailer: Codable, Sendable, Identifiable {
29nonisolated struct CommitTrailer: Codable, Sendable, Identifiable {
3030 var id: String { "\(name):\(value)" }
3131 let name: String
3232 let value: String
3333}
3434
35struct ParentCommit: Codable, Sendable, Identifiable, Hashable {
35nonisolated struct ParentCommit: Codable, Sendable, Identifiable, Hashable {
3636 let id: String
3737 let shortId: String
3838 let author: ParentAuthor
3939}
4040
41struct ParentAuthor: Codable, Sendable, Hashable {
41nonisolated struct ParentAuthor: Codable, Sendable, Hashable {
4242 let name: String
4343}
4444
45struct CommitTree: Codable, Sendable {
45nonisolated struct CommitTree: Codable, Sendable {
4646 let entries: CommitTreeEntries
4747}
4848
49struct CommitTreeEntries: Codable, Sendable {
49nonisolated struct CommitTreeEntries: Codable, Sendable {
5050 let results: [CommitTreeEntry]
5151 let cursor: String?
5252}
5353
54struct CommitTreeEntry: Codable, Sendable, Identifiable {
54nonisolated struct CommitTreeEntry: Codable, Sendable, Identifiable {
5555 let id: String
5656 let name: String
5757 let mode: Int
5858 let object: CommitTreeObject?
5959}
6060
61struct CommitTreeObject: Codable, Sendable {
61nonisolated struct CommitTreeObject: Codable, Sendable {
6262 let type: String?
6363 let id: String?
6464 let shortId: String?
Hutch/Models/CommitSummary.swift +2 −2
@@ -2,7 +2,7 @@ import Foundation
22
33/// Lightweight commit model for list views. Matches the subset of fields
44/// returned by the repository log query.
5struct CommitSummary: Codable, Sendable, Identifiable, Hashable {
5nonisolated struct CommitSummary: Codable, Sendable, Identifiable, Hashable {
66 let id: String
77 let shortId: String
88 let author: CommitAuthor
@@ -15,7 +15,7 @@ struct CommitSummary: Codable, Sendable, Identifiable, Hashable {
1515}
1616
1717/// A compact author representation used in commit list responses.
18struct CommitAuthor: Codable, Sendable, Hashable {
18nonisolated struct CommitAuthor: Codable, Sendable, Hashable {
1919 let name: String
2020 let email: String?
2121 let time: Date
Hutch/Models/ContributionCalendar.swift +14 −14
@@ -1,6 +1,6 @@
11import Foundation
22
3struct ContributionCalendarResponse: Decodable, Sendable, Hashable {
3nonisolated struct ContributionCalendarResponse: Decodable, Sendable, Hashable {
44 let actor: String
55 let from: Date
66 let to: Date
@@ -9,7 +9,7 @@ struct ContributionCalendarResponse: Decodable, Sendable, Hashable {
99 let indexingState: ContributionIndexingState
1010 let days: [ContributionDay]
1111
12 enum CodingKeys: String, CodingKey {
12 nonisolated enum CodingKeys: String, CodingKey {
1313 case actor
1414 case from
1515 case to
@@ -59,14 +59,14 @@ struct ContributionCalendarResponse: Decodable, Sendable, Hashable {
5959 }
6060}
6161
62struct ContributionDay: Decodable, Sendable, Hashable, Identifiable {
62nonisolated struct ContributionDay: Decodable, Sendable, Hashable, Identifiable {
6363 var id: Date { date }
6464
6565 let date: Date
6666 let count: Int
6767 let score: Double
6868
69 enum CodingKeys: String, CodingKey {
69 nonisolated enum CodingKeys: String, CodingKey {
7070 case date
7171 case count
7272 case score
@@ -90,7 +90,7 @@ struct ContributionDay: Decodable, Sendable, Hashable, Identifiable {
9090 }
9191}
9292
93struct ContributionStatsResponse: Decodable, Sendable, Hashable {
93nonisolated struct ContributionStatsResponse: Decodable, Sendable, Hashable {
9494 let actor: String
9595 let from: Date
9696 let to: Date
@@ -103,7 +103,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable {
103103 let longestStreak: Int
104104 let currentStreak: Int
105105
106 enum CodingKeys: String, CodingKey {
106 nonisolated enum CodingKeys: String, CodingKey {
107107 case actor
108108 case from
109109 case to
@@ -117,7 +117,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable {
117117 case currentStreak = "current_streak"
118118 }
119119
120 struct StatsWindow: Sendable, Hashable {
120 nonisolated struct StatsWindow: Sendable, Hashable {
121121 let actor: String
122122 let from: Date
123123 let to: Date
@@ -126,7 +126,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable {
126126 let indexingState: ContributionIndexingState
127127 }
128128
129 struct StatsTotals: Sendable, Hashable {
129 nonisolated struct StatsTotals: Sendable, Hashable {
130130 let totalEvents: Int
131131 let totalScore: Double
132132 let activeDays: Int
@@ -181,13 +181,13 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable {
181181 }
182182}
183183
184enum ContributionIndexingState: String, Codable, Sendable, Hashable {
184nonisolated enum ContributionIndexingState: String, Codable, Sendable, Hashable {
185185 case pending
186186 case indexed
187187 case error
188188}
189189
190enum ContributionIntensity: Int, Sendable, CaseIterable {
190nonisolated enum ContributionIntensity: Int, Sendable, CaseIterable {
191191 case empty = 0
192192 case level1 = 1
193193 case level2 = 2
@@ -210,12 +210,12 @@ enum ContributionIntensity: Int, Sendable, CaseIterable {
210210 }
211211}
212212
213struct ContributionWeek: Sendable, Hashable {
213nonisolated struct ContributionWeek: Sendable, Hashable {
214214 let startDate: Date
215215 let days: [ContributionDay]
216216}
217217
218enum ContributionCalendarLayout {
218nonisolated enum ContributionCalendarLayout {
219219 static func weekColumns(
220220 from days: [ContributionDay],
221221 calendar: Calendar = .contributionCalendar
@@ -241,7 +241,7 @@ enum ContributionCalendarLayout {
241241 }
242242}
243243
244enum ContributionDateParser {
244nonisolated enum ContributionDateParser {
245245 static func parse(_ rawValue: String) -> Date? {
246246 let parts = rawValue.split(separator: "-", omittingEmptySubsequences: false)
247247 guard
@@ -348,7 +348,7 @@ enum ContributionDateParser {
348348 }
349349}
350350
351extension Calendar {
351nonisolated extension Calendar {
352352 static var contributionCalendar: Calendar {
353353 var calendar = Calendar(identifier: .gregorian)
354354 calendar.firstWeekday = 1
Hutch/Models/Git.swift +20 −20
@@ -3,14 +3,14 @@ import Foundation
33// MARK: - Enums
44
55/// Repository visibility level.
6enum Visibility: String, Codable, Sendable {
6nonisolated enum Visibility: String, Codable, Sendable {
77 case publicVisibility = "PUBLIC"
88 case unlisted = "UNLISTED"
99 case privateVisibility = "PRIVATE"
1010}
1111
1212/// Repository access mode.
13enum AccessMode: String, Codable, Sendable, CaseIterable {
13nonisolated enum AccessMode: String, Codable, Sendable, CaseIterable {
1414 case ro = "RO"
1515 case rw = "RW"
1616}
@@ -19,14 +19,14 @@ enum AccessMode: String, Codable, Sendable, CaseIterable {
1919
2020/// The `Entity` GraphQL interface from git.sr.ht. Represents the owner of a
2121/// resource (typically a user).
22struct Entity: Codable, Sendable, Hashable {
22nonisolated struct Entity: Codable, Sendable, Hashable {
2323 let canonicalName: String
2424}
2525
2626// MARK: - Repository
2727
2828/// A git repository from git.sr.ht.
29struct Repository: Codable, Sendable, Identifiable {
29nonisolated struct Repository: Codable, Sendable, Identifiable {
3030 let id: Int
3131 let created: Date
3232 let updated: Date
@@ -37,7 +37,7 @@ struct Repository: Codable, Sendable, Identifiable {
3737 let accessMode: AccessMode
3838 let owner: Entity
3939
40 enum CodingKeys: String, CodingKey {
40 nonisolated enum CodingKeys: String, CodingKey {
4141 case id, created, updated, name, description, visibility, readme
4242 case accessMode = "access"
4343 case owner
@@ -47,7 +47,7 @@ struct Repository: Codable, Sendable, Identifiable {
4747// MARK: - Signature
4848
4949/// A Git commit/tag signature (author or committer).
50struct Signature: Codable, Sendable {
50nonisolated struct Signature: Codable, Sendable {
5151 let name: String
5252 let email: String
5353 let time: Date
@@ -56,7 +56,7 @@ struct Signature: Codable, Sendable {
5656// MARK: - Trailer
5757
5858/// A Git commit trailer (e.g. "Signed-off-by", "Co-authored-by").
59struct Trailer: Codable, Sendable {
59nonisolated struct Trailer: Codable, Sendable {
6060 let name: String
6161 let value: String
6262}
@@ -64,7 +64,7 @@ struct Trailer: Codable, Sendable {
6464// MARK: - Commit
6565
6666/// A Git commit from git.sr.ht.
67struct Commit: Codable, Sendable, Identifiable {
67nonisolated struct Commit: Codable, Sendable, Identifiable {
6868 let id: String
6969 let shortId: String
7070 let author: Signature
@@ -77,13 +77,13 @@ struct Commit: Codable, Sendable, Identifiable {
7777// MARK: - Reference
7878
7979/// A Git reference (branch or tag name).
80struct Reference: Codable, Sendable, Hashable {
80nonisolated struct Reference: Codable, Sendable, Hashable {
8181 let name: String
8282 let target: String?
8383}
8484
8585/// A Git reference enriched with the date of its tip commit or tag, for display in the Refs tab.
86struct ReferenceDetail: Sendable, Hashable {
86nonisolated struct ReferenceDetail: Sendable, Hashable {
8787 let name: String
8888 let target: String?
8989 let date: Date?
@@ -92,7 +92,7 @@ struct ReferenceDetail: Sendable, Hashable {
9292// MARK: - TreeEntry
9393
9494/// An entry in a Git tree (file or directory).
95struct TreeEntry: Codable, Sendable, Identifiable {
95nonisolated struct TreeEntry: Codable, Sendable, Identifiable {
9696 let id: String
9797 let name: String
9898 let mode: Int?
@@ -104,41 +104,41 @@ struct TreeEntry: Codable, Sendable, Identifiable {
104104/// Both TextBlob and BinaryBlob have type == "BLOB"; they are
105105/// distinguished by the presence of the "text" key (TextBlob) vs
106106/// the "content" key (BinaryBlob).
107enum GitObject: Sendable {
107nonisolated enum GitObject: Sendable {
108108 case tree(GitTree)
109109 case textBlob(GitTextBlob)
110110 case binaryBlob(GitBinaryBlob)
111111 case unknown
112112}
113113
114struct GitTree: Codable, Sendable {
114nonisolated struct GitTree: Codable, Sendable {
115115 let id: String?
116116 let shortId: String?
117117 let entries: GitTreeEntryPage?
118118}
119119
120struct GitTextBlob: Codable, Sendable {
120nonisolated struct GitTextBlob: Codable, Sendable {
121121 let id: String?
122122 let shortId: String?
123123 let text: String?
124124 let size: Int?
125125}
126126
127struct GitBinaryBlob: Codable, Sendable {
127nonisolated struct GitBinaryBlob: Codable, Sendable {
128128 let id: String?
129129 let shortId: String?
130130 let size: Int?
131131 let content: String?
132132}
133133
134struct GitTreeEntryPage: Codable, Sendable {
134nonisolated struct GitTreeEntryPage: Codable, Sendable {
135135 let results: [TreeEntry]
136136 let cursor: String?
137137}
138138
139139// MARK: - GitObject Codable
140140
141extension GitObject: Codable {
141nonisolated extension GitObject: Codable {
142142 private enum CodingKeys: String, CodingKey {
143143 case type, id, shortId, entries, text, size, content
144144 case typename = "__typename"
@@ -211,7 +211,7 @@ extension GitObject: Codable {
211211}
212212
213213/// Convenience helpers for checking object type.
214extension GitObject {
214nonisolated extension GitObject {
215215 var isTree: Bool {
216216 if case .tree = self { return true }
217217 return false
@@ -230,7 +230,7 @@ extension GitObject {
230230// MARK: - Tag
231231
232232/// An annotated Git tag from git.sr.ht.
233struct Tag: Codable, Sendable, Identifiable {
233nonisolated struct Tag: Codable, Sendable, Identifiable {
234234 let id: String
235235 let shortId: String
236236 let name: String
@@ -241,7 +241,7 @@ struct Tag: Codable, Sendable, Identifiable {
241241// MARK: - Artifact
242242
243243/// A release artifact attached to a Git tag.
244struct Artifact: Codable, Sendable, Identifiable {
244nonisolated struct Artifact: Codable, Sendable, Identifiable {
245245 let id: Int
246246 let created: Date
247247 let filename: String
Hutch/Models/Inbox.swift +10 −10
@@ -1,6 +1,6 @@
11import Foundation
22
3struct InboxThreadSummary: Identifiable, Hashable, Sendable {
3nonisolated struct InboxThreadSummary: Identifiable, Hashable, Sendable {
44 let rootEmailID: Int
55 let rootMessageID: String
66 let threadRootEmailIDs: [Int]
@@ -76,7 +76,7 @@ struct InboxThreadSummary: Identifiable, Hashable, Sendable {
7676 }
7777}
7878
79struct InboxMessage: Identifiable, Hashable, Sendable {
79nonisolated struct InboxMessage: Identifiable, Hashable, Sendable {
8080 let id: Int
8181 let author: Entity
8282 let date: Date
@@ -89,12 +89,12 @@ struct InboxMessage: Identifiable, Hashable, Sendable {
8989 let rawMessageURL: URL?
9090}
9191
92enum InboxMessageContentBlock: Hashable, Sendable {
92nonisolated enum InboxMessageContentBlock: Hashable, Sendable {
9393 case plainText(String)
9494 case diff(String)
9595}
9696
97struct InboxThreadDetail: Sendable {
97nonisolated struct InboxThreadDetail: Sendable {
9898 let id: String
9999 let rootEmailID: Int
100100 let rootMessageID: String
@@ -114,7 +114,7 @@ struct InboxThreadDetail: Sendable {
114114 }
115115}
116116
117extension InboxThreadDetail {
117nonisolated extension InboxThreadDetail {
118118 var replyRecipient: String {
119119 "\(listOwner.canonicalName)/\(listName)@lists.sr.ht"
120120 }
@@ -144,7 +144,7 @@ extension InboxThreadDetail {
144144 }
145145}
146146
147struct MailComposeDraft: Sendable {
147nonisolated struct MailComposeDraft: Sendable {
148148 let recipients: [String]
149149 let ccRecipients: [String]
150150 let subject: String
@@ -155,20 +155,20 @@ struct MailComposeDraft: Sendable {
155155 }
156156}
157157
158extension MailComposeDraft: Identifiable {}
158nonisolated extension MailComposeDraft: Identifiable {}
159159
160struct InboxMailingListReference: Decodable, Sendable, Hashable, Identifiable {
160nonisolated struct InboxMailingListReference: Decodable, Sendable, Hashable, Identifiable {
161161 let id: Int
162162 let rid: String
163163 let name: String
164164 let owner: Entity
165165}
166166
167struct InboxPatchPreview: Decodable, Sendable, Hashable {
167nonisolated struct InboxPatchPreview: Decodable, Sendable, Hashable {
168168 let subject: String?
169169}
170170
171enum InboxReadStateStore {
171nonisolated enum InboxReadStateStore {
172172 private static let key = "InboxThreadLastViewed"
173173 private static let baselineKey = "InboxUnreadBaseline"
174174
Hutch/Models/Meta.swift +8 −8
@@ -3,7 +3,7 @@ import Foundation
33// MARK: - User Profile (full)
44
55/// Extended user profile from meta.sr.ht with all fields from the `me` query.
6struct UserProfile: Codable, Sendable {
6nonisolated struct UserProfile: Codable, Sendable {
77 let username: String
88 let canonicalName: String
99 let email: String
@@ -20,7 +20,7 @@ struct UserProfile: Codable, Sendable {
2020
2121// MARK: - SSH Key
2222
23struct SSHKey: Codable, Sendable, Identifiable {
23nonisolated struct SSHKey: Codable, Sendable, Identifiable {
2424 let id: Int
2525 let comment: String?
2626 let created: Date
@@ -34,27 +34,27 @@ struct SSHKey: Codable, Sendable, Identifiable {
3434 }
3535}
3636
37struct SSHKeyPage: Codable, Sendable {
37nonisolated struct SSHKeyPage: Codable, Sendable {
3838 let results: [SSHKey]
3939 let cursor: String?
4040}
4141
4242// MARK: - PGP Key
4343
44struct PGPKey: Codable, Sendable, Identifiable {
44nonisolated struct PGPKey: Codable, Sendable, Identifiable {
4545 let id: Int
4646 let fingerprint: String
4747 let created: Date
4848}
4949
50struct PGPKeyPage: Codable, Sendable {
50nonisolated struct PGPKeyPage: Codable, Sendable {
5151 let results: [PGPKey]
5252 let cursor: String?
5353}
5454
5555// MARK: - Subscription
5656
57struct Subscription: Codable, Sendable {
57nonisolated struct Subscription: Codable, Sendable {
5858 let status: String?
5959 let autorenew: Bool?
6060 let interval: String?
@@ -62,7 +62,7 @@ struct Subscription: Codable, Sendable {
6262
6363// MARK: - Personal Access Token
6464
65struct PersonalAccessToken: Codable, Sendable, Identifiable {
65nonisolated struct PersonalAccessToken: Codable, Sendable, Identifiable {
6666 let id: Int
6767 let issued: Date
6868 let expires: Date?
@@ -72,7 +72,7 @@ struct PersonalAccessToken: Codable, Sendable, Identifiable {
7272
7373/// One entry in meta.sr.ht's audit log: a security-relevant action on the
7474/// account, with the address it came from.
75struct AuditLogEntry: Codable, Sendable, Identifiable {
75nonisolated struct AuditLogEntry: Codable, Sendable, Identifiable {
7676 let id: Int
7777 let created: Date
7878 let ipAddress: String
Hutch/Models/Paste.swift +3 −3
@@ -1,6 +1,6 @@
11import Foundation
22
3struct PasteFile: Codable, Sendable, Hashable, Identifiable {
3nonisolated struct PasteFile: Codable, Sendable, Hashable, Identifiable {
44 let filename: String?
55 let hash: String
66 let contents: URL?
@@ -8,7 +8,7 @@ struct PasteFile: Codable, Sendable, Hashable, Identifiable {
88 var id: String { hash }
99}
1010
11struct Paste: Codable, Sendable, Identifiable, Hashable {
11nonisolated struct Paste: Codable, Sendable, Identifiable, Hashable {
1212 let id: String
1313 let created: Date
1414 let visibility: Visibility
@@ -16,7 +16,7 @@ struct Paste: Codable, Sendable, Identifiable, Hashable {
1616 let user: Entity
1717}
1818
19struct PasteUploadDraft: Identifiable, Equatable, Sendable {
19nonisolated struct PasteUploadDraft: Identifiable, Equatable, Sendable {
2020 let id: UUID
2121 var filename: String
2222 var contents: String
Hutch/Models/Patchset.swift +6 −6
@@ -1,7 +1,7 @@
11import Foundation
22
33/// Review state of a patchset on lists.sr.ht.
4enum PatchsetStatus: String, Codable, Sendable, CaseIterable {
4nonisolated enum PatchsetStatus: String, Codable, Sendable, CaseIterable {
55 case unknown = "UNKNOWN"
66 case proposed = "PROPOSED"
77 case needsRevision = "NEEDS_REVISION"
@@ -55,7 +55,7 @@ enum PatchsetStatus: String, Codable, Sendable, CaseIterable {
5555/// A patchset as it appears in a mailing list listing, derived from the thread's
5656/// root email rather than a dedicated patchsets query — `MailingList` exposes no
5757/// such field.
58struct PatchsetSummary: Identifiable, Hashable, Sendable {
58nonisolated struct PatchsetSummary: Identifiable, Hashable, Sendable {
5959 let id: Int
6060 let subject: String
6161 let version: Int
@@ -70,7 +70,7 @@ struct PatchsetSummary: Identifiable, Hashable, Sendable {
7070}
7171
7272/// One email within a patchset: either the cover letter or a single patch.
73struct PatchsetEmail: Identifiable, Hashable, Sendable {
73nonisolated struct PatchsetEmail: Identifiable, Hashable, Sendable {
7474 let id: Int
7575 let subject: String
7676 let date: Date?
@@ -88,13 +88,13 @@ struct PatchsetEmail: Identifiable, Hashable, Sendable {
8888}
8989
9090/// A build or check reported against a patchset.
91struct PatchsetToolResult: Identifiable, Hashable, Sendable {
91nonisolated struct PatchsetToolResult: Identifiable, Hashable, Sendable {
9292 let id: Int
9393 let icon: PatchsetToolIcon
9494 let details: String
9595}
9696
97enum PatchsetToolIcon: String, Codable, Sendable {
97nonisolated enum PatchsetToolIcon: String, Codable, Sendable {
9898 case pending = "PENDING"
9999 case waiting = "WAITING"
100100 case success = "SUCCESS"
@@ -112,7 +112,7 @@ enum PatchsetToolIcon: String, Codable, Sendable {
112112}
113113
114114/// A patchset with its cover letter, patches, and review context.
115struct PatchsetDetail: Sendable {
115nonisolated struct PatchsetDetail: Sendable {
116116 let id: Int
117117 let created: Date
118118 let updated: Date
Hutch/Models/Project.swift +12 −12
@@ -1,7 +1,7 @@
11import Foundation
22
3struct Project: Identifiable, Hashable, Sendable {
4 struct MailingList: Identifiable, Hashable, Sendable {
3nonisolated struct Project: Identifiable, Hashable, Sendable {
4 nonisolated struct MailingList: Identifiable, Hashable, Sendable {
55 let id: String
66 let name: String
77 let description: String?
@@ -22,8 +22,8 @@ struct Project: Identifiable, Hashable, Sendable {
2222 }
2323 }
2424
25 struct SourceRepo: Identifiable, Hashable, Sendable {
26 enum RepoType: String, Decodable, Sendable {
25 nonisolated struct SourceRepo: Identifiable, Hashable, Sendable {
26 nonisolated enum RepoType: String, Decodable, Sendable {
2727 case git = "GIT"
2828 case hg = "HG"
2929
@@ -47,7 +47,7 @@ struct Project: Identifiable, Hashable, Sendable {
4747 }
4848 }
4949
50 struct Tracker: Identifiable, Hashable, Sendable {
50 nonisolated struct Tracker: Identifiable, Hashable, Sendable {
5151 let id: String
5252 let name: String
5353 let description: String?
@@ -72,7 +72,7 @@ struct Project: Identifiable, Hashable, Sendable {
7272 let isFullyLoaded: Bool
7373
7474 /// Identity and display fields for a project.
75 struct Metadata: Sendable, Hashable {
75 nonisolated struct Metadata: Sendable, Hashable {
7676 let id: String
7777 let name: String
7878 let description: String?
@@ -82,7 +82,7 @@ struct Project: Identifiable, Hashable, Sendable {
8282 let updated: Date
8383 }
8484
85 struct Resources: Sendable, Hashable {
85 nonisolated struct Resources: Sendable, Hashable {
8686 let mailingLists: [MailingList]
8787 let sources: [SourceRepo]
8888 let trackers: [Tracker]
@@ -171,7 +171,7 @@ struct Project: Identifiable, Hashable, Sendable {
171171 }
172172}
173173
174extension Project.MailingList {
174nonisolated extension Project.MailingList {
175175 var displayName: String {
176176 Project.normalizedText(name) ?? "Untitled Mailing List"
177177 }
@@ -185,7 +185,7 @@ extension Project.MailingList {
185185 }
186186}
187187
188extension Project.SourceRepo {
188nonisolated extension Project.SourceRepo {
189189 var displayName: String {
190190 Project.normalizedText(name) ?? "Untitled Repository"
191191 }
@@ -203,7 +203,7 @@ extension Project.SourceRepo {
203203 }
204204}
205205
206extension Project.Tracker {
206nonisolated extension Project.Tracker {
207207 var displayName: String {
208208 Project.normalizedText(name) ?? "Untitled Tracker"
209209 }
@@ -221,13 +221,13 @@ extension Project.Tracker {
221221 }
222222}
223223
224extension String {
224nonisolated extension String {
225225 var srhtUsername: String {
226226 hasPrefix("~") ? String(dropFirst()) : self
227227 }
228228}
229229
230extension Visibility {
230nonisolated extension Visibility {
231231 var displayName: String {
232232 switch self {
233233 case .publicVisibility:
Hutch/Models/RepositoryACL.swift +2 −2
@@ -1,12 +1,12 @@
11import Foundation
22
3struct RepositoryACLEntry: Codable, Sendable, Identifiable, Hashable {
3nonisolated struct RepositoryACLEntry: Codable, Sendable, Identifiable, Hashable {
44 let id: Int
55 let mode: AccessMode
66 let entity: Entity
77}
88
9extension AccessMode {
9nonisolated extension AccessMode {
1010 var shortLabel: String { rawValue }
1111
1212 var displayName: String {
Hutch/Models/RepositorySummary.swift +4 −4
@@ -3,7 +3,7 @@ import Foundation
33/// Lightweight repository model matching the fields returned by the
44/// repositories list query. Avoids optionalizing all fields on the full
55/// `Repository` model.
6struct RepositorySummary: Codable, Sendable, Identifiable, Hashable {
6nonisolated struct RepositorySummary: Codable, Sendable, Identifiable, Hashable {
77 let id: Int
88 /// GraphQL resource identifier used by `repository(rid:)` queries.
99 let rid: String
@@ -15,13 +15,13 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable {
1515 let owner: Entity
1616 let head: Reference?
1717
18 enum CodingKeys: String, CodingKey {
18 nonisolated enum CodingKeys: String, CodingKey {
1919 case id, rid, service, name, description, visibility, updated, owner
2020 case head = "HEAD"
2121 }
2222
2323 /// Grouped initializer fields (single parameter keeps APIs explicit without exceeding parameter-count limits).
24 struct Fields: Sendable, Hashable {
24 nonisolated struct Fields: Sendable, Hashable {
2525 let id: Int
2626 let rid: String
2727 let service: SRHTService
@@ -59,7 +59,7 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable {
5959 }
6060}
6161
62extension RepositorySummary {
62nonisolated extension RepositorySummary {
6363 var defaultBranchName: String? {
6464 head?.name.replacingOccurrences(of: "refs/heads/", with: "")
6565 }
Hutch/Models/SystemStatusModels.swift +5 −5
@@ -1,6 +1,6 @@
11import Foundation
22
3enum StatusLevel: String, Codable, Sendable {
3nonisolated enum StatusLevel: String, Codable, Sendable {
44 case operational
55 case degraded
66 case majorOutage
@@ -32,7 +32,7 @@ enum StatusLevel: String, Codable, Sendable {
3232 }
3333}
3434
35struct StatusServiceState: Identifiable, Hashable, Codable, Sendable {
35nonisolated struct StatusServiceState: Identifiable, Hashable, Codable, Sendable {
3636 let id: String
3737 let name: String
3838 let slug: String?
@@ -40,7 +40,7 @@ struct StatusServiceState: Identifiable, Hashable, Codable, Sendable {
4040 let description: String?
4141}
4242
43struct StatusIncident: Identifiable, Hashable, Codable, Sendable {
43nonisolated struct StatusIncident: Identifiable, Hashable, Codable, Sendable {
4444 let id: String
4545 let title: String
4646 let summary: String?
@@ -50,7 +50,7 @@ struct StatusIncident: Identifiable, Hashable, Codable, Sendable {
5050 let isActive: Bool?
5151}
5252
53struct SystemStatusSnapshot: Hashable, Codable, Sendable {
53nonisolated struct SystemStatusSnapshot: Hashable, Codable, Sendable {
5454 let services: [StatusServiceState]
5555 let activeIncidents: [StatusIncident]
5656 let lastUpdated: Date
@@ -78,7 +78,7 @@ struct SystemStatusSnapshot: Hashable, Codable, Sendable {
7878 }
7979}
8080
81struct SystemStatusPageData: Sendable {
81nonisolated struct SystemStatusPageData: Sendable {
8282 let snapshot: SystemStatusSnapshot
8383 let recentIncidents: [StatusIncident]
8484}
Hutch/Models/TicketBulkAction.swift +3 −3
@@ -1,6 +1,6 @@
11import Foundation
22
3enum TicketBulkActionKind: String, Sendable {
3nonisolated enum TicketBulkActionKind: String, Sendable {
44 case close
55 case assign
66
@@ -23,7 +23,7 @@ enum TicketBulkActionKind: String, Sendable {
2323 }
2424}
2525
26struct TicketBulkActionResult: Identifiable, Sendable {
26nonisolated struct TicketBulkActionResult: Identifiable, Sendable {
2727 let id = UUID()
2828 let action: TicketBulkActionKind
2929 let totalCount: Int
@@ -82,7 +82,7 @@ struct TicketBulkActionResult: Identifiable, Sendable {
8282 }
8383}
8484
85struct TicketBulkActionFailure: Sendable {
85nonisolated struct TicketBulkActionFailure: Sendable {
8686 let ticketID: Int
8787 let message: String
8888}
Hutch/Models/Todo.swift +15 −15
@@ -3,7 +3,7 @@ import Foundation
33// MARK: - Enums
44
55/// Status of a ticket.
6enum TicketStatus: String, Codable, Sendable, CaseIterable {
6nonisolated enum TicketStatus: String, Codable, Sendable, CaseIterable {
77 case reported = "REPORTED"
88 case confirmed = "CONFIRMED"
99 case inProgress = "IN_PROGRESS"
@@ -30,7 +30,7 @@ enum TicketStatus: String, Codable, Sendable, CaseIterable {
3030}
3131
3232/// Resolution of a ticket.
33enum TicketResolution: String, Codable, Sendable {
33nonisolated enum TicketResolution: String, Codable, Sendable {
3434 case unresolved = "UNRESOLVED"
3535 case fixed = "FIXED"
3636 case implemented = "IMPLEMENTED"
@@ -64,7 +64,7 @@ enum TicketResolution: String, Codable, Sendable {
6464}
6565
6666/// Authenticity of a ticket or comment.
67enum Authenticity: String, Codable, Sendable {
67nonisolated enum Authenticity: String, Codable, Sendable {
6868 case authentic = "AUTHENTIC"
6969 case tampered = "TAMPERED"
7070 case unauthenticated = "UNAUTHENTICATED"
@@ -74,7 +74,7 @@ enum Authenticity: String, Codable, Sendable {
7474
7575/// A label that can be applied to tickets.
7676/// Named `TicketLabel` to avoid collision with `SwiftUI.Label`.
77struct TicketLabel: Codable, Sendable, Identifiable, Hashable {
77nonisolated struct TicketLabel: Codable, Sendable, Identifiable, Hashable {
7878 let id: Int
7979 let name: String
8080 let backgroundColor: String
@@ -83,7 +83,7 @@ struct TicketLabel: Codable, Sendable, Identifiable, Hashable {
8383
8484// MARK: - Tracker ACL
8585
86struct TrackerACLPermissions: Codable, Sendable, Hashable {
86nonisolated struct TrackerACLPermissions: Codable, Sendable, Hashable {
8787 let browse: Bool
8888 let submit: Bool
8989 let comment: Bool
@@ -91,7 +91,7 @@ struct TrackerACLPermissions: Codable, Sendable, Hashable {
9191 let triage: Bool
9292}
9393
94struct TrackerACL: Codable, Sendable, Identifiable, Hashable {
94nonisolated struct TrackerACL: Codable, Sendable, Identifiable, Hashable {
9595 let id: Int
9696 let created: Date
9797 let entity: Entity
@@ -112,7 +112,7 @@ struct TrackerACL: Codable, Sendable, Identifiable, Hashable {
112112 }
113113}
114114
115struct DefaultTrackerACL: Codable, Sendable, Hashable {
115nonisolated struct DefaultTrackerACL: Codable, Sendable, Hashable {
116116 let browse: Bool
117117 let submit: Bool
118118 let comment: Bool
@@ -133,7 +133,7 @@ struct DefaultTrackerACL: Codable, Sendable, Hashable {
133133// MARK: - Tracker
134134
135135/// A bug tracker from todo.sr.ht.
136struct Tracker: Codable, Sendable, Identifiable, Hashable {
136nonisolated struct Tracker: Codable, Sendable, Identifiable, Hashable {
137137 let id: Int
138138 let created: Date
139139 let updated: Date
@@ -154,7 +154,7 @@ struct Tracker: Codable, Sendable, Identifiable, Hashable {
154154// MARK: - Tracker Summary (for list view)
155155
156156/// Lightweight tracker model matching the fields returned by the trackers list query.
157struct TrackerSummary: Codable, Sendable, Identifiable, Hashable {
157nonisolated struct TrackerSummary: Codable, Sendable, Identifiable, Hashable {
158158 let id: Int
159159 /// GraphQL resource identifier used by `tracker(id:)` queries.
160160 let rid: String
@@ -168,7 +168,7 @@ struct TrackerSummary: Codable, Sendable, Identifiable, Hashable {
168168// MARK: - Ticket Summary (for list view)
169169
170170/// Lightweight ticket model for the list query.
171struct TicketSummary: Codable, Sendable, Identifiable, Hashable {
171nonisolated struct TicketSummary: Codable, Sendable, Identifiable, Hashable {
172172 let id: Int
173173 let title: String
174174 let status: TicketStatus
@@ -190,7 +190,7 @@ struct TicketSummary: Codable, Sendable, Identifiable, Hashable {
190190// MARK: - Ticket Detail
191191
192192/// Full ticket model for the detail view.
193struct TicketDetail: Codable, Sendable {
193nonisolated struct TicketDetail: Codable, Sendable {
194194 let id: Int
195195 let created: Date
196196 let updated: Date
@@ -207,7 +207,7 @@ struct TicketDetail: Codable, Sendable {
207207// MARK: - Event
208208
209209/// A timeline event on a ticket. Each event contains one or more changes.
210struct TicketEvent: Codable, Sendable, Identifiable {
210nonisolated struct TicketEvent: Codable, Sendable, Identifiable {
211211 let id: Int
212212 let created: Date
213213 var changes: [EventChange]
@@ -215,7 +215,7 @@ struct TicketEvent: Codable, Sendable, Identifiable {
215215
216216/// A single change within an event, decoded from the polymorphic EventDetail
217217/// interface using inline fragments.
218struct EventChange: Codable, Sendable, Identifiable {
218nonisolated struct EventChange: Codable, Sendable, Identifiable {
219219 let id: UUID
220220
221221 let eventType: String
@@ -272,12 +272,12 @@ struct EventChange: Codable, Sendable, Identifiable {
272272
273273/// Decoded from the `mentioned` field which may be a Ticket (with `id`) or
274274/// an Entity (with `canonicalName`) depending on the event type.
275struct MentionTarget: Codable, Sendable {
275nonisolated struct MentionTarget: Codable, Sendable {
276276 let id: Int?
277277 let canonicalName: String?
278278}
279279
280280/// Label info as returned within a LabelUpdate event change.
281struct EventLabel: Codable, Sendable {
281nonisolated struct EventLabel: Codable, Sendable {
282282 let name: String
283283}
Hutch/Models/User.swift +1 −1
@@ -1,7 +1,7 @@
11import Foundation
22
33/// A Sourcehut user from meta.sr.ht.
4struct User: Decodable, Sendable, Hashable {
4nonisolated struct User: Decodable, Sendable, Hashable {
55 let id: Int
66 let created: String?
77 let updated: String?
Hutch/Views/Lists/MailingListListView.swift +1 −1
@@ -100,7 +100,7 @@ final class MailingListListViewModel {
100100 query: Self.createMailingListMutation,
101101 variables: [
102102 "name": trimmedName,
103 "description": trimmedDescription.isEmpty ? nil as String? as Any : trimmedDescription,
103 "description": trimmedDescription.isEmpty ? nil as String? as any Sendable : trimmedDescription,
104104 "visibility": visibility.rawValue
105105 ],
106106 responseType: Response.self
Hutch/Views/More/TipStoreViewModel.swift +3 −1
@@ -32,7 +32,9 @@ final class TipStoreViewModel {
3232 var errorMessage: String?
3333 var statusMessage: String?
3434
35 private var transactionUpdatesTask: Task<Void, Never>?
35 // Internal task handle, not observable state; assigned only on the main
36 // actor and read once from the nonisolated deinit.
37 @ObservationIgnored nonisolated(unsafe) private var transactionUpdatesTask: Task<Void, Never>?
3638
3739 init() {
3840 transactionUpdatesTask = Task.detached(priority: .background) {
Hutch/Views/Repositories/RepositoryDetailViewModel.swift +1 −1
@@ -557,7 +557,7 @@ final class RepositoryDetailViewModel {
557557 variables: [
558558 "repoId": repository.id,
559559 "revspec": revspec,
560 "file": nil as String? as Any
560 "file": nil as String? as any Sendable
561561 ],
562562 file: MultipartUploadFile(
563563 variablePath: "file",
Hutch/Views/Repositories/SyntaxHighlighter.swift +1 −1
@@ -208,7 +208,7 @@ nonisolated final class SyntaxHighlighter {
208208 ]
209209}
210210
211private extension UIColor {
211nonisolated private extension UIColor {
212212 /// `#rrggbb` for HTML inline styles, or `nil` if the color isn't RGB-convertible.
213213 var hexRGBString: String? {
214214 var red: CGFloat = 0
Hutch/Views/Settings/SettingsViewModel.swift +5 −5
@@ -198,9 +198,9 @@ final class SettingsViewModel {
198198 do {
199199 let input: [String: any Sendable] = [
200200 "email": email,
201 "url": url.isEmpty ? nil as String? as Any : url,
202 "location": location.isEmpty ? nil as String? as Any : location,
203 "bio": bio.isEmpty ? nil as String? as Any : bio
201 "url": url.isEmpty ? nil as String? as any Sendable : url,
202 "location": location.isEmpty ? nil as String? as any Sendable : location,
203 "bio": bio.isEmpty ? nil as String? as any Sendable : bio
204204 ]
205205 let result = try await client.execute(
206206 service: .meta,
@@ -243,7 +243,7 @@ final class SettingsViewModel {
243243 do {
244244 // The input variable has avatar set to null; the actual file
245245 // is sent as a separate multipart part per graphql-multipart-request-spec.
246 let input: [String: any Sendable] = ["avatar": nil as String? as Any]
246 let input: [String: any Sendable] = ["avatar": nil as String? as any Sendable]
247247 let result = try await client.executeMultipart(
248248 service: .meta,
249249 query: Self.updateUserMutation,
@@ -286,7 +286,7 @@ final class SettingsViewModel {
286286 error = nil
287287
288288 do {
289 let input: [String: any Sendable] = ["avatar": nil as String? as Any]
289 let input: [String: any Sendable] = ["avatar": nil as String? as any Sendable]
290290 let result = try await client.execute(
291291 service: .meta,
292292 query: Self.updateUserMutation,
HutchTests/APICacheTests.swift +1
@@ -3,6 +3,7 @@ import Testing
33@testable import Hutch
44
55@Suite(.serialized)
6@MainActor
67struct APICacheTests {
78 private struct Payload: Codable, Sendable, Equatable {
89 let value: String
HutchTests/AppStateTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct AppStateTests {
67
78 @Test
HutchTests/BuildListViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct BuildListViewModelTests {
67
78 @Test
HutchTests/BuildTaskLogSearchTests.swift +1
@@ -1,6 +1,7 @@
11import Testing
22@testable import Hutch
33
4@MainActor
45struct BuildTaskLogSearchTests {
56
67 @Test
HutchTests/BuildTaskTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct BuildTaskTests {
67
78 @Test
HutchTests/BundleUserAgentTests.swift +1
@@ -40,6 +40,7 @@ private final class CapturingURLProtocol: URLProtocol, @unchecked Sendable {
4040
4141/// Tests are serialized because CapturingURLProtocol uses shared static state.
4242@Suite(.serialized)
43@MainActor
4344struct BundleUserAgentTests {
4445
4546 // MARK: Bundle extension
HutchTests/ContributionCalendarTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ContributionCalendarTests {
67
78 @Test
HutchTests/DeepLinkTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct DeepLinkTests {
67
78 @Test
HutchTests/GitObjectTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct GitObjectTests {
67
78 @Test
HutchTests/HomeViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct HomeViewModelTests {
67
78 @Test
HutchTests/HutchTests.swift +1
@@ -7,6 +7,7 @@
77
88import Testing
99
10@MainActor
1011struct HutchTests {
1112
1213 @Test func example() async throws {
HutchTests/InboxViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct InboxViewModelTests {
67
78 @Test
HutchTests/LookupHistoryStoreTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct LookupHistoryStoreTests {
67
78 @Test
HutchTests/MailingListActivityTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct MailingListActivityTests {
67
78 @Test
HutchTests/ManPageCatalogTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ManPageCatalogTests {
67 @Test
78 func loadsBundledCatalog() {
HutchTests/PatchsetTests.swift +2
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct PatchsetStatusTests {
67
78 @Test
@@ -46,6 +47,7 @@ struct PatchsetStatusTests {
4647 }
4748}
4849
50@MainActor
4951struct PatchsetSummaryTests {
5052
5153 @Test
HutchTests/ProjectFormTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ProjectFormTests {
67 @Test
78 func parsesTagsSplittingAndTrimming() {
HutchTests/ProjectPinStoreTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ProjectPinStoreTests {
67 @Test
78 func storesPinnedProjectsPerUser() {
HutchTests/ProjectTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ProjectTests {
67 private enum Fixture {
78 static let exampleWebsite = "https://example.com"
HutchTests/ReadmeViewTests.swift +5
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ReadmeViewTests {
67
78 @Test
@@ -28,6 +29,7 @@ struct ReadmeViewTests {
2829 }
2930}
3031
32@MainActor
3133struct MarkdownRenderingTests {
3234
3335 @Test
@@ -219,6 +221,7 @@ struct MarkdownRenderingTests {
219221 }
220222}
221223
224@MainActor
222225struct OrgRenderingTests {
223226
224227 @Test
@@ -376,6 +379,7 @@ struct OrgRenderingTests {
376379 }
377380}
378381
382@MainActor
379383struct RepositoryAssetURLTests {
380384
381385 @Test
@@ -391,6 +395,7 @@ struct RepositoryAssetURLTests {
391395 }
392396}
393397
398@MainActor
394399struct RepositoryLinkURLTests {
395400
396401 @Test
HutchTests/RecentActivityStoreTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct RecentActivityStoreTests {
67 @Test
78 func removeDropsMatchingEntryKeepingOthers() {
HutchTests/RepositoryACLViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct RepositoryACLViewModelTests {
67
78 @Test
HutchTests/RepositoryDeployKeyTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct RepositoryDeployKeyTests {
67 @Test
78 func decodesDeployKey() throws {
HutchTests/RepositoryListViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct RepositoryListViewModelTests {
67
78 @Test
HutchTests/RepositorySettingsViewModelTests.swift +1
@@ -10,6 +10,7 @@ private struct UpdateRepositoryInfoPayload: Decodable {
1010 let id: Int
1111}
1212
13@MainActor
1314struct RepositorySettingsViewModelTests {
1415
1516 @Test
HutchTests/SRHTClientTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct SRHTClientTests {
67
78 @Test
HutchTests/SRHTWebURLTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct SRHTWebURLTests {
67 private enum Expected {
78 static let chatOrigin = "https://chat.sr.ht"
HutchTests/ScopedSearchHistoryStoreTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct ScopedSearchHistoryStoreTests {
67
78 @Test
HutchTests/SettingsViewModelTests.swift +1
@@ -64,6 +64,7 @@ private struct DeleteResultPayload: Decodable {
6464}
6565
6666@Suite(.serialized)
67@MainActor
6768struct SettingsViewModelTests {
6869
6970 @Test
HutchTests/SettingsViewTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct SettingsViewTests {
67
78 @Test
HutchTests/SourceHutWebDeepLinkMapperTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct SourceHutWebDeepLinkMapperTests {
67 @Test
78 func mapsRepositoryURLs() {
HutchTests/SyntaxHighlighterTests.swift +4 −5
@@ -3,6 +3,7 @@ import Testing
33import UIKit
44@testable import Hutch
55
6@MainActor
67struct SyntaxHighlighterTests {
78 @Test
89 func mapsFileNamesToLanguages() {
@@ -24,9 +25,8 @@ struct SyntaxHighlighterTests {
2425 let highlighter = SyntaxHighlighter(theme: .light)
2526 let html = highlighter.highlightedHTML(for: "let answer = 42", language: "swift")
2627
27 let unwrapped = try? #require(html)
28 #expect(unwrapped?.contains("<span style=\"color:") == true)
29 #expect(unwrapped?.contains("answer") == true)
28 #expect(html?.contains("<span style=\"color:") == true)
29 #expect(html?.contains("answer") == true)
3030 }
3131
3232 @Test
@@ -42,7 +42,6 @@ struct SyntaxHighlighterTests {
4242 let font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular)
4343 let attributed = highlighter.attributedText(for: "print(\"hi\")", language: "python", font: font)
4444
45 let unwrapped = try? #require(attributed)
46 #expect(unwrapped?.string.contains("print") == true)
45 #expect(attributed?.string.contains("print") == true)
4746 }
4847}
HutchTests/SystemStatusRepositoryTests.swift +5 −4
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct SystemStatusRepositoryTests {
67
78 @Test
@@ -111,21 +112,21 @@ private struct TestSystemStatusService: SystemStatusServing {
111112}
112113
113114private extension SystemStatusRepositoryTests {
114 static let cachedSnapshotHTML = #"""
115 nonisolated static let cachedSnapshotHTML = #"""
115116 <div class="component" data-status="disrupted">
116117 <a href="/affected/git.sr.ht/">git.sr.ht</a>
117118 <span class="component-status">Disrupted</span>
118119 </div>
119120 """#
120121
121 static let cachedOperationalHTML = #"""
122 nonisolated static let cachedOperationalHTML = #"""
122123 <div class="component" data-status="ok">
123124 <a href="/affected/meta.sr.ht/">meta.sr.ht</a>
124125 <span class="component-status">Operational</span>
125126 </div>
126127 """#
127128
128 static let cachedIncidentRSS = #"""
129 nonisolated static let cachedIncidentRSS = #"""
129130 <rss version="2.0">
130131 <channel>
131132 <item>
@@ -139,7 +140,7 @@ private extension SystemStatusRepositoryTests {
139140 </rss>
140141 """#
141142
142 static let emptyRSS = #"""
143 nonisolated static let emptyRSS = #"""
143144 <rss version="2.0">
144145 <channel></channel>
145146 </rss>
HutchTests/SystemStatusServiceTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct SystemStatusServiceTests {
67 private enum Fixture {
78 static let ddosIssueURL = "https://status.sr.ht/issues/2026-04-06-ddos-attack/"
HutchTests/TicketBulkActionTests.swift +1
@@ -1,6 +1,7 @@
11import Testing
22@testable import Hutch
33
4@MainActor
45struct TicketBulkActionTests {
56
67 @Test
HutchTests/TicketDetailViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct TicketDetailViewModelTests {
67
78 @Test
HutchTests/TicketSavedFilterStoreTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct TicketSavedFilterStoreTests {
67
78 @Test
HutchTests/TipStoreViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct TipStoreViewModelTests {
67
78 @Test
HutchTests/TrackerListViewModelTests.swift +1
@@ -2,6 +2,7 @@ import Foundation
22import Testing
33@testable import Hutch
44
5@MainActor
56struct TrackerListViewModelTests {
67
78 @Test
HutchTests/TrackerManagementViewModelTests.swift +1
@@ -1,6 +1,7 @@
11import Testing
22@testable import Hutch
33
4@MainActor
45struct TrackerManagementViewModelTests {
56
67 @Test
HutchWidgetExtension/ContributionGraphWidget.swift +4
@@ -28,12 +28,16 @@ struct ContributionGraphTimelineProvider: TimelineProvider {
2828 }
2929
3030 func getSnapshot(in _: Context, completion: @escaping (ContributionGraphEntry) -> Void) {
31 // WidgetKit's completion is safe to call from the task; rebind as
32 // nonisolated(unsafe) so it can cross into the `sending` Task closure.
33 nonisolated(unsafe) let completion = completion
3134 Task {
3235 completion(await loadEntry())
3336 }
3437 }
3538
3639 func getTimeline(in _: Context, completion: @escaping (Timeline<ContributionGraphEntry>) -> Void) {
40 nonisolated(unsafe) let completion = completion
3741 Task {
3842 let entry = await loadEntry()
3943 let refreshDate = Calendar.contributionCalendar.date(byAdding: .hour, value: 1, to: Date()) ?? Date().addingTimeInterval(3600)
ROADMAP.txt +24 −16
@@ -158,7 +158,7 @@ so "breaking change" does not apply. These buckets track *user-visible scale*.
158158| v3.11.0 | ~~Mailing list subscribe/unsubscribe toggle~~ (shipped) | Ingest-surfaced; state via the `subscriptions` query (the `subscription` field is a trap) |
159159| v3.12.0 | Accessibility | Independent, device-verified |
160160| v4.0.0 | Localization *with* translations | The only true re-presentation |
161| — | Swift 6 language mode; cache reads | Internal; ride along, no tag |
161| — | ~~Swift 6 language mode~~ (done); cache reads | Internal; ride along, no tag |
162162
1631633.9.0 was cut this session, bundling the hub.sr.ht writes with the other
164164features listed. That reorders the original plan: the "What's cooking" ingest
@@ -384,21 +384,29 @@ rather than a bare `TODO`.)
384384 saved-search store for an intent to open. Add the intent once global
385385 saved-search persistence exists.
386386
387### Swift 6 language mode — no release of its own
388
389The project builds in Swift 5 language mode with
390`SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`. Moving to Swift 6 is blocked on
391concurrency diagnostics that are warnings today and errors there:
392
393- `APICacheTests` and `BundleUserAgentTests` call main-actor-isolated
394 initialisers and properties from nonisolated contexts, and `await` a few
395 expressions without marking them. Roughly 20 warnings, all in tests.
396- Response types are implicitly `@MainActor` under the default isolation, so
397 their `Decodable` conformances are too. Decoding one from a nonisolated
398 context — an `async let` over a raw `client.execute`, say — warns now and
399 fails then. The pattern that avoids it is `async let` over `@MainActor`
400 methods, as in `HomeViewModel.loadDashboard` and
401 `NotificationPreferencesViewModel.load`.
387### Swift 6 language mode — done
388
389`SWIFT_VERSION` is now `6.0` (keeping `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`).
390The migration was mostly mechanical, in a few buckets:
391
392- **Models are `nonisolated`.** Under MainActor-default, every value-type model
393 was implicitly `@MainActor`; the pure data types in `Models/` (and utility
394 extensions like `Date.relativeDescription`, `DateFormatter+SRHT`, `SRHTWebURL`)
395 are now `nonisolated`, so the nonisolated networking layer can use them.
396- **App Intents statics.** `AppIntent`/`AppEntity`/`AppEnum` `static var`s were
397 "global shared mutable state"; the stored ones are now `static let`.
398- **Sendable dictionaries.** `nil as String? as Any` in `[String: any Sendable]`
399 GraphQL variable dicts became `nil as String? as any Sendable`.
400- **`UserDefaults`** gets a retroactive `@unchecked Sendable` (documented
401 thread-safe) since it threads through account sessions and stores.
402- **WidgetKit** completion handlers are rebound `nonisolated(unsafe)` to cross
403 into their `Task {}`; the `@Observable` `TipStoreViewModel` task handle is
404 `@ObservationIgnored nonisolated(unsafe)` for its nonisolated `deinit`.
405- **Tests** run on `@MainActor` (they exercise MainActor app code), with a few
406 constant fixtures marked `nonisolated` for use inside `@Sendable` stub
407 closures.
408
409Builds and the full suite are clean in Swift 6 mode with no behaviour change.
402410
403411### Cache reads that bypass the client — no release of its own
404412