Commit 81520e73a1
Unsigned
Layout: unified · split
Hutch.xcodeproj/project.pbxproj +8 −8
| @@ -622,7 +622,7 @@ | ||
| 622 | 622 | SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; |
| 623 | 623 | SWIFT_EMIT_LOC_STRINGS = YES; |
| 624 | 624 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 625 | SWIFT_VERSION = 5.0; | |
| 625 | SWIFT_VERSION = 6.0; | |
| 626 | 626 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 627 | 627 | }; |
| 628 | 628 | name = Debug; |
| @@ -659,7 +659,7 @@ | ||
| 659 | 659 | SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; |
| 660 | 660 | SWIFT_EMIT_LOC_STRINGS = YES; |
| 661 | 661 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 662 | SWIFT_VERSION = 5.0; | |
| 662 | SWIFT_VERSION = 6.0; | |
| 663 | 663 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 664 | 664 | }; |
| 665 | 665 | name = Release; |
| @@ -680,7 +680,7 @@ | ||
| 680 | 680 | SWIFT_APPROACHABLE_CONCURRENCY = YES; |
| 681 | 681 | SWIFT_EMIT_LOC_STRINGS = NO; |
| 682 | 682 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 683 | SWIFT_VERSION = 5.0; | |
| 683 | SWIFT_VERSION = 6.0; | |
| 684 | 684 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 685 | 685 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Hutch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Hutch"; |
| 686 | 686 | }; |
| @@ -702,7 +702,7 @@ | ||
| 702 | 702 | SWIFT_APPROACHABLE_CONCURRENCY = YES; |
| 703 | 703 | SWIFT_EMIT_LOC_STRINGS = NO; |
| 704 | 704 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 705 | SWIFT_VERSION = 5.0; | |
| 705 | SWIFT_VERSION = 6.0; | |
| 706 | 706 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 707 | 707 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Hutch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Hutch"; |
| 708 | 708 | }; |
| @@ -732,7 +732,7 @@ | ||
| 732 | 732 | SWIFT_APPROACHABLE_CONCURRENCY = YES; |
| 733 | 733 | SWIFT_EMIT_LOC_STRINGS = YES; |
| 734 | 734 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 735 | SWIFT_VERSION = 5.0; | |
| 735 | SWIFT_VERSION = 6.0; | |
| 736 | 736 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 737 | 737 | }; |
| 738 | 738 | name = Debug; |
| @@ -761,7 +761,7 @@ | ||
| 761 | 761 | SWIFT_APPROACHABLE_CONCURRENCY = YES; |
| 762 | 762 | SWIFT_EMIT_LOC_STRINGS = YES; |
| 763 | 763 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 764 | SWIFT_VERSION = 5.0; | |
| 764 | SWIFT_VERSION = 6.0; | |
| 765 | 765 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 766 | 766 | }; |
| 767 | 767 | name = Release; |
| @@ -790,7 +790,7 @@ | ||
| 790 | 790 | SWIFT_APPROACHABLE_CONCURRENCY = YES; |
| 791 | 791 | SWIFT_EMIT_LOC_STRINGS = YES; |
| 792 | 792 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 793 | SWIFT_VERSION = 5.0; | |
| 793 | SWIFT_VERSION = 6.0; | |
| 794 | 794 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 795 | 795 | }; |
| 796 | 796 | name = Debug; |
| @@ -819,7 +819,7 @@ | ||
| 819 | 819 | SWIFT_APPROACHABLE_CONCURRENCY = YES; |
| 820 | 820 | SWIFT_EMIT_LOC_STRINGS = YES; |
| 821 | 821 | SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; |
| 822 | SWIFT_VERSION = 5.0; | |
| 822 | SWIFT_VERSION = 6.0; | |
| 823 | 823 | TARGETED_DEVICE_FAMILY = "1,2"; |
| 824 | 824 | }; |
| 825 | 825 | name = Release; |
Hutch/App/HutchIntents.swift +38 −38
| @@ -4,9 +4,9 @@ import Foundation | ||
| 4 | 4 | // MARK: - Navigation Intents |
| 5 | 5 | |
| 6 | 6 | struct 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 | |
| 10 | 10 | |
| 11 | 11 | var route: HutchRoute { .workQueue(scope: .all) } |
| 12 | 12 | |
| @@ -18,9 +18,9 @@ struct OpenWorkQueueIntent: AppIntent { | ||
| 18 | 18 | } |
| 19 | 19 | |
| 20 | 20 | struct 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 | |
| 24 | 24 | |
| 25 | 25 | var route: HutchRoute { .recentActivity } |
| 26 | 26 | |
| @@ -32,9 +32,9 @@ struct OpenRecentActivityIntent: AppIntent { | ||
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | struct 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 | |
| 38 | 38 | |
| 39 | 39 | var route: HutchRoute { .systemStatus } |
| 40 | 40 | |
| @@ -46,9 +46,9 @@ struct OpenSystemStatusIntent: AppIntent { | ||
| 46 | 46 | } |
| 47 | 47 | |
| 48 | 48 | struct 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 | |
| 52 | 52 | |
| 53 | 53 | @Parameter(title: "Pinned Resource") |
| 54 | 54 | var pinnedResource: PinnedResourceEntity |
| @@ -63,9 +63,9 @@ struct OpenPinnedResourceIntent: AppIntent { | ||
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | struct 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 | |
| 69 | 69 | |
| 70 | 70 | @Parameter(title: "Project") |
| 71 | 71 | var project: ProjectEntity |
| @@ -84,16 +84,16 @@ struct OpenProjectDashboardIntent: AppIntent { | ||
| 84 | 84 | enum HutchShortcutScope: String, AppEnum { |
| 85 | 85 | case all |
| 86 | 86 | |
| 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] = [ | |
| 89 | 89 | .all: "All" |
| 90 | 90 | ] |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | 93 | struct 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 | |
| 97 | 97 | |
| 98 | 98 | @Parameter(title: "Scope", default: .all) |
| 99 | 99 | var scope: HutchShortcutScope |
| @@ -108,9 +108,9 @@ struct OpenFailedBuildsIntent: AppIntent { | ||
| 108 | 108 | } |
| 109 | 109 | |
| 110 | 110 | struct 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 | |
| 114 | 114 | |
| 115 | 115 | @Parameter(title: "Scope", default: .all) |
| 116 | 116 | var scope: HutchShortcutScope |
| @@ -125,9 +125,9 @@ struct OpenAssignedTicketsIntent: AppIntent { | ||
| 125 | 125 | } |
| 126 | 126 | |
| 127 | 127 | struct 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 | |
| 131 | 131 | |
| 132 | 132 | @Parameter(title: "Query") |
| 133 | 133 | var query: String |
| @@ -155,8 +155,8 @@ struct SearchHutchIntent: AppIntent { | ||
| 155 | 155 | // MARK: - App Entities |
| 156 | 156 | |
| 157 | 157 | struct 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() | |
| 160 | 160 | |
| 161 | 161 | let id: String |
| 162 | 162 | let name: String |
| @@ -181,8 +181,8 @@ struct PinnedResourceQuery: EntityQuery { | ||
| 181 | 181 | } |
| 182 | 182 | |
| 183 | 183 | struct 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() | |
| 186 | 186 | |
| 187 | 187 | let id: String |
| 188 | 188 | let name: String |
| @@ -278,8 +278,8 @@ enum HutchIntentEntityStore { | ||
| 278 | 278 | // MARK: - Existing Read-Only Summary Intents |
| 279 | 279 | |
| 280 | 280 | struct 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.") | |
| 283 | 283 | |
| 284 | 284 | @MainActor |
| 285 | 285 | func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog { |
| @@ -302,8 +302,8 @@ struct CheckSystemStatusIntent: AppIntent { | ||
| 302 | 302 | } |
| 303 | 303 | |
| 304 | 304 | struct 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.") | |
| 307 | 307 | |
| 308 | 308 | @MainActor |
| 309 | 309 | func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog { |
| @@ -357,8 +357,8 @@ extension LookupType: AppEnum { | ||
| 357 | 357 | // MARK: - Mutating Intents |
| 358 | 358 | |
| 359 | 359 | struct 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.") | |
| 362 | 362 | |
| 363 | 363 | @MainActor |
| 364 | 364 | func perform() async throws -> some IntentResult & ProvidesDialog { |
| @@ -368,8 +368,8 @@ struct ClearRecentActivityIntent: AppIntent { | ||
| 368 | 368 | } |
| 369 | 369 | |
| 370 | 370 | struct 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.") | |
| 373 | 373 | |
| 374 | 374 | @Parameter(title: "Pinned Resource") |
| 375 | 375 | var pinnedResource: PinnedResourceEntity |
Hutch/Extensions/Date+Relative.swift +1 −1
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | extension Date { | |
| 3 | nonisolated extension Date { | |
| 4 | 4 | /// A short relative description like "2h ago", "3d ago", or "Jan 5, 2025". |
| 5 | 5 | var relativeDescription: String { |
| 6 | 6 | let formatter = RelativeDateTimeFormatter() |
Hutch/Extensions/DateFormatter+SRHT.swift +1 −1
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | extension DateFormatter { | |
| 3 | nonisolated extension DateFormatter { | |
| 4 | 4 | /// Formatter for the sr.ht `Time` scalar: `%Y-%m-%dT%H:%M:%SZ` (UTC). |
| 5 | 5 | static let srht: DateFormatter = { |
| 6 | 6 | let formatter = DateFormatter() |
Hutch/Extensions/Foundation+Sendable.swift added +7
| @@ -0,0 +1,7 @@ | ||
| 1 | import 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. | |
| 7 | extension UserDefaults: @retroactive @unchecked Sendable {} | |
Hutch/Extensions/SRHTWebURL.swift +1 −1
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | enum SRHTWebURL { | |
| 3 | nonisolated enum SRHTWebURL { | |
| 4 | 4 | static let chat = URL(string: "https://chat.sr.ht")! |
| 5 | 5 | static let status = URL(string: "https://status.sr.ht")! |
| 6 | 6 | /// RSS/Atom feed for status incidents (HTML snapshot uses `status`). |
Hutch/Models/AccountEntry.swift +1 −1
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | 3 | /// A stored sr.ht account (token + resolved username). |
| 4 | struct AccountEntry: Codable, Identifiable, Equatable, Sendable { | |
| 4 | nonisolated struct AccountEntry: Codable, Identifiable, Equatable, Sendable { | |
| 5 | 5 | let id: String |
| 6 | 6 | var username: String |
| 7 | 7 | var token: String |
Hutch/Models/ArtifactInfo.swift +3 −3
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | 3 | /// An artifact with its parent reference name, used in the artifacts tab. |
| 4 | struct ArtifactInfo: Codable, Sendable, Identifiable { | |
| 4 | nonisolated struct ArtifactInfo: Codable, Sendable, Identifiable { | |
| 5 | 5 | let id: Int |
| 6 | 6 | let filename: String |
| 7 | 7 | let checksum: String |
| @@ -9,13 +9,13 @@ struct ArtifactInfo: Codable, Sendable, Identifiable { | ||
| 9 | 9 | let url: URL |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | struct ArtifactPage: Codable, Sendable { | |
| 12 | nonisolated struct ArtifactPage: Codable, Sendable { | |
| 13 | 13 | let results: [ArtifactInfo] |
| 14 | 14 | let cursor: String? |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | /// A reference (tag) that has associated artifacts. |
| 18 | struct ReferenceWithArtifacts: Codable, Sendable, Identifiable { | |
| 18 | nonisolated struct ReferenceWithArtifacts: Codable, Sendable, Identifiable { | |
| 19 | 19 | var id: String { name } |
| 20 | 20 | let name: String |
| 21 | 21 | let artifacts: [ArtifactInfo] |
Hutch/Models/Builds.swift +9 −9
| @@ -3,7 +3,7 @@ import Foundation | ||
| 3 | 3 | // MARK: - Enums |
| 4 | 4 | |
| 5 | 5 | /// Status of a build job. |
| 6 | enum JobStatus: String, Codable, Sendable { | |
| 6 | nonisolated enum JobStatus: String, Codable, Sendable { | |
| 7 | 7 | case pending = "PENDING" |
| 8 | 8 | case queued = "QUEUED" |
| 9 | 9 | case running = "RUNNING" |
| @@ -30,7 +30,7 @@ enum JobStatus: String, Codable, Sendable { | ||
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | /// Status of a single build task within a job. |
| 33 | enum TaskStatus: String, Codable, Sendable { | |
| 33 | nonisolated enum TaskStatus: String, Codable, Sendable { | |
| 34 | 34 | case pending = "PENDING" |
| 35 | 35 | case running = "RUNNING" |
| 36 | 36 | case success = "SUCCESS" |
| @@ -41,7 +41,7 @@ enum TaskStatus: String, Codable, Sendable { | ||
| 41 | 41 | // MARK: - Build Task |
| 42 | 42 | |
| 43 | 43 | /// A single task within a build job. |
| 44 | struct BuildTask: Codable, Sendable, Identifiable, Equatable { | |
| 44 | nonisolated struct BuildTask: Codable, Sendable, Identifiable, Equatable { | |
| 45 | 45 | private(set) var ordinal: Int? |
| 46 | 46 | let name: String |
| 47 | 47 | let status: TaskStatus |
| @@ -74,7 +74,7 @@ struct BuildTask: Codable, Sendable, Identifiable, Equatable { | ||
| 74 | 74 | // MARK: - Job Summary (for list view) |
| 75 | 75 | |
| 76 | 76 | /// Lightweight job model matching the fields returned by the jobs list query. |
| 77 | struct JobSummary: Codable, Sendable, Identifiable, Hashable { | |
| 77 | nonisolated struct JobSummary: Codable, Sendable, Identifiable, Hashable { | |
| 78 | 78 | let id: Int |
| 79 | 79 | let created: Date |
| 80 | 80 | let updated: Date |
| @@ -103,7 +103,7 @@ struct JobSummary: Codable, Sendable, Identifiable, Hashable { | ||
| 103 | 103 | } |
| 104 | 104 | |
| 105 | 105 | /// Minimal task info for the list query. |
| 106 | struct JobTaskSummary: Codable, Sendable, Hashable { | |
| 106 | nonisolated struct JobTaskSummary: Codable, Sendable, Hashable { | |
| 107 | 107 | let name: String |
| 108 | 108 | let status: TaskStatus |
| 109 | 109 | } |
| @@ -111,7 +111,7 @@ struct JobTaskSummary: Codable, Sendable, Hashable { | ||
| 111 | 111 | // MARK: - Job Detail (for detail view) |
| 112 | 112 | |
| 113 | 113 | /// Full job model with all fields for the detail view. |
| 114 | struct JobDetail: Codable, Sendable, Equatable { | |
| 114 | nonisolated struct JobDetail: Codable, Sendable, Equatable { | |
| 115 | 115 | let id: Int |
| 116 | 116 | let created: Date |
| 117 | 117 | let updated: Date |
| @@ -128,7 +128,7 @@ struct JobDetail: Codable, Sendable, Equatable { | ||
| 128 | 128 | } |
| 129 | 129 | |
| 130 | 130 | /// A downloadable artifact emitted by a build job. |
| 131 | struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { | |
| 131 | nonisolated struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { | |
| 132 | 132 | let id: Int |
| 133 | 133 | let created: Date |
| 134 | 134 | let path: String |
| @@ -145,14 +145,14 @@ struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { | ||
| 145 | 145 | } |
| 146 | 146 | |
| 147 | 147 | /// The log associated with a build job. |
| 148 | struct BuildLog: Codable, Sendable, Equatable { | |
| 148 | nonisolated struct BuildLog: Codable, Sendable, Equatable { | |
| 149 | 149 | let fullURL: String |
| 150 | 150 | } |
| 151 | 151 | |
| 152 | 152 | // MARK: - Job Group |
| 153 | 153 | |
| 154 | 154 | /// A group of related build jobs. |
| 155 | struct JobGroup: Codable, Sendable, Identifiable { | |
| 155 | nonisolated struct JobGroup: Codable, Sendable, Identifiable { | |
| 156 | 156 | let id: Int |
| 157 | 157 | let created: Date |
| 158 | 158 | let note: String? |
Hutch/Models/CommitDetail.swift +8 −8
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | 3 | /// Full commit detail returned by the revparse_single query. |
| 4 | struct CommitDetail: Codable, Sendable, Identifiable { | |
| 4 | nonisolated struct CommitDetail: Codable, Sendable, Identifiable { | |
| 5 | 5 | let id: String |
| 6 | 6 | let shortId: String |
| 7 | 7 | let author: CommitAuthor |
| @@ -26,39 +26,39 @@ struct CommitDetail: Codable, Sendable, Identifiable { | ||
| 26 | 26 | } |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | struct CommitTrailer: Codable, Sendable, Identifiable { | |
| 29 | nonisolated struct CommitTrailer: Codable, Sendable, Identifiable { | |
| 30 | 30 | var id: String { "\(name):\(value)" } |
| 31 | 31 | let name: String |
| 32 | 32 | let value: String |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | struct ParentCommit: Codable, Sendable, Identifiable, Hashable { | |
| 35 | nonisolated struct ParentCommit: Codable, Sendable, Identifiable, Hashable { | |
| 36 | 36 | let id: String |
| 37 | 37 | let shortId: String |
| 38 | 38 | let author: ParentAuthor |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | struct ParentAuthor: Codable, Sendable, Hashable { | |
| 41 | nonisolated struct ParentAuthor: Codable, Sendable, Hashable { | |
| 42 | 42 | let name: String |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | struct CommitTree: Codable, Sendable { | |
| 45 | nonisolated struct CommitTree: Codable, Sendable { | |
| 46 | 46 | let entries: CommitTreeEntries |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | struct CommitTreeEntries: Codable, Sendable { | |
| 49 | nonisolated struct CommitTreeEntries: Codable, Sendable { | |
| 50 | 50 | let results: [CommitTreeEntry] |
| 51 | 51 | let cursor: String? |
| 52 | 52 | } |
| 53 | 53 | |
| 54 | struct CommitTreeEntry: Codable, Sendable, Identifiable { | |
| 54 | nonisolated struct CommitTreeEntry: Codable, Sendable, Identifiable { | |
| 55 | 55 | let id: String |
| 56 | 56 | let name: String |
| 57 | 57 | let mode: Int |
| 58 | 58 | let object: CommitTreeObject? |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | struct CommitTreeObject: Codable, Sendable { | |
| 61 | nonisolated struct CommitTreeObject: Codable, Sendable { | |
| 62 | 62 | let type: String? |
| 63 | 63 | let id: String? |
| 64 | 64 | let shortId: String? |
Hutch/Models/CommitSummary.swift +2 −2
| @@ -2,7 +2,7 @@ import Foundation | ||
| 2 | 2 | |
| 3 | 3 | /// Lightweight commit model for list views. Matches the subset of fields |
| 4 | 4 | /// returned by the repository log query. |
| 5 | struct CommitSummary: Codable, Sendable, Identifiable, Hashable { | |
| 5 | nonisolated struct CommitSummary: Codable, Sendable, Identifiable, Hashable { | |
| 6 | 6 | let id: String |
| 7 | 7 | let shortId: String |
| 8 | 8 | let author: CommitAuthor |
| @@ -15,7 +15,7 @@ struct CommitSummary: Codable, Sendable, Identifiable, Hashable { | ||
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | /// A compact author representation used in commit list responses. |
| 18 | struct CommitAuthor: Codable, Sendable, Hashable { | |
| 18 | nonisolated struct CommitAuthor: Codable, Sendable, Hashable { | |
| 19 | 19 | let name: String |
| 20 | 20 | let email: String? |
| 21 | 21 | let time: Date |
Hutch/Models/ContributionCalendar.swift +14 −14
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | struct ContributionCalendarResponse: Decodable, Sendable, Hashable { | |
| 3 | nonisolated struct ContributionCalendarResponse: Decodable, Sendable, Hashable { | |
| 4 | 4 | let actor: String |
| 5 | 5 | let from: Date |
| 6 | 6 | let to: Date |
| @@ -9,7 +9,7 @@ struct ContributionCalendarResponse: Decodable, Sendable, Hashable { | ||
| 9 | 9 | let indexingState: ContributionIndexingState |
| 10 | 10 | let days: [ContributionDay] |
| 11 | 11 | |
| 12 | enum CodingKeys: String, CodingKey { | |
| 12 | nonisolated enum CodingKeys: String, CodingKey { | |
| 13 | 13 | case actor |
| 14 | 14 | case from |
| 15 | 15 | case to |
| @@ -59,14 +59,14 @@ struct ContributionCalendarResponse: Decodable, Sendable, Hashable { | ||
| 59 | 59 | } |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | struct ContributionDay: Decodable, Sendable, Hashable, Identifiable { | |
| 62 | nonisolated struct ContributionDay: Decodable, Sendable, Hashable, Identifiable { | |
| 63 | 63 | var id: Date { date } |
| 64 | 64 | |
| 65 | 65 | let date: Date |
| 66 | 66 | let count: Int |
| 67 | 67 | let score: Double |
| 68 | 68 | |
| 69 | enum CodingKeys: String, CodingKey { | |
| 69 | nonisolated enum CodingKeys: String, CodingKey { | |
| 70 | 70 | case date |
| 71 | 71 | case count |
| 72 | 72 | case score |
| @@ -90,7 +90,7 @@ struct ContributionDay: Decodable, Sendable, Hashable, Identifiable { | ||
| 90 | 90 | } |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | struct ContributionStatsResponse: Decodable, Sendable, Hashable { | |
| 93 | nonisolated struct ContributionStatsResponse: Decodable, Sendable, Hashable { | |
| 94 | 94 | let actor: String |
| 95 | 95 | let from: Date |
| 96 | 96 | let to: Date |
| @@ -103,7 +103,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { | ||
| 103 | 103 | let longestStreak: Int |
| 104 | 104 | let currentStreak: Int |
| 105 | 105 | |
| 106 | enum CodingKeys: String, CodingKey { | |
| 106 | nonisolated enum CodingKeys: String, CodingKey { | |
| 107 | 107 | case actor |
| 108 | 108 | case from |
| 109 | 109 | case to |
| @@ -117,7 +117,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { | ||
| 117 | 117 | case currentStreak = "current_streak" |
| 118 | 118 | } |
| 119 | 119 | |
| 120 | struct StatsWindow: Sendable, Hashable { | |
| 120 | nonisolated struct StatsWindow: Sendable, Hashable { | |
| 121 | 121 | let actor: String |
| 122 | 122 | let from: Date |
| 123 | 123 | let to: Date |
| @@ -126,7 +126,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { | ||
| 126 | 126 | let indexingState: ContributionIndexingState |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | struct StatsTotals: Sendable, Hashable { | |
| 129 | nonisolated struct StatsTotals: Sendable, Hashable { | |
| 130 | 130 | let totalEvents: Int |
| 131 | 131 | let totalScore: Double |
| 132 | 132 | let activeDays: Int |
| @@ -181,13 +181,13 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { | ||
| 181 | 181 | } |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | enum ContributionIndexingState: String, Codable, Sendable, Hashable { | |
| 184 | nonisolated enum ContributionIndexingState: String, Codable, Sendable, Hashable { | |
| 185 | 185 | case pending |
| 186 | 186 | case indexed |
| 187 | 187 | case error |
| 188 | 188 | } |
| 189 | 189 | |
| 190 | enum ContributionIntensity: Int, Sendable, CaseIterable { | |
| 190 | nonisolated enum ContributionIntensity: Int, Sendable, CaseIterable { | |
| 191 | 191 | case empty = 0 |
| 192 | 192 | case level1 = 1 |
| 193 | 193 | case level2 = 2 |
| @@ -210,12 +210,12 @@ enum ContributionIntensity: Int, Sendable, CaseIterable { | ||
| 210 | 210 | } |
| 211 | 211 | } |
| 212 | 212 | |
| 213 | struct ContributionWeek: Sendable, Hashable { | |
| 213 | nonisolated struct ContributionWeek: Sendable, Hashable { | |
| 214 | 214 | let startDate: Date |
| 215 | 215 | let days: [ContributionDay] |
| 216 | 216 | } |
| 217 | 217 | |
| 218 | enum ContributionCalendarLayout { | |
| 218 | nonisolated enum ContributionCalendarLayout { | |
| 219 | 219 | static func weekColumns( |
| 220 | 220 | from days: [ContributionDay], |
| 221 | 221 | calendar: Calendar = .contributionCalendar |
| @@ -241,7 +241,7 @@ enum ContributionCalendarLayout { | ||
| 241 | 241 | } |
| 242 | 242 | } |
| 243 | 243 | |
| 244 | enum ContributionDateParser { | |
| 244 | nonisolated enum ContributionDateParser { | |
| 245 | 245 | static func parse(_ rawValue: String) -> Date? { |
| 246 | 246 | let parts = rawValue.split(separator: "-", omittingEmptySubsequences: false) |
| 247 | 247 | guard |
| @@ -348,7 +348,7 @@ enum ContributionDateParser { | ||
| 348 | 348 | } |
| 349 | 349 | } |
| 350 | 350 | |
| 351 | extension Calendar { | |
| 351 | nonisolated extension Calendar { | |
| 352 | 352 | static var contributionCalendar: Calendar { |
| 353 | 353 | var calendar = Calendar(identifier: .gregorian) |
| 354 | 354 | calendar.firstWeekday = 1 |
Hutch/Models/Git.swift +20 −20
| @@ -3,14 +3,14 @@ import Foundation | ||
| 3 | 3 | // MARK: - Enums |
| 4 | 4 | |
| 5 | 5 | /// Repository visibility level. |
| 6 | enum Visibility: String, Codable, Sendable { | |
| 6 | nonisolated enum Visibility: String, Codable, Sendable { | |
| 7 | 7 | case publicVisibility = "PUBLIC" |
| 8 | 8 | case unlisted = "UNLISTED" |
| 9 | 9 | case privateVisibility = "PRIVATE" |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | 12 | /// Repository access mode. |
| 13 | enum AccessMode: String, Codable, Sendable, CaseIterable { | |
| 13 | nonisolated enum AccessMode: String, Codable, Sendable, CaseIterable { | |
| 14 | 14 | case ro = "RO" |
| 15 | 15 | case rw = "RW" |
| 16 | 16 | } |
| @@ -19,14 +19,14 @@ enum AccessMode: String, Codable, Sendable, CaseIterable { | ||
| 19 | 19 | |
| 20 | 20 | /// The `Entity` GraphQL interface from git.sr.ht. Represents the owner of a |
| 21 | 21 | /// resource (typically a user). |
| 22 | struct Entity: Codable, Sendable, Hashable { | |
| 22 | nonisolated struct Entity: Codable, Sendable, Hashable { | |
| 23 | 23 | let canonicalName: String |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | 26 | // MARK: - Repository |
| 27 | 27 | |
| 28 | 28 | /// A git repository from git.sr.ht. |
| 29 | struct Repository: Codable, Sendable, Identifiable { | |
| 29 | nonisolated struct Repository: Codable, Sendable, Identifiable { | |
| 30 | 30 | let id: Int |
| 31 | 31 | let created: Date |
| 32 | 32 | let updated: Date |
| @@ -37,7 +37,7 @@ struct Repository: Codable, Sendable, Identifiable { | ||
| 37 | 37 | let accessMode: AccessMode |
| 38 | 38 | let owner: Entity |
| 39 | 39 | |
| 40 | enum CodingKeys: String, CodingKey { | |
| 40 | nonisolated enum CodingKeys: String, CodingKey { | |
| 41 | 41 | case id, created, updated, name, description, visibility, readme |
| 42 | 42 | case accessMode = "access" |
| 43 | 43 | case owner |
| @@ -47,7 +47,7 @@ struct Repository: Codable, Sendable, Identifiable { | ||
| 47 | 47 | // MARK: - Signature |
| 48 | 48 | |
| 49 | 49 | /// A Git commit/tag signature (author or committer). |
| 50 | struct Signature: Codable, Sendable { | |
| 50 | nonisolated struct Signature: Codable, Sendable { | |
| 51 | 51 | let name: String |
| 52 | 52 | let email: String |
| 53 | 53 | let time: Date |
| @@ -56,7 +56,7 @@ struct Signature: Codable, Sendable { | ||
| 56 | 56 | // MARK: - Trailer |
| 57 | 57 | |
| 58 | 58 | /// A Git commit trailer (e.g. "Signed-off-by", "Co-authored-by"). |
| 59 | struct Trailer: Codable, Sendable { | |
| 59 | nonisolated struct Trailer: Codable, Sendable { | |
| 60 | 60 | let name: String |
| 61 | 61 | let value: String |
| 62 | 62 | } |
| @@ -64,7 +64,7 @@ struct Trailer: Codable, Sendable { | ||
| 64 | 64 | // MARK: - Commit |
| 65 | 65 | |
| 66 | 66 | /// A Git commit from git.sr.ht. |
| 67 | struct Commit: Codable, Sendable, Identifiable { | |
| 67 | nonisolated struct Commit: Codable, Sendable, Identifiable { | |
| 68 | 68 | let id: String |
| 69 | 69 | let shortId: String |
| 70 | 70 | let author: Signature |
| @@ -77,13 +77,13 @@ struct Commit: Codable, Sendable, Identifiable { | ||
| 77 | 77 | // MARK: - Reference |
| 78 | 78 | |
| 79 | 79 | /// A Git reference (branch or tag name). |
| 80 | struct Reference: Codable, Sendable, Hashable { | |
| 80 | nonisolated struct Reference: Codable, Sendable, Hashable { | |
| 81 | 81 | let name: String |
| 82 | 82 | let target: String? |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | 85 | /// A Git reference enriched with the date of its tip commit or tag, for display in the Refs tab. |
| 86 | struct ReferenceDetail: Sendable, Hashable { | |
| 86 | nonisolated struct ReferenceDetail: Sendable, Hashable { | |
| 87 | 87 | let name: String |
| 88 | 88 | let target: String? |
| 89 | 89 | let date: Date? |
| @@ -92,7 +92,7 @@ struct ReferenceDetail: Sendable, Hashable { | ||
| 92 | 92 | // MARK: - TreeEntry |
| 93 | 93 | |
| 94 | 94 | /// An entry in a Git tree (file or directory). |
| 95 | struct TreeEntry: Codable, Sendable, Identifiable { | |
| 95 | nonisolated struct TreeEntry: Codable, Sendable, Identifiable { | |
| 96 | 96 | let id: String |
| 97 | 97 | let name: String |
| 98 | 98 | let mode: Int? |
| @@ -104,41 +104,41 @@ struct TreeEntry: Codable, Sendable, Identifiable { | ||
| 104 | 104 | /// Both TextBlob and BinaryBlob have type == "BLOB"; they are |
| 105 | 105 | /// distinguished by the presence of the "text" key (TextBlob) vs |
| 106 | 106 | /// the "content" key (BinaryBlob). |
| 107 | enum GitObject: Sendable { | |
| 107 | nonisolated enum GitObject: Sendable { | |
| 108 | 108 | case tree(GitTree) |
| 109 | 109 | case textBlob(GitTextBlob) |
| 110 | 110 | case binaryBlob(GitBinaryBlob) |
| 111 | 111 | case unknown |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | struct GitTree: Codable, Sendable { | |
| 114 | nonisolated struct GitTree: Codable, Sendable { | |
| 115 | 115 | let id: String? |
| 116 | 116 | let shortId: String? |
| 117 | 117 | let entries: GitTreeEntryPage? |
| 118 | 118 | } |
| 119 | 119 | |
| 120 | struct GitTextBlob: Codable, Sendable { | |
| 120 | nonisolated struct GitTextBlob: Codable, Sendable { | |
| 121 | 121 | let id: String? |
| 122 | 122 | let shortId: String? |
| 123 | 123 | let text: String? |
| 124 | 124 | let size: Int? |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | struct GitBinaryBlob: Codable, Sendable { | |
| 127 | nonisolated struct GitBinaryBlob: Codable, Sendable { | |
| 128 | 128 | let id: String? |
| 129 | 129 | let shortId: String? |
| 130 | 130 | let size: Int? |
| 131 | 131 | let content: String? |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | struct GitTreeEntryPage: Codable, Sendable { | |
| 134 | nonisolated struct GitTreeEntryPage: Codable, Sendable { | |
| 135 | 135 | let results: [TreeEntry] |
| 136 | 136 | let cursor: String? |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | // MARK: - GitObject Codable |
| 140 | 140 | |
| 141 | extension GitObject: Codable { | |
| 141 | nonisolated extension GitObject: Codable { | |
| 142 | 142 | private enum CodingKeys: String, CodingKey { |
| 143 | 143 | case type, id, shortId, entries, text, size, content |
| 144 | 144 | case typename = "__typename" |
| @@ -211,7 +211,7 @@ extension GitObject: Codable { | ||
| 211 | 211 | } |
| 212 | 212 | |
| 213 | 213 | /// Convenience helpers for checking object type. |
| 214 | extension GitObject { | |
| 214 | nonisolated extension GitObject { | |
| 215 | 215 | var isTree: Bool { |
| 216 | 216 | if case .tree = self { return true } |
| 217 | 217 | return false |
| @@ -230,7 +230,7 @@ extension GitObject { | ||
| 230 | 230 | // MARK: - Tag |
| 231 | 231 | |
| 232 | 232 | /// An annotated Git tag from git.sr.ht. |
| 233 | struct Tag: Codable, Sendable, Identifiable { | |
| 233 | nonisolated struct Tag: Codable, Sendable, Identifiable { | |
| 234 | 234 | let id: String |
| 235 | 235 | let shortId: String |
| 236 | 236 | let name: String |
| @@ -241,7 +241,7 @@ struct Tag: Codable, Sendable, Identifiable { | ||
| 241 | 241 | // MARK: - Artifact |
| 242 | 242 | |
| 243 | 243 | /// A release artifact attached to a Git tag. |
| 244 | struct Artifact: Codable, Sendable, Identifiable { | |
| 244 | nonisolated struct Artifact: Codable, Sendable, Identifiable { | |
| 245 | 245 | let id: Int |
| 246 | 246 | let created: Date |
| 247 | 247 | let filename: String |
Hutch/Models/Inbox.swift +10 −10
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | struct InboxThreadSummary: Identifiable, Hashable, Sendable { | |
| 3 | nonisolated struct InboxThreadSummary: Identifiable, Hashable, Sendable { | |
| 4 | 4 | let rootEmailID: Int |
| 5 | 5 | let rootMessageID: String |
| 6 | 6 | let threadRootEmailIDs: [Int] |
| @@ -76,7 +76,7 @@ struct InboxThreadSummary: Identifiable, Hashable, Sendable { | ||
| 76 | 76 | } |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | struct InboxMessage: Identifiable, Hashable, Sendable { | |
| 79 | nonisolated struct InboxMessage: Identifiable, Hashable, Sendable { | |
| 80 | 80 | let id: Int |
| 81 | 81 | let author: Entity |
| 82 | 82 | let date: Date |
| @@ -89,12 +89,12 @@ struct InboxMessage: Identifiable, Hashable, Sendable { | ||
| 89 | 89 | let rawMessageURL: URL? |
| 90 | 90 | } |
| 91 | 91 | |
| 92 | enum InboxMessageContentBlock: Hashable, Sendable { | |
| 92 | nonisolated enum InboxMessageContentBlock: Hashable, Sendable { | |
| 93 | 93 | case plainText(String) |
| 94 | 94 | case diff(String) |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | struct InboxThreadDetail: Sendable { | |
| 97 | nonisolated struct InboxThreadDetail: Sendable { | |
| 98 | 98 | let id: String |
| 99 | 99 | let rootEmailID: Int |
| 100 | 100 | let rootMessageID: String |
| @@ -114,7 +114,7 @@ struct InboxThreadDetail: Sendable { | ||
| 114 | 114 | } |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | extension InboxThreadDetail { | |
| 117 | nonisolated extension InboxThreadDetail { | |
| 118 | 118 | var replyRecipient: String { |
| 119 | 119 | "\(listOwner.canonicalName)/\(listName)@lists.sr.ht" |
| 120 | 120 | } |
| @@ -144,7 +144,7 @@ extension InboxThreadDetail { | ||
| 144 | 144 | } |
| 145 | 145 | } |
| 146 | 146 | |
| 147 | struct MailComposeDraft: Sendable { | |
| 147 | nonisolated struct MailComposeDraft: Sendable { | |
| 148 | 148 | let recipients: [String] |
| 149 | 149 | let ccRecipients: [String] |
| 150 | 150 | let subject: String |
| @@ -155,20 +155,20 @@ struct MailComposeDraft: Sendable { | ||
| 155 | 155 | } |
| 156 | 156 | } |
| 157 | 157 | |
| 158 | extension MailComposeDraft: Identifiable {} | |
| 158 | nonisolated extension MailComposeDraft: Identifiable {} | |
| 159 | 159 | |
| 160 | struct InboxMailingListReference: Decodable, Sendable, Hashable, Identifiable { | |
| 160 | nonisolated struct InboxMailingListReference: Decodable, Sendable, Hashable, Identifiable { | |
| 161 | 161 | let id: Int |
| 162 | 162 | let rid: String |
| 163 | 163 | let name: String |
| 164 | 164 | let owner: Entity |
| 165 | 165 | } |
| 166 | 166 | |
| 167 | struct InboxPatchPreview: Decodable, Sendable, Hashable { | |
| 167 | nonisolated struct InboxPatchPreview: Decodable, Sendable, Hashable { | |
| 168 | 168 | let subject: String? |
| 169 | 169 | } |
| 170 | 170 | |
| 171 | enum InboxReadStateStore { | |
| 171 | nonisolated enum InboxReadStateStore { | |
| 172 | 172 | private static let key = "InboxThreadLastViewed" |
| 173 | 173 | private static let baselineKey = "InboxUnreadBaseline" |
| 174 | 174 | |
Hutch/Models/Meta.swift +8 −8
| @@ -3,7 +3,7 @@ import Foundation | ||
| 3 | 3 | // MARK: - User Profile (full) |
| 4 | 4 | |
| 5 | 5 | /// Extended user profile from meta.sr.ht with all fields from the `me` query. |
| 6 | struct UserProfile: Codable, Sendable { | |
| 6 | nonisolated struct UserProfile: Codable, Sendable { | |
| 7 | 7 | let username: String |
| 8 | 8 | let canonicalName: String |
| 9 | 9 | let email: String |
| @@ -20,7 +20,7 @@ struct UserProfile: Codable, Sendable { | ||
| 20 | 20 | |
| 21 | 21 | // MARK: - SSH Key |
| 22 | 22 | |
| 23 | struct SSHKey: Codable, Sendable, Identifiable { | |
| 23 | nonisolated struct SSHKey: Codable, Sendable, Identifiable { | |
| 24 | 24 | let id: Int |
| 25 | 25 | let comment: String? |
| 26 | 26 | let created: Date |
| @@ -34,27 +34,27 @@ struct SSHKey: Codable, Sendable, Identifiable { | ||
| 34 | 34 | } |
| 35 | 35 | } |
| 36 | 36 | |
| 37 | struct SSHKeyPage: Codable, Sendable { | |
| 37 | nonisolated struct SSHKeyPage: Codable, Sendable { | |
| 38 | 38 | let results: [SSHKey] |
| 39 | 39 | let cursor: String? |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | // MARK: - PGP Key |
| 43 | 43 | |
| 44 | struct PGPKey: Codable, Sendable, Identifiable { | |
| 44 | nonisolated struct PGPKey: Codable, Sendable, Identifiable { | |
| 45 | 45 | let id: Int |
| 46 | 46 | let fingerprint: String |
| 47 | 47 | let created: Date |
| 48 | 48 | } |
| 49 | 49 | |
| 50 | struct PGPKeyPage: Codable, Sendable { | |
| 50 | nonisolated struct PGPKeyPage: Codable, Sendable { | |
| 51 | 51 | let results: [PGPKey] |
| 52 | 52 | let cursor: String? |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | 55 | // MARK: - Subscription |
| 56 | 56 | |
| 57 | struct Subscription: Codable, Sendable { | |
| 57 | nonisolated struct Subscription: Codable, Sendable { | |
| 58 | 58 | let status: String? |
| 59 | 59 | let autorenew: Bool? |
| 60 | 60 | let interval: String? |
| @@ -62,7 +62,7 @@ struct Subscription: Codable, Sendable { | ||
| 62 | 62 | |
| 63 | 63 | // MARK: - Personal Access Token |
| 64 | 64 | |
| 65 | struct PersonalAccessToken: Codable, Sendable, Identifiable { | |
| 65 | nonisolated struct PersonalAccessToken: Codable, Sendable, Identifiable { | |
| 66 | 66 | let id: Int |
| 67 | 67 | let issued: Date |
| 68 | 68 | let expires: Date? |
| @@ -72,7 +72,7 @@ struct PersonalAccessToken: Codable, Sendable, Identifiable { | ||
| 72 | 72 | |
| 73 | 73 | /// One entry in meta.sr.ht's audit log: a security-relevant action on the |
| 74 | 74 | /// account, with the address it came from. |
| 75 | struct AuditLogEntry: Codable, Sendable, Identifiable { | |
| 75 | nonisolated struct AuditLogEntry: Codable, Sendable, Identifiable { | |
| 76 | 76 | let id: Int |
| 77 | 77 | let created: Date |
| 78 | 78 | let ipAddress: String |
Hutch/Models/Paste.swift +3 −3
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | struct PasteFile: Codable, Sendable, Hashable, Identifiable { | |
| 3 | nonisolated struct PasteFile: Codable, Sendable, Hashable, Identifiable { | |
| 4 | 4 | let filename: String? |
| 5 | 5 | let hash: String |
| 6 | 6 | let contents: URL? |
| @@ -8,7 +8,7 @@ struct PasteFile: Codable, Sendable, Hashable, Identifiable { | ||
| 8 | 8 | var id: String { hash } |
| 9 | 9 | } |
| 10 | 10 | |
| 11 | struct Paste: Codable, Sendable, Identifiable, Hashable { | |
| 11 | nonisolated struct Paste: Codable, Sendable, Identifiable, Hashable { | |
| 12 | 12 | let id: String |
| 13 | 13 | let created: Date |
| 14 | 14 | let visibility: Visibility |
| @@ -16,7 +16,7 @@ struct Paste: Codable, Sendable, Identifiable, Hashable { | ||
| 16 | 16 | let user: Entity |
| 17 | 17 | } |
| 18 | 18 | |
| 19 | struct PasteUploadDraft: Identifiable, Equatable, Sendable { | |
| 19 | nonisolated struct PasteUploadDraft: Identifiable, Equatable, Sendable { | |
| 20 | 20 | let id: UUID |
| 21 | 21 | var filename: String |
| 22 | 22 | var contents: String |
Hutch/Models/Patchset.swift +6 −6
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | 3 | /// Review state of a patchset on lists.sr.ht. |
| 4 | enum PatchsetStatus: String, Codable, Sendable, CaseIterable { | |
| 4 | nonisolated enum PatchsetStatus: String, Codable, Sendable, CaseIterable { | |
| 5 | 5 | case unknown = "UNKNOWN" |
| 6 | 6 | case proposed = "PROPOSED" |
| 7 | 7 | case needsRevision = "NEEDS_REVISION" |
| @@ -55,7 +55,7 @@ enum PatchsetStatus: String, Codable, Sendable, CaseIterable { | ||
| 55 | 55 | /// A patchset as it appears in a mailing list listing, derived from the thread's |
| 56 | 56 | /// root email rather than a dedicated patchsets query — `MailingList` exposes no |
| 57 | 57 | /// such field. |
| 58 | struct PatchsetSummary: Identifiable, Hashable, Sendable { | |
| 58 | nonisolated struct PatchsetSummary: Identifiable, Hashable, Sendable { | |
| 59 | 59 | let id: Int |
| 60 | 60 | let subject: String |
| 61 | 61 | let version: Int |
| @@ -70,7 +70,7 @@ struct PatchsetSummary: Identifiable, Hashable, Sendable { | ||
| 70 | 70 | } |
| 71 | 71 | |
| 72 | 72 | /// One email within a patchset: either the cover letter or a single patch. |
| 73 | struct PatchsetEmail: Identifiable, Hashable, Sendable { | |
| 73 | nonisolated struct PatchsetEmail: Identifiable, Hashable, Sendable { | |
| 74 | 74 | let id: Int |
| 75 | 75 | let subject: String |
| 76 | 76 | let date: Date? |
| @@ -88,13 +88,13 @@ struct PatchsetEmail: Identifiable, Hashable, Sendable { | ||
| 88 | 88 | } |
| 89 | 89 | |
| 90 | 90 | /// A build or check reported against a patchset. |
| 91 | struct PatchsetToolResult: Identifiable, Hashable, Sendable { | |
| 91 | nonisolated struct PatchsetToolResult: Identifiable, Hashable, Sendable { | |
| 92 | 92 | let id: Int |
| 93 | 93 | let icon: PatchsetToolIcon |
| 94 | 94 | let details: String |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | enum PatchsetToolIcon: String, Codable, Sendable { | |
| 97 | nonisolated enum PatchsetToolIcon: String, Codable, Sendable { | |
| 98 | 98 | case pending = "PENDING" |
| 99 | 99 | case waiting = "WAITING" |
| 100 | 100 | case success = "SUCCESS" |
| @@ -112,7 +112,7 @@ enum PatchsetToolIcon: String, Codable, Sendable { | ||
| 112 | 112 | } |
| 113 | 113 | |
| 114 | 114 | /// A patchset with its cover letter, patches, and review context. |
| 115 | struct PatchsetDetail: Sendable { | |
| 115 | nonisolated struct PatchsetDetail: Sendable { | |
| 116 | 116 | let id: Int |
| 117 | 117 | let created: Date |
| 118 | 118 | let updated: Date |
Hutch/Models/Project.swift +12 −12
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | struct Project: Identifiable, Hashable, Sendable { | |
| 4 | struct MailingList: Identifiable, Hashable, Sendable { | |
| 3 | nonisolated struct Project: Identifiable, Hashable, Sendable { | |
| 4 | nonisolated struct MailingList: Identifiable, Hashable, Sendable { | |
| 5 | 5 | let id: String |
| 6 | 6 | let name: String |
| 7 | 7 | let description: String? |
| @@ -22,8 +22,8 @@ struct Project: Identifiable, Hashable, Sendable { | ||
| 22 | 22 | } |
| 23 | 23 | } |
| 24 | 24 | |
| 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 { | |
| 27 | 27 | case git = "GIT" |
| 28 | 28 | case hg = "HG" |
| 29 | 29 | |
| @@ -47,7 +47,7 @@ struct Project: Identifiable, Hashable, Sendable { | ||
| 47 | 47 | } |
| 48 | 48 | } |
| 49 | 49 | |
| 50 | struct Tracker: Identifiable, Hashable, Sendable { | |
| 50 | nonisolated struct Tracker: Identifiable, Hashable, Sendable { | |
| 51 | 51 | let id: String |
| 52 | 52 | let name: String |
| 53 | 53 | let description: String? |
| @@ -72,7 +72,7 @@ struct Project: Identifiable, Hashable, Sendable { | ||
| 72 | 72 | let isFullyLoaded: Bool |
| 73 | 73 | |
| 74 | 74 | /// Identity and display fields for a project. |
| 75 | struct Metadata: Sendable, Hashable { | |
| 75 | nonisolated struct Metadata: Sendable, Hashable { | |
| 76 | 76 | let id: String |
| 77 | 77 | let name: String |
| 78 | 78 | let description: String? |
| @@ -82,7 +82,7 @@ struct Project: Identifiable, Hashable, Sendable { | ||
| 82 | 82 | let updated: Date |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | struct Resources: Sendable, Hashable { | |
| 85 | nonisolated struct Resources: Sendable, Hashable { | |
| 86 | 86 | let mailingLists: [MailingList] |
| 87 | 87 | let sources: [SourceRepo] |
| 88 | 88 | let trackers: [Tracker] |
| @@ -171,7 +171,7 @@ struct Project: Identifiable, Hashable, Sendable { | ||
| 171 | 171 | } |
| 172 | 172 | } |
| 173 | 173 | |
| 174 | extension Project.MailingList { | |
| 174 | nonisolated extension Project.MailingList { | |
| 175 | 175 | var displayName: String { |
| 176 | 176 | Project.normalizedText(name) ?? "Untitled Mailing List" |
| 177 | 177 | } |
| @@ -185,7 +185,7 @@ extension Project.MailingList { | ||
| 185 | 185 | } |
| 186 | 186 | } |
| 187 | 187 | |
| 188 | extension Project.SourceRepo { | |
| 188 | nonisolated extension Project.SourceRepo { | |
| 189 | 189 | var displayName: String { |
| 190 | 190 | Project.normalizedText(name) ?? "Untitled Repository" |
| 191 | 191 | } |
| @@ -203,7 +203,7 @@ extension Project.SourceRepo { | ||
| 203 | 203 | } |
| 204 | 204 | } |
| 205 | 205 | |
| 206 | extension Project.Tracker { | |
| 206 | nonisolated extension Project.Tracker { | |
| 207 | 207 | var displayName: String { |
| 208 | 208 | Project.normalizedText(name) ?? "Untitled Tracker" |
| 209 | 209 | } |
| @@ -221,13 +221,13 @@ extension Project.Tracker { | ||
| 221 | 221 | } |
| 222 | 222 | } |
| 223 | 223 | |
| 224 | extension String { | |
| 224 | nonisolated extension String { | |
| 225 | 225 | var srhtUsername: String { |
| 226 | 226 | hasPrefix("~") ? String(dropFirst()) : self |
| 227 | 227 | } |
| 228 | 228 | } |
| 229 | 229 | |
| 230 | extension Visibility { | |
| 230 | nonisolated extension Visibility { | |
| 231 | 231 | var displayName: String { |
| 232 | 232 | switch self { |
| 233 | 233 | case .publicVisibility: |
Hutch/Models/RepositoryACL.swift +2 −2
| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | struct RepositoryACLEntry: Codable, Sendable, Identifiable, Hashable { | |
| 3 | nonisolated struct RepositoryACLEntry: Codable, Sendable, Identifiable, Hashable { | |
| 4 | 4 | let id: Int |
| 5 | 5 | let mode: AccessMode |
| 6 | 6 | let entity: Entity |
| 7 | 7 | } |
| 8 | 8 | |
| 9 | extension AccessMode { | |
| 9 | nonisolated extension AccessMode { | |
| 10 | 10 | var shortLabel: String { rawValue } |
| 11 | 11 | |
| 12 | 12 | var displayName: String { |
Hutch/Models/RepositorySummary.swift +4 −4
| @@ -3,7 +3,7 @@ import Foundation | ||
| 3 | 3 | /// Lightweight repository model matching the fields returned by the |
| 4 | 4 | /// repositories list query. Avoids optionalizing all fields on the full |
| 5 | 5 | /// `Repository` model. |
| 6 | struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { | |
| 6 | nonisolated struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { | |
| 7 | 7 | let id: Int |
| 8 | 8 | /// GraphQL resource identifier used by `repository(rid:)` queries. |
| 9 | 9 | let rid: String |
| @@ -15,13 +15,13 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { | ||
| 15 | 15 | let owner: Entity |
| 16 | 16 | let head: Reference? |
| 17 | 17 | |
| 18 | enum CodingKeys: String, CodingKey { | |
| 18 | nonisolated enum CodingKeys: String, CodingKey { | |
| 19 | 19 | case id, rid, service, name, description, visibility, updated, owner |
| 20 | 20 | case head = "HEAD" |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | 23 | /// Grouped initializer fields (single parameter keeps APIs explicit without exceeding parameter-count limits). |
| 24 | struct Fields: Sendable, Hashable { | |
| 24 | nonisolated struct Fields: Sendable, Hashable { | |
| 25 | 25 | let id: Int |
| 26 | 26 | let rid: String |
| 27 | 27 | let service: SRHTService |
| @@ -59,7 +59,7 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { | ||
| 59 | 59 | } |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | extension RepositorySummary { | |
| 62 | nonisolated extension RepositorySummary { | |
| 63 | 63 | var defaultBranchName: String? { |
| 64 | 64 | head?.name.replacingOccurrences(of: "refs/heads/", with: "") |
| 65 | 65 | } |
Hutch/Models/SystemStatusModels.swift +5 −5
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | enum StatusLevel: String, Codable, Sendable { | |
| 3 | nonisolated enum StatusLevel: String, Codable, Sendable { | |
| 4 | 4 | case operational |
| 5 | 5 | case degraded |
| 6 | 6 | case majorOutage |
| @@ -32,7 +32,7 @@ enum StatusLevel: String, Codable, Sendable { | ||
| 32 | 32 | } |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | struct StatusServiceState: Identifiable, Hashable, Codable, Sendable { | |
| 35 | nonisolated struct StatusServiceState: Identifiable, Hashable, Codable, Sendable { | |
| 36 | 36 | let id: String |
| 37 | 37 | let name: String |
| 38 | 38 | let slug: String? |
| @@ -40,7 +40,7 @@ struct StatusServiceState: Identifiable, Hashable, Codable, Sendable { | ||
| 40 | 40 | let description: String? |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | struct StatusIncident: Identifiable, Hashable, Codable, Sendable { | |
| 43 | nonisolated struct StatusIncident: Identifiable, Hashable, Codable, Sendable { | |
| 44 | 44 | let id: String |
| 45 | 45 | let title: String |
| 46 | 46 | let summary: String? |
| @@ -50,7 +50,7 @@ struct StatusIncident: Identifiable, Hashable, Codable, Sendable { | ||
| 50 | 50 | let isActive: Bool? |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | struct SystemStatusSnapshot: Hashable, Codable, Sendable { | |
| 53 | nonisolated struct SystemStatusSnapshot: Hashable, Codable, Sendable { | |
| 54 | 54 | let services: [StatusServiceState] |
| 55 | 55 | let activeIncidents: [StatusIncident] |
| 56 | 56 | let lastUpdated: Date |
| @@ -78,7 +78,7 @@ struct SystemStatusSnapshot: Hashable, Codable, Sendable { | ||
| 78 | 78 | } |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | struct SystemStatusPageData: Sendable { | |
| 81 | nonisolated struct SystemStatusPageData: Sendable { | |
| 82 | 82 | let snapshot: SystemStatusSnapshot |
| 83 | 83 | let recentIncidents: [StatusIncident] |
| 84 | 84 | } |
Hutch/Models/TicketBulkAction.swift +3 −3
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | enum TicketBulkActionKind: String, Sendable { | |
| 3 | nonisolated enum TicketBulkActionKind: String, Sendable { | |
| 4 | 4 | case close |
| 5 | 5 | case assign |
| 6 | 6 | |
| @@ -23,7 +23,7 @@ enum TicketBulkActionKind: String, Sendable { | ||
| 23 | 23 | } |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | struct TicketBulkActionResult: Identifiable, Sendable { | |
| 26 | nonisolated struct TicketBulkActionResult: Identifiable, Sendable { | |
| 27 | 27 | let id = UUID() |
| 28 | 28 | let action: TicketBulkActionKind |
| 29 | 29 | let totalCount: Int |
| @@ -82,7 +82,7 @@ struct TicketBulkActionResult: Identifiable, Sendable { | ||
| 82 | 82 | } |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | struct TicketBulkActionFailure: Sendable { | |
| 85 | nonisolated struct TicketBulkActionFailure: Sendable { | |
| 86 | 86 | let ticketID: Int |
| 87 | 87 | let message: String |
| 88 | 88 | } |
Hutch/Models/Todo.swift +15 −15
| @@ -3,7 +3,7 @@ import Foundation | ||
| 3 | 3 | // MARK: - Enums |
| 4 | 4 | |
| 5 | 5 | /// Status of a ticket. |
| 6 | enum TicketStatus: String, Codable, Sendable, CaseIterable { | |
| 6 | nonisolated enum TicketStatus: String, Codable, Sendable, CaseIterable { | |
| 7 | 7 | case reported = "REPORTED" |
| 8 | 8 | case confirmed = "CONFIRMED" |
| 9 | 9 | case inProgress = "IN_PROGRESS" |
| @@ -30,7 +30,7 @@ enum TicketStatus: String, Codable, Sendable, CaseIterable { | ||
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | /// Resolution of a ticket. |
| 33 | enum TicketResolution: String, Codable, Sendable { | |
| 33 | nonisolated enum TicketResolution: String, Codable, Sendable { | |
| 34 | 34 | case unresolved = "UNRESOLVED" |
| 35 | 35 | case fixed = "FIXED" |
| 36 | 36 | case implemented = "IMPLEMENTED" |
| @@ -64,7 +64,7 @@ enum TicketResolution: String, Codable, Sendable { | ||
| 64 | 64 | } |
| 65 | 65 | |
| 66 | 66 | /// Authenticity of a ticket or comment. |
| 67 | enum Authenticity: String, Codable, Sendable { | |
| 67 | nonisolated enum Authenticity: String, Codable, Sendable { | |
| 68 | 68 | case authentic = "AUTHENTIC" |
| 69 | 69 | case tampered = "TAMPERED" |
| 70 | 70 | case unauthenticated = "UNAUTHENTICATED" |
| @@ -74,7 +74,7 @@ enum Authenticity: String, Codable, Sendable { | ||
| 74 | 74 | |
| 75 | 75 | /// A label that can be applied to tickets. |
| 76 | 76 | /// Named `TicketLabel` to avoid collision with `SwiftUI.Label`. |
| 77 | struct TicketLabel: Codable, Sendable, Identifiable, Hashable { | |
| 77 | nonisolated struct TicketLabel: Codable, Sendable, Identifiable, Hashable { | |
| 78 | 78 | let id: Int |
| 79 | 79 | let name: String |
| 80 | 80 | let backgroundColor: String |
| @@ -83,7 +83,7 @@ struct TicketLabel: Codable, Sendable, Identifiable, Hashable { | ||
| 83 | 83 | |
| 84 | 84 | // MARK: - Tracker ACL |
| 85 | 85 | |
| 86 | struct TrackerACLPermissions: Codable, Sendable, Hashable { | |
| 86 | nonisolated struct TrackerACLPermissions: Codable, Sendable, Hashable { | |
| 87 | 87 | let browse: Bool |
| 88 | 88 | let submit: Bool |
| 89 | 89 | let comment: Bool |
| @@ -91,7 +91,7 @@ struct TrackerACLPermissions: Codable, Sendable, Hashable { | ||
| 91 | 91 | let triage: Bool |
| 92 | 92 | } |
| 93 | 93 | |
| 94 | struct TrackerACL: Codable, Sendable, Identifiable, Hashable { | |
| 94 | nonisolated struct TrackerACL: Codable, Sendable, Identifiable, Hashable { | |
| 95 | 95 | let id: Int |
| 96 | 96 | let created: Date |
| 97 | 97 | let entity: Entity |
| @@ -112,7 +112,7 @@ struct TrackerACL: Codable, Sendable, Identifiable, Hashable { | ||
| 112 | 112 | } |
| 113 | 113 | } |
| 114 | 114 | |
| 115 | struct DefaultTrackerACL: Codable, Sendable, Hashable { | |
| 115 | nonisolated struct DefaultTrackerACL: Codable, Sendable, Hashable { | |
| 116 | 116 | let browse: Bool |
| 117 | 117 | let submit: Bool |
| 118 | 118 | let comment: Bool |
| @@ -133,7 +133,7 @@ struct DefaultTrackerACL: Codable, Sendable, Hashable { | ||
| 133 | 133 | // MARK: - Tracker |
| 134 | 134 | |
| 135 | 135 | /// A bug tracker from todo.sr.ht. |
| 136 | struct Tracker: Codable, Sendable, Identifiable, Hashable { | |
| 136 | nonisolated struct Tracker: Codable, Sendable, Identifiable, Hashable { | |
| 137 | 137 | let id: Int |
| 138 | 138 | let created: Date |
| 139 | 139 | let updated: Date |
| @@ -154,7 +154,7 @@ struct Tracker: Codable, Sendable, Identifiable, Hashable { | ||
| 154 | 154 | // MARK: - Tracker Summary (for list view) |
| 155 | 155 | |
| 156 | 156 | /// Lightweight tracker model matching the fields returned by the trackers list query. |
| 157 | struct TrackerSummary: Codable, Sendable, Identifiable, Hashable { | |
| 157 | nonisolated struct TrackerSummary: Codable, Sendable, Identifiable, Hashable { | |
| 158 | 158 | let id: Int |
| 159 | 159 | /// GraphQL resource identifier used by `tracker(id:)` queries. |
| 160 | 160 | let rid: String |
| @@ -168,7 +168,7 @@ struct TrackerSummary: Codable, Sendable, Identifiable, Hashable { | ||
| 168 | 168 | // MARK: - Ticket Summary (for list view) |
| 169 | 169 | |
| 170 | 170 | /// Lightweight ticket model for the list query. |
| 171 | struct TicketSummary: Codable, Sendable, Identifiable, Hashable { | |
| 171 | nonisolated struct TicketSummary: Codable, Sendable, Identifiable, Hashable { | |
| 172 | 172 | let id: Int |
| 173 | 173 | let title: String |
| 174 | 174 | let status: TicketStatus |
| @@ -190,7 +190,7 @@ struct TicketSummary: Codable, Sendable, Identifiable, Hashable { | ||
| 190 | 190 | // MARK: - Ticket Detail |
| 191 | 191 | |
| 192 | 192 | /// Full ticket model for the detail view. |
| 193 | struct TicketDetail: Codable, Sendable { | |
| 193 | nonisolated struct TicketDetail: Codable, Sendable { | |
| 194 | 194 | let id: Int |
| 195 | 195 | let created: Date |
| 196 | 196 | let updated: Date |
| @@ -207,7 +207,7 @@ struct TicketDetail: Codable, Sendable { | ||
| 207 | 207 | // MARK: - Event |
| 208 | 208 | |
| 209 | 209 | /// A timeline event on a ticket. Each event contains one or more changes. |
| 210 | struct TicketEvent: Codable, Sendable, Identifiable { | |
| 210 | nonisolated struct TicketEvent: Codable, Sendable, Identifiable { | |
| 211 | 211 | let id: Int |
| 212 | 212 | let created: Date |
| 213 | 213 | var changes: [EventChange] |
| @@ -215,7 +215,7 @@ struct TicketEvent: Codable, Sendable, Identifiable { | ||
| 215 | 215 | |
| 216 | 216 | /// A single change within an event, decoded from the polymorphic EventDetail |
| 217 | 217 | /// interface using inline fragments. |
| 218 | struct EventChange: Codable, Sendable, Identifiable { | |
| 218 | nonisolated struct EventChange: Codable, Sendable, Identifiable { | |
| 219 | 219 | let id: UUID |
| 220 | 220 | |
| 221 | 221 | let eventType: String |
| @@ -272,12 +272,12 @@ struct EventChange: Codable, Sendable, Identifiable { | ||
| 272 | 272 | |
| 273 | 273 | /// Decoded from the `mentioned` field which may be a Ticket (with `id`) or |
| 274 | 274 | /// an Entity (with `canonicalName`) depending on the event type. |
| 275 | struct MentionTarget: Codable, Sendable { | |
| 275 | nonisolated struct MentionTarget: Codable, Sendable { | |
| 276 | 276 | let id: Int? |
| 277 | 277 | let canonicalName: String? |
| 278 | 278 | } |
| 279 | 279 | |
| 280 | 280 | /// Label info as returned within a LabelUpdate event change. |
| 281 | struct EventLabel: Codable, Sendable { | |
| 281 | nonisolated struct EventLabel: Codable, Sendable { | |
| 282 | 282 | let name: String |
| 283 | 283 | } |
Hutch/Models/User.swift +1 −1
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import Foundation |
| 2 | 2 | |
| 3 | 3 | /// A Sourcehut user from meta.sr.ht. |
| 4 | struct User: Decodable, Sendable, Hashable { | |
| 4 | nonisolated struct User: Decodable, Sendable, Hashable { | |
| 5 | 5 | let id: Int |
| 6 | 6 | let created: String? |
| 7 | 7 | let updated: String? |
Hutch/Views/Lists/MailingListListView.swift +1 −1
| @@ -100,7 +100,7 @@ final class MailingListListViewModel { | ||
| 100 | 100 | query: Self.createMailingListMutation, |
| 101 | 101 | variables: [ |
| 102 | 102 | "name": trimmedName, |
| 103 | "description": trimmedDescription.isEmpty ? nil as String? as Any : trimmedDescription, | |
| 103 | "description": trimmedDescription.isEmpty ? nil as String? as any Sendable : trimmedDescription, | |
| 104 | 104 | "visibility": visibility.rawValue |
| 105 | 105 | ], |
| 106 | 106 | responseType: Response.self |
Hutch/Views/More/TipStoreViewModel.swift +3 −1
| @@ -32,7 +32,9 @@ final class TipStoreViewModel { | ||
| 32 | 32 | var errorMessage: String? |
| 33 | 33 | var statusMessage: String? |
| 34 | 34 | |
| 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>? | |
| 36 | 38 | |
| 37 | 39 | init() { |
| 38 | 40 | transactionUpdatesTask = Task.detached(priority: .background) { |
Hutch/Views/Repositories/RepositoryDetailViewModel.swift +1 −1
| @@ -557,7 +557,7 @@ final class RepositoryDetailViewModel { | ||
| 557 | 557 | variables: [ |
| 558 | 558 | "repoId": repository.id, |
| 559 | 559 | "revspec": revspec, |
| 560 | "file": nil as String? as Any | |
| 560 | "file": nil as String? as any Sendable | |
| 561 | 561 | ], |
| 562 | 562 | file: MultipartUploadFile( |
| 563 | 563 | variablePath: "file", |
Hutch/Views/Repositories/SyntaxHighlighter.swift +1 −1
| @@ -208,7 +208,7 @@ nonisolated final class SyntaxHighlighter { | ||
| 208 | 208 | ] |
| 209 | 209 | } |
| 210 | 210 | |
| 211 | private extension UIColor { | |
| 211 | nonisolated private extension UIColor { | |
| 212 | 212 | /// `#rrggbb` for HTML inline styles, or `nil` if the color isn't RGB-convertible. |
| 213 | 213 | var hexRGBString: String? { |
| 214 | 214 | var red: CGFloat = 0 |
Hutch/Views/Settings/SettingsViewModel.swift +5 −5
| @@ -198,9 +198,9 @@ final class SettingsViewModel { | ||
| 198 | 198 | do { |
| 199 | 199 | let input: [String: any Sendable] = [ |
| 200 | 200 | "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 | |
| 204 | 204 | ] |
| 205 | 205 | let result = try await client.execute( |
| 206 | 206 | service: .meta, |
| @@ -243,7 +243,7 @@ final class SettingsViewModel { | ||
| 243 | 243 | do { |
| 244 | 244 | // The input variable has avatar set to null; the actual file |
| 245 | 245 | // 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] | |
| 247 | 247 | let result = try await client.executeMultipart( |
| 248 | 248 | service: .meta, |
| 249 | 249 | query: Self.updateUserMutation, |
| @@ -286,7 +286,7 @@ final class SettingsViewModel { | ||
| 286 | 286 | error = nil |
| 287 | 287 | |
| 288 | 288 | 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] | |
| 290 | 290 | let result = try await client.execute( |
| 291 | 291 | service: .meta, |
| 292 | 292 | query: Self.updateUserMutation, |
HutchTests/APICacheTests.swift +1
| @@ -3,6 +3,7 @@ import Testing | ||
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | 5 | @Suite(.serialized) |
| 6 | @MainActor | |
| 6 | 7 | struct APICacheTests { |
| 7 | 8 | private struct Payload: Codable, Sendable, Equatable { |
| 8 | 9 | let value: String |
HutchTests/AppStateTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct AppStateTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/BuildListViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct BuildListViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/BuildTaskLogSearchTests.swift +1
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import Testing |
| 2 | 2 | @testable import Hutch |
| 3 | 3 | |
| 4 | @MainActor | |
| 4 | 5 | struct BuildTaskLogSearchTests { |
| 5 | 6 | |
| 6 | 7 | @Test |
HutchTests/BuildTaskTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct BuildTaskTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/BundleUserAgentTests.swift +1
| @@ -40,6 +40,7 @@ private final class CapturingURLProtocol: URLProtocol, @unchecked Sendable { | ||
| 40 | 40 | |
| 41 | 41 | /// Tests are serialized because CapturingURLProtocol uses shared static state. |
| 42 | 42 | @Suite(.serialized) |
| 43 | @MainActor | |
| 43 | 44 | struct BundleUserAgentTests { |
| 44 | 45 | |
| 45 | 46 | // MARK: Bundle extension |
HutchTests/ContributionCalendarTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ContributionCalendarTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/DeepLinkTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct DeepLinkTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/GitObjectTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct GitObjectTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/HomeViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct HomeViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/HutchTests.swift +1
| @@ -7,6 +7,7 @@ | ||
| 7 | 7 | |
| 8 | 8 | import Testing |
| 9 | 9 | |
| 10 | @MainActor | |
| 10 | 11 | struct HutchTests { |
| 11 | 12 | |
| 12 | 13 | @Test func example() async throws { |
HutchTests/InboxViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct InboxViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/LookupHistoryStoreTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct LookupHistoryStoreTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/MailingListActivityTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct MailingListActivityTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/ManPageCatalogTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ManPageCatalogTests { |
| 6 | 7 | @Test |
| 7 | 8 | func loadsBundledCatalog() { |
HutchTests/PatchsetTests.swift +2
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct PatchsetStatusTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
| @@ -46,6 +47,7 @@ struct PatchsetStatusTests { | ||
| 46 | 47 | } |
| 47 | 48 | } |
| 48 | 49 | |
| 50 | @MainActor | |
| 49 | 51 | struct PatchsetSummaryTests { |
| 50 | 52 | |
| 51 | 53 | @Test |
HutchTests/ProjectFormTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ProjectFormTests { |
| 6 | 7 | @Test |
| 7 | 8 | func parsesTagsSplittingAndTrimming() { |
HutchTests/ProjectPinStoreTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ProjectPinStoreTests { |
| 6 | 7 | @Test |
| 7 | 8 | func storesPinnedProjectsPerUser() { |
HutchTests/ProjectTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ProjectTests { |
| 6 | 7 | private enum Fixture { |
| 7 | 8 | static let exampleWebsite = "https://example.com" |
HutchTests/ReadmeViewTests.swift +5
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ReadmeViewTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
| @@ -28,6 +29,7 @@ struct ReadmeViewTests { | ||
| 28 | 29 | } |
| 29 | 30 | } |
| 30 | 31 | |
| 32 | @MainActor | |
| 31 | 33 | struct MarkdownRenderingTests { |
| 32 | 34 | |
| 33 | 35 | @Test |
| @@ -219,6 +221,7 @@ struct MarkdownRenderingTests { | ||
| 219 | 221 | } |
| 220 | 222 | } |
| 221 | 223 | |
| 224 | @MainActor | |
| 222 | 225 | struct OrgRenderingTests { |
| 223 | 226 | |
| 224 | 227 | @Test |
| @@ -376,6 +379,7 @@ struct OrgRenderingTests { | ||
| 376 | 379 | } |
| 377 | 380 | } |
| 378 | 381 | |
| 382 | @MainActor | |
| 379 | 383 | struct RepositoryAssetURLTests { |
| 380 | 384 | |
| 381 | 385 | @Test |
| @@ -391,6 +395,7 @@ struct RepositoryAssetURLTests { | ||
| 391 | 395 | } |
| 392 | 396 | } |
| 393 | 397 | |
| 398 | @MainActor | |
| 394 | 399 | struct RepositoryLinkURLTests { |
| 395 | 400 | |
| 396 | 401 | @Test |
HutchTests/RecentActivityStoreTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct RecentActivityStoreTests { |
| 6 | 7 | @Test |
| 7 | 8 | func removeDropsMatchingEntryKeepingOthers() { |
HutchTests/RepositoryACLViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct RepositoryACLViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/RepositoryDeployKeyTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct RepositoryDeployKeyTests { |
| 6 | 7 | @Test |
| 7 | 8 | func decodesDeployKey() throws { |
HutchTests/RepositoryListViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct RepositoryListViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/RepositorySettingsViewModelTests.swift +1
| @@ -10,6 +10,7 @@ private struct UpdateRepositoryInfoPayload: Decodable { | ||
| 10 | 10 | let id: Int |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | @MainActor | |
| 13 | 14 | struct RepositorySettingsViewModelTests { |
| 14 | 15 | |
| 15 | 16 | @Test |
HutchTests/SRHTClientTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct SRHTClientTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/SRHTWebURLTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct SRHTWebURLTests { |
| 6 | 7 | private enum Expected { |
| 7 | 8 | static let chatOrigin = "https://chat.sr.ht" |
HutchTests/ScopedSearchHistoryStoreTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct ScopedSearchHistoryStoreTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/SettingsViewModelTests.swift +1
| @@ -64,6 +64,7 @@ private struct DeleteResultPayload: Decodable { | ||
| 64 | 64 | } |
| 65 | 65 | |
| 66 | 66 | @Suite(.serialized) |
| 67 | @MainActor | |
| 67 | 68 | struct SettingsViewModelTests { |
| 68 | 69 | |
| 69 | 70 | @Test |
HutchTests/SettingsViewTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct SettingsViewTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/SourceHutWebDeepLinkMapperTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct SourceHutWebDeepLinkMapperTests { |
| 6 | 7 | @Test |
| 7 | 8 | func mapsRepositoryURLs() { |
HutchTests/SyntaxHighlighterTests.swift +4 −5
| @@ -3,6 +3,7 @@ import Testing | ||
| 3 | 3 | import UIKit |
| 4 | 4 | @testable import Hutch |
| 5 | 5 | |
| 6 | @MainActor | |
| 6 | 7 | struct SyntaxHighlighterTests { |
| 7 | 8 | @Test |
| 8 | 9 | func mapsFileNamesToLanguages() { |
| @@ -24,9 +25,8 @@ struct SyntaxHighlighterTests { | ||
| 24 | 25 | let highlighter = SyntaxHighlighter(theme: .light) |
| 25 | 26 | let html = highlighter.highlightedHTML(for: "let answer = 42", language: "swift") |
| 26 | 27 | |
| 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) | |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | @Test |
| @@ -42,7 +42,6 @@ struct SyntaxHighlighterTests { | ||
| 42 | 42 | let font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) |
| 43 | 43 | let attributed = highlighter.attributedText(for: "print(\"hi\")", language: "python", font: font) |
| 44 | 44 | |
| 45 | let unwrapped = try? #require(attributed) | |
| 46 | #expect(unwrapped?.string.contains("print") == true) | |
| 45 | #expect(attributed?.string.contains("print") == true) | |
| 47 | 46 | } |
| 48 | 47 | } |
HutchTests/SystemStatusRepositoryTests.swift +5 −4
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct SystemStatusRepositoryTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
| @@ -111,21 +112,21 @@ private struct TestSystemStatusService: SystemStatusServing { | ||
| 111 | 112 | } |
| 112 | 113 | |
| 113 | 114 | private extension SystemStatusRepositoryTests { |
| 114 | static let cachedSnapshotHTML = #""" | |
| 115 | nonisolated static let cachedSnapshotHTML = #""" | |
| 115 | 116 | <div class="component" data-status="disrupted"> |
| 116 | 117 | <a href="/affected/git.sr.ht/">git.sr.ht</a> |
| 117 | 118 | <span class="component-status">Disrupted</span> |
| 118 | 119 | </div> |
| 119 | 120 | """# |
| 120 | 121 | |
| 121 | static let cachedOperationalHTML = #""" | |
| 122 | nonisolated static let cachedOperationalHTML = #""" | |
| 122 | 123 | <div class="component" data-status="ok"> |
| 123 | 124 | <a href="/affected/meta.sr.ht/">meta.sr.ht</a> |
| 124 | 125 | <span class="component-status">Operational</span> |
| 125 | 126 | </div> |
| 126 | 127 | """# |
| 127 | 128 | |
| 128 | static let cachedIncidentRSS = #""" | |
| 129 | nonisolated static let cachedIncidentRSS = #""" | |
| 129 | 130 | <rss version="2.0"> |
| 130 | 131 | <channel> |
| 131 | 132 | <item> |
| @@ -139,7 +140,7 @@ private extension SystemStatusRepositoryTests { | ||
| 139 | 140 | </rss> |
| 140 | 141 | """# |
| 141 | 142 | |
| 142 | static let emptyRSS = #""" | |
| 143 | nonisolated static let emptyRSS = #""" | |
| 143 | 144 | <rss version="2.0"> |
| 144 | 145 | <channel></channel> |
| 145 | 146 | </rss> |
HutchTests/SystemStatusServiceTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct SystemStatusServiceTests { |
| 6 | 7 | private enum Fixture { |
| 7 | 8 | static let ddosIssueURL = "https://status.sr.ht/issues/2026-04-06-ddos-attack/" |
HutchTests/TicketBulkActionTests.swift +1
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import Testing |
| 2 | 2 | @testable import Hutch |
| 3 | 3 | |
| 4 | @MainActor | |
| 4 | 5 | struct TicketBulkActionTests { |
| 5 | 6 | |
| 6 | 7 | @Test |
HutchTests/TicketDetailViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct TicketDetailViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/TicketSavedFilterStoreTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct TicketSavedFilterStoreTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/TipStoreViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct TipStoreViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/TrackerListViewModelTests.swift +1
| @@ -2,6 +2,7 @@ import Foundation | ||
| 2 | 2 | import Testing |
| 3 | 3 | @testable import Hutch |
| 4 | 4 | |
| 5 | @MainActor | |
| 5 | 6 | struct TrackerListViewModelTests { |
| 6 | 7 | |
| 7 | 8 | @Test |
HutchTests/TrackerManagementViewModelTests.swift +1
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import Testing |
| 2 | 2 | @testable import Hutch |
| 3 | 3 | |
| 4 | @MainActor | |
| 4 | 5 | struct TrackerManagementViewModelTests { |
| 5 | 6 | |
| 6 | 7 | @Test |
HutchWidgetExtension/ContributionGraphWidget.swift +4
| @@ -28,12 +28,16 @@ struct ContributionGraphTimelineProvider: TimelineProvider { | ||
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | 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 | |
| 31 | 34 | Task { |
| 32 | 35 | completion(await loadEntry()) |
| 33 | 36 | } |
| 34 | 37 | } |
| 35 | 38 | |
| 36 | 39 | func getTimeline(in _: Context, completion: @escaping (Timeline<ContributionGraphEntry>) -> Void) { |
| 40 | nonisolated(unsafe) let completion = completion | |
| 37 | 41 | Task { |
| 38 | 42 | let entry = await loadEntry() |
| 39 | 43 | 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*. | ||
| 158 | 158 | | v3.11.0 | ~~Mailing list subscribe/unsubscribe toggle~~ (shipped) | Ingest-surfaced; state via the `subscriptions` query (the `subscription` field is a trap) | |
| 159 | 159 | | v3.12.0 | Accessibility | Independent, device-verified | |
| 160 | 160 | | 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 | | |
| 162 | 162 | |
| 163 | 163 | 3.9.0 was cut this session, bundling the hub.sr.ht writes with the other |
| 164 | 164 | features listed. That reorders the original plan: the "What's cooking" ingest |
| @@ -384,21 +384,29 @@ rather than a bare `TODO`.) | ||
| 384 | 384 | saved-search store for an intent to open. Add the intent once global |
| 385 | 385 | saved-search persistence exists. |
| 386 | 386 | |
| 387 | ### Swift 6 language mode — no release of its own | |
| 388 | ||
| 389 | The project builds in Swift 5 language mode with | |
| 390 | `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`. Moving to Swift 6 is blocked on | |
| 391 | concurrency 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`). | |
| 390 | The 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 | ||
| 409 | Builds and the full suite are clean in Swift 6 mode with no behaviour change. | |
| 402 | 410 | |
| 403 | 411 | ### Cache reads that bypass the client — no release of its own |
| 404 | 412 | |