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