diff options
| author | Christian Cleberg <[email protected]> | 2026-08-07 17:33:46 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-08-07 17:33:46 -0500 |
| commit | 283fbf00fdffd64c924b6cb22c84e803b718f078 (patch) | |
| tree | f6603518ba62134faa699d864003b90464930ab9 /Hutch | |
| parent | b2a1265cadccc34cbdab71b9ed638b908cac9f62 (diff) | |
| download | hutch-283fbf00fdffd64c924b6cb22c84e803b718f078.tar.gz hutch-283fbf00fdffd64c924b6cb22c84e803b718f078.tar.bz2 hutch-283fbf00fdffd64c924b6cb22c84e803b718f078.zip | |
Adopt Swift 6 language mode
Flip SWIFT_VERSION to 6.0 (keeping SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor)
and resolve every resulting diagnostic. Mechanical, no behaviour change:
- Models: pure value types in Models/ and utility extensions (Date, DateFormatter,
SRHTWebURL) marked nonisolated so the nonisolated networking layer can use them.
- App Intents: stored 'static var' (title/description/openAppWhenRun/
typeDisplayRepresentation/defaultQuery/caseDisplayRepresentations) -> 'static let';
drop the invalid @retroactive on the in-module LookupType: AppEnum.
- Sendable dicts: 'nil as String? as Any' -> 'nil as String? as any Sendable'.
- UserDefaults: retroactive @unchecked Sendable (documented thread-safe).
- WidgetKit completion handlers rebound nonisolated(unsafe) to cross into Task {};
TipStoreViewModel task handle is @ObservationIgnored nonisolated(unsafe) for
its nonisolated deinit.
- Tests run on @MainActor; a few constant fixtures marked nonisolated for use in
@Sendable stub closures.
Builds and the full suite are clean in Swift 6 mode. Roadmap updated.
Diffstat (limited to 'Hutch')
28 files changed, 185 insertions, 176 deletions
diff --git a/Hutch/App/HutchIntents.swift b/Hutch/App/HutchIntents.swift index ff8fec8..3f4cd83 100644 --- a/Hutch/App/HutchIntents.swift +++ b/Hutch/App/HutchIntents.swift @@ -4,9 +4,9 @@ import Foundation // MARK: - Navigation Intents struct OpenWorkQueueIntent: AppIntent { - static var title: LocalizedStringResource = "Open Work Queue" - static var description = IntentDescription("Opens Hutch to your Work Queue.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open Work Queue" + static let description = IntentDescription("Opens Hutch to your Work Queue.") + static let openAppWhenRun = true var route: HutchRoute { .workQueue(scope: .all) } @@ -18,9 +18,9 @@ struct OpenWorkQueueIntent: AppIntent { } struct OpenRecentActivityIntent: AppIntent { - static var title: LocalizedStringResource = "Open Recent Activity" - static var description = IntentDescription("Opens Hutch to recent activity.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open Recent Activity" + static let description = IntentDescription("Opens Hutch to recent activity.") + static let openAppWhenRun = true var route: HutchRoute { .recentActivity } @@ -32,9 +32,9 @@ struct OpenRecentActivityIntent: AppIntent { } struct OpenSystemStatusIntent: AppIntent { - static var title: LocalizedStringResource = "Open System Status" - static var description = IntentDescription("Opens Hutch to SourceHut system status.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open System Status" + static let description = IntentDescription("Opens Hutch to SourceHut system status.") + static let openAppWhenRun = true var route: HutchRoute { .systemStatus } @@ -46,9 +46,9 @@ struct OpenSystemStatusIntent: AppIntent { } struct OpenPinnedResourceIntent: AppIntent { - static var title: LocalizedStringResource = "Open Pinned Resource" - static var description = IntentDescription("Opens a pinned Hutch resource.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open Pinned Resource" + static let description = IntentDescription("Opens a pinned Hutch resource.") + static let openAppWhenRun = true @Parameter(title: "Pinned Resource") var pinnedResource: PinnedResourceEntity @@ -63,9 +63,9 @@ struct OpenPinnedResourceIntent: AppIntent { } struct OpenProjectDashboardIntent: AppIntent { - static var title: LocalizedStringResource = "Open Project Dashboard" - static var description = IntentDescription("Opens a pinned project dashboard in Hutch.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open Project Dashboard" + static let description = IntentDescription("Opens a pinned project dashboard in Hutch.") + static let openAppWhenRun = true @Parameter(title: "Project") var project: ProjectEntity @@ -84,16 +84,16 @@ struct OpenProjectDashboardIntent: AppIntent { enum HutchShortcutScope: String, AppEnum { case all - static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Scope") - static var caseDisplayRepresentations: [HutchShortcutScope: DisplayRepresentation] = [ + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Scope") + static let caseDisplayRepresentations: [HutchShortcutScope: DisplayRepresentation] = [ .all: "All" ] } struct OpenFailedBuildsIntent: AppIntent { - static var title: LocalizedStringResource = "Open Failed Builds" - static var description = IntentDescription("Opens Hutch to failed builds.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open Failed Builds" + static let description = IntentDescription("Opens Hutch to failed builds.") + static let openAppWhenRun = true @Parameter(title: "Scope", default: .all) var scope: HutchShortcutScope @@ -108,9 +108,9 @@ struct OpenFailedBuildsIntent: AppIntent { } struct OpenAssignedTicketsIntent: AppIntent { - static var title: LocalizedStringResource = "Open Assigned Tickets" - static var description = IntentDescription("Opens Hutch to tickets assigned to you.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Open Assigned Tickets" + static let description = IntentDescription("Opens Hutch to tickets assigned to you.") + static let openAppWhenRun = true @Parameter(title: "Scope", default: .all) var scope: HutchShortcutScope @@ -125,9 +125,9 @@ struct OpenAssignedTicketsIntent: AppIntent { } struct SearchHutchIntent: AppIntent { - static var title: LocalizedStringResource = "Search Hutch" - static var description = IntentDescription("Opens Hutch lookup with a search query.") - static var openAppWhenRun = true + static let title: LocalizedStringResource = "Search Hutch" + static let description = IntentDescription("Opens Hutch lookup with a search query.") + static let openAppWhenRun = true @Parameter(title: "Query") var query: String @@ -155,8 +155,8 @@ struct SearchHutchIntent: AppIntent { // MARK: - App Entities struct PinnedResourceEntity: AppEntity, Identifiable { - static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Pinned Resource") - static var defaultQuery = PinnedResourceQuery() + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Pinned Resource") + static let defaultQuery = PinnedResourceQuery() let id: String let name: String @@ -181,8 +181,8 @@ struct PinnedResourceQuery: EntityQuery { } struct ProjectEntity: AppEntity, Identifiable { - static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Project") - static var defaultQuery = ProjectEntityQuery() + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Project") + static let defaultQuery = ProjectEntityQuery() let id: String let name: String @@ -278,8 +278,8 @@ enum HutchIntentEntityStore { // MARK: - Existing Read-Only Summary Intents struct CheckSystemStatusIntent: AppIntent { - static var title: LocalizedStringResource = "Check SourceHut Status" - static var description = IntentDescription("Returns the current SourceHut system status.") + static let title: LocalizedStringResource = "Check SourceHut Status" + static let description = IntentDescription("Returns the current SourceHut system status.") @MainActor func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog { @@ -302,8 +302,8 @@ struct CheckSystemStatusIntent: AppIntent { } struct CheckBuildsIntent: AppIntent { - static var title: LocalizedStringResource = "Check Hutch Builds" - static var description = IntentDescription("Returns a summary of your recent build status.") + static let title: LocalizedStringResource = "Check Hutch Builds" + static let description = IntentDescription("Returns a summary of your recent build status.") @MainActor func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog { @@ -357,8 +357,8 @@ extension LookupType: AppEnum { // MARK: - Mutating Intents struct ClearRecentActivityIntent: AppIntent { - static var title: LocalizedStringResource = "Clear Recent Activity" - static var description = IntentDescription("Clears the Recent list on the Hutch Home tab.") + static let title: LocalizedStringResource = "Clear Recent Activity" + static let description = IntentDescription("Clears the Recent list on the Hutch Home tab.") @MainActor func perform() async throws -> some IntentResult & ProvidesDialog { @@ -368,8 +368,8 @@ struct ClearRecentActivityIntent: AppIntent { } struct UnpinResourceIntent: AppIntent { - static var title: LocalizedStringResource = "Unpin Resource" - static var description = IntentDescription("Removes a pinned resource from the Hutch Home tab.") + static let title: LocalizedStringResource = "Unpin Resource" + static let description = IntentDescription("Removes a pinned resource from the Hutch Home tab.") @Parameter(title: "Pinned Resource") var pinnedResource: PinnedResourceEntity diff --git a/Hutch/Extensions/Date+Relative.swift b/Hutch/Extensions/Date+Relative.swift index f06ba59..b6c28ed 100644 --- a/Hutch/Extensions/Date+Relative.swift +++ b/Hutch/Extensions/Date+Relative.swift @@ -1,6 +1,6 @@ import Foundation -extension Date { +nonisolated extension Date { /// A short relative description like "2h ago", "3d ago", or "Jan 5, 2025". var relativeDescription: String { let formatter = RelativeDateTimeFormatter() diff --git a/Hutch/Extensions/DateFormatter+SRHT.swift b/Hutch/Extensions/DateFormatter+SRHT.swift index c017b11..a06b58a 100644 --- a/Hutch/Extensions/DateFormatter+SRHT.swift +++ b/Hutch/Extensions/DateFormatter+SRHT.swift @@ -1,6 +1,6 @@ import Foundation -extension DateFormatter { +nonisolated extension DateFormatter { /// Formatter for the sr.ht `Time` scalar: `%Y-%m-%dT%H:%M:%SZ` (UTC). static let srht: DateFormatter = { let formatter = DateFormatter() diff --git a/Hutch/Extensions/Foundation+Sendable.swift b/Hutch/Extensions/Foundation+Sendable.swift new file mode 100644 index 0000000..9a12aa6 --- /dev/null +++ b/Hutch/Extensions/Foundation+Sendable.swift @@ -0,0 +1,7 @@ +import Foundation + +// `UserDefaults` is documented as thread-safe but is not marked `Sendable` by +// Foundation. Hutch passes it into account sessions and the various per-account +// stores, all of which need to cross concurrency boundaries. The retroactive +// unchecked conformance reflects the documented thread-safety. +extension UserDefaults: @retroactive @unchecked Sendable {} diff --git a/Hutch/Extensions/SRHTWebURL.swift b/Hutch/Extensions/SRHTWebURL.swift index fc5a1d5..5715ce4 100644 --- a/Hutch/Extensions/SRHTWebURL.swift +++ b/Hutch/Extensions/SRHTWebURL.swift @@ -1,6 +1,6 @@ import Foundation -enum SRHTWebURL { +nonisolated enum SRHTWebURL { static let chat = URL(string: "https://chat.sr.ht")! static let status = URL(string: "https://status.sr.ht")! /// RSS/Atom feed for status incidents (HTML snapshot uses `status`). diff --git a/Hutch/Models/AccountEntry.swift b/Hutch/Models/AccountEntry.swift index 551cd19..cfc2f62 100644 --- a/Hutch/Models/AccountEntry.swift +++ b/Hutch/Models/AccountEntry.swift @@ -1,7 +1,7 @@ import Foundation /// A stored sr.ht account (token + resolved username). -struct AccountEntry: Codable, Identifiable, Equatable, Sendable { +nonisolated struct AccountEntry: Codable, Identifiable, Equatable, Sendable { let id: String var username: String var token: String diff --git a/Hutch/Models/ArtifactInfo.swift b/Hutch/Models/ArtifactInfo.swift index dbe9d96..6418ba8 100644 --- a/Hutch/Models/ArtifactInfo.swift +++ b/Hutch/Models/ArtifactInfo.swift @@ -1,7 +1,7 @@ import Foundation /// An artifact with its parent reference name, used in the artifacts tab. -struct ArtifactInfo: Codable, Sendable, Identifiable { +nonisolated struct ArtifactInfo: Codable, Sendable, Identifiable { let id: Int let filename: String let checksum: String @@ -9,13 +9,13 @@ struct ArtifactInfo: Codable, Sendable, Identifiable { let url: URL } -struct ArtifactPage: Codable, Sendable { +nonisolated struct ArtifactPage: Codable, Sendable { let results: [ArtifactInfo] let cursor: String? } /// A reference (tag) that has associated artifacts. -struct ReferenceWithArtifacts: Codable, Sendable, Identifiable { +nonisolated struct ReferenceWithArtifacts: Codable, Sendable, Identifiable { var id: String { name } let name: String let artifacts: [ArtifactInfo] diff --git a/Hutch/Models/Builds.swift b/Hutch/Models/Builds.swift index 3926888..48a9c91 100644 --- a/Hutch/Models/Builds.swift +++ b/Hutch/Models/Builds.swift @@ -3,7 +3,7 @@ import Foundation // MARK: - Enums /// Status of a build job. -enum JobStatus: String, Codable, Sendable { +nonisolated enum JobStatus: String, Codable, Sendable { case pending = "PENDING" case queued = "QUEUED" case running = "RUNNING" @@ -30,7 +30,7 @@ enum JobStatus: String, Codable, Sendable { } /// Status of a single build task within a job. -enum TaskStatus: String, Codable, Sendable { +nonisolated enum TaskStatus: String, Codable, Sendable { case pending = "PENDING" case running = "RUNNING" case success = "SUCCESS" @@ -41,7 +41,7 @@ enum TaskStatus: String, Codable, Sendable { // MARK: - Build Task /// A single task within a build job. -struct BuildTask: Codable, Sendable, Identifiable, Equatable { +nonisolated struct BuildTask: Codable, Sendable, Identifiable, Equatable { private(set) var ordinal: Int? let name: String let status: TaskStatus @@ -74,7 +74,7 @@ struct BuildTask: Codable, Sendable, Identifiable, Equatable { // MARK: - Job Summary (for list view) /// Lightweight job model matching the fields returned by the jobs list query. -struct JobSummary: Codable, Sendable, Identifiable, Hashable { +nonisolated struct JobSummary: Codable, Sendable, Identifiable, Hashable { let id: Int let created: Date let updated: Date @@ -103,7 +103,7 @@ struct JobSummary: Codable, Sendable, Identifiable, Hashable { } /// Minimal task info for the list query. -struct JobTaskSummary: Codable, Sendable, Hashable { +nonisolated struct JobTaskSummary: Codable, Sendable, Hashable { let name: String let status: TaskStatus } @@ -111,7 +111,7 @@ struct JobTaskSummary: Codable, Sendable, Hashable { // MARK: - Job Detail (for detail view) /// Full job model with all fields for the detail view. -struct JobDetail: Codable, Sendable, Equatable { +nonisolated struct JobDetail: Codable, Sendable, Equatable { let id: Int let created: Date let updated: Date @@ -128,7 +128,7 @@ struct JobDetail: Codable, Sendable, Equatable { } /// A downloadable artifact emitted by a build job. -struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { +nonisolated struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { let id: Int let created: Date let path: String @@ -145,14 +145,14 @@ struct BuildArtifact: Codable, Sendable, Identifiable, Equatable { } /// The log associated with a build job. -struct BuildLog: Codable, Sendable, Equatable { +nonisolated struct BuildLog: Codable, Sendable, Equatable { let fullURL: String } // MARK: - Job Group /// A group of related build jobs. -struct JobGroup: Codable, Sendable, Identifiable { +nonisolated struct JobGroup: Codable, Sendable, Identifiable { let id: Int let created: Date let note: String? diff --git a/Hutch/Models/CommitDetail.swift b/Hutch/Models/CommitDetail.swift index c95199d..962d2a4 100644 --- a/Hutch/Models/CommitDetail.swift +++ b/Hutch/Models/CommitDetail.swift @@ -1,7 +1,7 @@ import Foundation /// Full commit detail returned by the revparse_single query. -struct CommitDetail: Codable, Sendable, Identifiable { +nonisolated struct CommitDetail: Codable, Sendable, Identifiable { let id: String let shortId: String let author: CommitAuthor @@ -26,39 +26,39 @@ struct CommitDetail: Codable, Sendable, Identifiable { } } -struct CommitTrailer: Codable, Sendable, Identifiable { +nonisolated struct CommitTrailer: Codable, Sendable, Identifiable { var id: String { "\(name):\(value)" } let name: String let value: String } -struct ParentCommit: Codable, Sendable, Identifiable, Hashable { +nonisolated struct ParentCommit: Codable, Sendable, Identifiable, Hashable { let id: String let shortId: String let author: ParentAuthor } -struct ParentAuthor: Codable, Sendable, Hashable { +nonisolated struct ParentAuthor: Codable, Sendable, Hashable { let name: String } -struct CommitTree: Codable, Sendable { +nonisolated struct CommitTree: Codable, Sendable { let entries: CommitTreeEntries } -struct CommitTreeEntries: Codable, Sendable { +nonisolated struct CommitTreeEntries: Codable, Sendable { let results: [CommitTreeEntry] let cursor: String? } -struct CommitTreeEntry: Codable, Sendable, Identifiable { +nonisolated struct CommitTreeEntry: Codable, Sendable, Identifiable { let id: String let name: String let mode: Int let object: CommitTreeObject? } -struct CommitTreeObject: Codable, Sendable { +nonisolated struct CommitTreeObject: Codable, Sendable { let type: String? let id: String? let shortId: String? diff --git a/Hutch/Models/CommitSummary.swift b/Hutch/Models/CommitSummary.swift index 88c3e7d..99ed233 100644 --- a/Hutch/Models/CommitSummary.swift +++ b/Hutch/Models/CommitSummary.swift @@ -2,7 +2,7 @@ import Foundation /// Lightweight commit model for list views. Matches the subset of fields /// returned by the repository log query. -struct CommitSummary: Codable, Sendable, Identifiable, Hashable { +nonisolated struct CommitSummary: Codable, Sendable, Identifiable, Hashable { let id: String let shortId: String let author: CommitAuthor @@ -15,7 +15,7 @@ struct CommitSummary: Codable, Sendable, Identifiable, Hashable { } /// A compact author representation used in commit list responses. -struct CommitAuthor: Codable, Sendable, Hashable { +nonisolated struct CommitAuthor: Codable, Sendable, Hashable { let name: String let email: String? let time: Date diff --git a/Hutch/Models/ContributionCalendar.swift b/Hutch/Models/ContributionCalendar.swift index a429fa2..cf87ca7 100644 --- a/Hutch/Models/ContributionCalendar.swift +++ b/Hutch/Models/ContributionCalendar.swift @@ -1,6 +1,6 @@ import Foundation -struct ContributionCalendarResponse: Decodable, Sendable, Hashable { +nonisolated struct ContributionCalendarResponse: Decodable, Sendable, Hashable { let actor: String let from: Date let to: Date @@ -9,7 +9,7 @@ struct ContributionCalendarResponse: Decodable, Sendable, Hashable { let indexingState: ContributionIndexingState let days: [ContributionDay] - enum CodingKeys: String, CodingKey { + nonisolated enum CodingKeys: String, CodingKey { case actor case from case to @@ -59,14 +59,14 @@ struct ContributionCalendarResponse: Decodable, Sendable, Hashable { } } -struct ContributionDay: Decodable, Sendable, Hashable, Identifiable { +nonisolated struct ContributionDay: Decodable, Sendable, Hashable, Identifiable { var id: Date { date } let date: Date let count: Int let score: Double - enum CodingKeys: String, CodingKey { + nonisolated enum CodingKeys: String, CodingKey { case date case count case score @@ -90,7 +90,7 @@ struct ContributionDay: Decodable, Sendable, Hashable, Identifiable { } } -struct ContributionStatsResponse: Decodable, Sendable, Hashable { +nonisolated struct ContributionStatsResponse: Decodable, Sendable, Hashable { let actor: String let from: Date let to: Date @@ -103,7 +103,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { let longestStreak: Int let currentStreak: Int - enum CodingKeys: String, CodingKey { + nonisolated enum CodingKeys: String, CodingKey { case actor case from case to @@ -117,7 +117,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { case currentStreak = "current_streak" } - struct StatsWindow: Sendable, Hashable { + nonisolated struct StatsWindow: Sendable, Hashable { let actor: String let from: Date let to: Date @@ -126,7 +126,7 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { let indexingState: ContributionIndexingState } - struct StatsTotals: Sendable, Hashable { + nonisolated struct StatsTotals: Sendable, Hashable { let totalEvents: Int let totalScore: Double let activeDays: Int @@ -181,13 +181,13 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable { } } -enum ContributionIndexingState: String, Codable, Sendable, Hashable { +nonisolated enum ContributionIndexingState: String, Codable, Sendable, Hashable { case pending case indexed case error } -enum ContributionIntensity: Int, Sendable, CaseIterable { +nonisolated enum ContributionIntensity: Int, Sendable, CaseIterable { case empty = 0 case level1 = 1 case level2 = 2 @@ -210,12 +210,12 @@ enum ContributionIntensity: Int, Sendable, CaseIterable { } } -struct ContributionWeek: Sendable, Hashable { +nonisolated struct ContributionWeek: Sendable, Hashable { let startDate: Date let days: [ContributionDay] } -enum ContributionCalendarLayout { +nonisolated enum ContributionCalendarLayout { static func weekColumns( from days: [ContributionDay], calendar: Calendar = .contributionCalendar @@ -241,7 +241,7 @@ enum ContributionCalendarLayout { } } -enum ContributionDateParser { +nonisolated enum ContributionDateParser { static func parse(_ rawValue: String) -> Date? { let parts = rawValue.split(separator: "-", omittingEmptySubsequences: false) guard @@ -348,7 +348,7 @@ enum ContributionDateParser { } } -extension Calendar { +nonisolated extension Calendar { static var contributionCalendar: Calendar { var calendar = Calendar(identifier: .gregorian) calendar.firstWeekday = 1 diff --git a/Hutch/Models/Git.swift b/Hutch/Models/Git.swift index 26a5e2d..ae74f19 100644 --- a/Hutch/Models/Git.swift +++ b/Hutch/Models/Git.swift @@ -3,14 +3,14 @@ import Foundation // MARK: - Enums /// Repository visibility level. -enum Visibility: String, Codable, Sendable { +nonisolated enum Visibility: String, Codable, Sendable { case publicVisibility = "PUBLIC" case unlisted = "UNLISTED" case privateVisibility = "PRIVATE" } /// Repository access mode. -enum AccessMode: String, Codable, Sendable, CaseIterable { +nonisolated enum AccessMode: String, Codable, Sendable, CaseIterable { case ro = "RO" case rw = "RW" } @@ -19,14 +19,14 @@ enum AccessMode: String, Codable, Sendable, CaseIterable { /// The `Entity` GraphQL interface from git.sr.ht. Represents the owner of a /// resource (typically a user). -struct Entity: Codable, Sendable, Hashable { +nonisolated struct Entity: Codable, Sendable, Hashable { let canonicalName: String } // MARK: - Repository /// A git repository from git.sr.ht. -struct Repository: Codable, Sendable, Identifiable { +nonisolated struct Repository: Codable, Sendable, Identifiable { let id: Int let created: Date let updated: Date @@ -37,7 +37,7 @@ struct Repository: Codable, Sendable, Identifiable { let accessMode: AccessMode let owner: Entity - enum CodingKeys: String, CodingKey { + nonisolated enum CodingKeys: String, CodingKey { case id, created, updated, name, description, visibility, readme case accessMode = "access" case owner @@ -47,7 +47,7 @@ struct Repository: Codable, Sendable, Identifiable { // MARK: - Signature /// A Git commit/tag signature (author or committer). -struct Signature: Codable, Sendable { +nonisolated struct Signature: Codable, Sendable { let name: String let email: String let time: Date @@ -56,7 +56,7 @@ struct Signature: Codable, Sendable { // MARK: - Trailer /// A Git commit trailer (e.g. "Signed-off-by", "Co-authored-by"). -struct Trailer: Codable, Sendable { +nonisolated struct Trailer: Codable, Sendable { let name: String let value: String } @@ -64,7 +64,7 @@ struct Trailer: Codable, Sendable { // MARK: - Commit /// A Git commit from git.sr.ht. -struct Commit: Codable, Sendable, Identifiable { +nonisolated struct Commit: Codable, Sendable, Identifiable { let id: String let shortId: String let author: Signature @@ -77,13 +77,13 @@ struct Commit: Codable, Sendable, Identifiable { // MARK: - Reference /// A Git reference (branch or tag name). -struct Reference: Codable, Sendable, Hashable { +nonisolated struct Reference: Codable, Sendable, Hashable { let name: String let target: String? } /// A Git reference enriched with the date of its tip commit or tag, for display in the Refs tab. -struct ReferenceDetail: Sendable, Hashable { +nonisolated struct ReferenceDetail: Sendable, Hashable { let name: String let target: String? let date: Date? @@ -92,7 +92,7 @@ struct ReferenceDetail: Sendable, Hashable { // MARK: - TreeEntry /// An entry in a Git tree (file or directory). -struct TreeEntry: Codable, Sendable, Identifiable { +nonisolated struct TreeEntry: Codable, Sendable, Identifiable { let id: String let name: String let mode: Int? @@ -104,41 +104,41 @@ struct TreeEntry: Codable, Sendable, Identifiable { /// Both TextBlob and BinaryBlob have type == "BLOB"; they are /// distinguished by the presence of the "text" key (TextBlob) vs /// the "content" key (BinaryBlob). -enum GitObject: Sendable { +nonisolated enum GitObject: Sendable { case tree(GitTree) case textBlob(GitTextBlob) case binaryBlob(GitBinaryBlob) case unknown } -struct GitTree: Codable, Sendable { +nonisolated struct GitTree: Codable, Sendable { let id: String? let shortId: String? let entries: GitTreeEntryPage? } -struct GitTextBlob: Codable, Sendable { +nonisolated struct GitTextBlob: Codable, Sendable { let id: String? let shortId: String? let text: String? let size: Int? } -struct GitBinaryBlob: Codable, Sendable { +nonisolated struct GitBinaryBlob: Codable, Sendable { let id: String? let shortId: String? let size: Int? let content: String? } -struct GitTreeEntryPage: Codable, Sendable { +nonisolated struct GitTreeEntryPage: Codable, Sendable { let results: [TreeEntry] let cursor: String? } // MARK: - GitObject Codable -extension GitObject: Codable { +nonisolated extension GitObject: Codable { private enum CodingKeys: String, CodingKey { case type, id, shortId, entries, text, size, content case typename = "__typename" @@ -211,7 +211,7 @@ extension GitObject: Codable { } /// Convenience helpers for checking object type. -extension GitObject { +nonisolated extension GitObject { var isTree: Bool { if case .tree = self { return true } return false @@ -230,7 +230,7 @@ extension GitObject { // MARK: - Tag /// An annotated Git tag from git.sr.ht. -struct Tag: Codable, Sendable, Identifiable { +nonisolated struct Tag: Codable, Sendable, Identifiable { let id: String let shortId: String let name: String @@ -241,7 +241,7 @@ struct Tag: Codable, Sendable, Identifiable { // MARK: - Artifact /// A release artifact attached to a Git tag. -struct Artifact: Codable, Sendable, Identifiable { +nonisolated struct Artifact: Codable, Sendable, Identifiable { let id: Int let created: Date let filename: String diff --git a/Hutch/Models/Inbox.swift b/Hutch/Models/Inbox.swift index c8e41bb..dfed719 100644 --- a/Hutch/Models/Inbox.swift +++ b/Hutch/Models/Inbox.swift @@ -1,6 +1,6 @@ import Foundation -struct InboxThreadSummary: Identifiable, Hashable, Sendable { +nonisolated struct InboxThreadSummary: Identifiable, Hashable, Sendable { let rootEmailID: Int let rootMessageID: String let threadRootEmailIDs: [Int] @@ -76,7 +76,7 @@ struct InboxThreadSummary: Identifiable, Hashable, Sendable { } } -struct InboxMessage: Identifiable, Hashable, Sendable { +nonisolated struct InboxMessage: Identifiable, Hashable, Sendable { let id: Int let author: Entity let date: Date @@ -89,12 +89,12 @@ struct InboxMessage: Identifiable, Hashable, Sendable { let rawMessageURL: URL? } -enum InboxMessageContentBlock: Hashable, Sendable { +nonisolated enum InboxMessageContentBlock: Hashable, Sendable { case plainText(String) case diff(String) } -struct InboxThreadDetail: Sendable { +nonisolated struct InboxThreadDetail: Sendable { let id: String let rootEmailID: Int let rootMessageID: String @@ -114,7 +114,7 @@ struct InboxThreadDetail: Sendable { } } -extension InboxThreadDetail { +nonisolated extension InboxThreadDetail { var replyRecipient: String { "\(listOwner.canonicalName)/\(listName)@lists.sr.ht" } @@ -144,7 +144,7 @@ extension InboxThreadDetail { } } -struct MailComposeDraft: Sendable { +nonisolated struct MailComposeDraft: Sendable { let recipients: [String] let ccRecipients: [String] let subject: String @@ -155,20 +155,20 @@ struct MailComposeDraft: Sendable { } } -extension MailComposeDraft: Identifiable {} +nonisolated extension MailComposeDraft: Identifiable {} -struct InboxMailingListReference: Decodable, Sendable, Hashable, Identifiable { +nonisolated struct InboxMailingListReference: Decodable, Sendable, Hashable, Identifiable { let id: Int let rid: String let name: String let owner: Entity } -struct InboxPatchPreview: Decodable, Sendable, Hashable { +nonisolated struct InboxPatchPreview: Decodable, Sendable, Hashable { let subject: String? } -enum InboxReadStateStore { +nonisolated enum InboxReadStateStore { private static let key = "InboxThreadLastViewed" private static let baselineKey = "InboxUnreadBaseline" diff --git a/Hutch/Models/Meta.swift b/Hutch/Models/Meta.swift index f52fc76..e0a09de 100644 --- a/Hutch/Models/Meta.swift +++ b/Hutch/Models/Meta.swift @@ -3,7 +3,7 @@ import Foundation // MARK: - User Profile (full) /// Extended user profile from meta.sr.ht with all fields from the `me` query. -struct UserProfile: Codable, Sendable { +nonisolated struct UserProfile: Codable, Sendable { let username: String let canonicalName: String let email: String @@ -20,7 +20,7 @@ struct UserProfile: Codable, Sendable { // MARK: - SSH Key -struct SSHKey: Codable, Sendable, Identifiable { +nonisolated struct SSHKey: Codable, Sendable, Identifiable { let id: Int let comment: String? let created: Date @@ -34,27 +34,27 @@ struct SSHKey: Codable, Sendable, Identifiable { } } -struct SSHKeyPage: Codable, Sendable { +nonisolated struct SSHKeyPage: Codable, Sendable { let results: [SSHKey] let cursor: String? } // MARK: - PGP Key -struct PGPKey: Codable, Sendable, Identifiable { +nonisolated struct PGPKey: Codable, Sendable, Identifiable { let id: Int let fingerprint: String let created: Date } -struct PGPKeyPage: Codable, Sendable { +nonisolated struct PGPKeyPage: Codable, Sendable { let results: [PGPKey] let cursor: String? } // MARK: - Subscription -struct Subscription: Codable, Sendable { +nonisolated struct Subscription: Codable, Sendable { let status: String? let autorenew: Bool? let interval: String? @@ -62,7 +62,7 @@ struct Subscription: Codable, Sendable { // MARK: - Personal Access Token -struct PersonalAccessToken: Codable, Sendable, Identifiable { +nonisolated struct PersonalAccessToken: Codable, Sendable, Identifiable { let id: Int let issued: Date let expires: Date? @@ -72,7 +72,7 @@ struct PersonalAccessToken: Codable, Sendable, Identifiable { /// One entry in meta.sr.ht's audit log: a security-relevant action on the /// account, with the address it came from. -struct AuditLogEntry: Codable, Sendable, Identifiable { +nonisolated struct AuditLogEntry: Codable, Sendable, Identifiable { let id: Int let created: Date let ipAddress: String diff --git a/Hutch/Models/Paste.swift b/Hutch/Models/Paste.swift index b4c8f18..a8bf926 100644 --- a/Hutch/Models/Paste.swift +++ b/Hutch/Models/Paste.swift @@ -1,6 +1,6 @@ import Foundation -struct PasteFile: Codable, Sendable, Hashable, Identifiable { +nonisolated struct PasteFile: Codable, Sendable, Hashable, Identifiable { let filename: String? let hash: String let contents: URL? @@ -8,7 +8,7 @@ struct PasteFile: Codable, Sendable, Hashable, Identifiable { var id: String { hash } } -struct Paste: Codable, Sendable, Identifiable, Hashable { +nonisolated struct Paste: Codable, Sendable, Identifiable, Hashable { let id: String let created: Date let visibility: Visibility @@ -16,7 +16,7 @@ struct Paste: Codable, Sendable, Identifiable, Hashable { let user: Entity } -struct PasteUploadDraft: Identifiable, Equatable, Sendable { +nonisolated struct PasteUploadDraft: Identifiable, Equatable, Sendable { let id: UUID var filename: String var contents: String diff --git a/Hutch/Models/Patchset.swift b/Hutch/Models/Patchset.swift index 330bbbc..cc10b35 100644 --- a/Hutch/Models/Patchset.swift +++ b/Hutch/Models/Patchset.swift @@ -1,7 +1,7 @@ import Foundation /// Review state of a patchset on lists.sr.ht. -enum PatchsetStatus: String, Codable, Sendable, CaseIterable { +nonisolated enum PatchsetStatus: String, Codable, Sendable, CaseIterable { case unknown = "UNKNOWN" case proposed = "PROPOSED" case needsRevision = "NEEDS_REVISION" @@ -55,7 +55,7 @@ enum PatchsetStatus: String, Codable, Sendable, CaseIterable { /// A patchset as it appears in a mailing list listing, derived from the thread's /// root email rather than a dedicated patchsets query — `MailingList` exposes no /// such field. -struct PatchsetSummary: Identifiable, Hashable, Sendable { +nonisolated struct PatchsetSummary: Identifiable, Hashable, Sendable { let id: Int let subject: String let version: Int @@ -70,7 +70,7 @@ struct PatchsetSummary: Identifiable, Hashable, Sendable { } /// One email within a patchset: either the cover letter or a single patch. -struct PatchsetEmail: Identifiable, Hashable, Sendable { +nonisolated struct PatchsetEmail: Identifiable, Hashable, Sendable { let id: Int let subject: String let date: Date? @@ -88,13 +88,13 @@ struct PatchsetEmail: Identifiable, Hashable, Sendable { } /// A build or check reported against a patchset. -struct PatchsetToolResult: Identifiable, Hashable, Sendable { +nonisolated struct PatchsetToolResult: Identifiable, Hashable, Sendable { let id: Int let icon: PatchsetToolIcon let details: String } -enum PatchsetToolIcon: String, Codable, Sendable { +nonisolated enum PatchsetToolIcon: String, Codable, Sendable { case pending = "PENDING" case waiting = "WAITING" case success = "SUCCESS" @@ -112,7 +112,7 @@ enum PatchsetToolIcon: String, Codable, Sendable { } /// A patchset with its cover letter, patches, and review context. -struct PatchsetDetail: Sendable { +nonisolated struct PatchsetDetail: Sendable { let id: Int let created: Date let updated: Date diff --git a/Hutch/Models/Project.swift b/Hutch/Models/Project.swift index 8f5aa68..bd1a688 100644 --- a/Hutch/Models/Project.swift +++ b/Hutch/Models/Project.swift @@ -1,7 +1,7 @@ import Foundation -struct Project: Identifiable, Hashable, Sendable { - struct MailingList: Identifiable, Hashable, Sendable { +nonisolated struct Project: Identifiable, Hashable, Sendable { + nonisolated struct MailingList: Identifiable, Hashable, Sendable { let id: String let name: String let description: String? @@ -22,8 +22,8 @@ struct Project: Identifiable, Hashable, Sendable { } } - struct SourceRepo: Identifiable, Hashable, Sendable { - enum RepoType: String, Decodable, Sendable { + nonisolated struct SourceRepo: Identifiable, Hashable, Sendable { + nonisolated enum RepoType: String, Decodable, Sendable { case git = "GIT" case hg = "HG" @@ -47,7 +47,7 @@ struct Project: Identifiable, Hashable, Sendable { } } - struct Tracker: Identifiable, Hashable, Sendable { + nonisolated struct Tracker: Identifiable, Hashable, Sendable { let id: String let name: String let description: String? @@ -72,7 +72,7 @@ struct Project: Identifiable, Hashable, Sendable { let isFullyLoaded: Bool /// Identity and display fields for a project. - struct Metadata: Sendable, Hashable { + nonisolated struct Metadata: Sendable, Hashable { let id: String let name: String let description: String? @@ -82,7 +82,7 @@ struct Project: Identifiable, Hashable, Sendable { let updated: Date } - struct Resources: Sendable, Hashable { + nonisolated struct Resources: Sendable, Hashable { let mailingLists: [MailingList] let sources: [SourceRepo] let trackers: [Tracker] @@ -171,7 +171,7 @@ struct Project: Identifiable, Hashable, Sendable { } } -extension Project.MailingList { +nonisolated extension Project.MailingList { var displayName: String { Project.normalizedText(name) ?? "Untitled Mailing List" } @@ -185,7 +185,7 @@ extension Project.MailingList { } } -extension Project.SourceRepo { +nonisolated extension Project.SourceRepo { var displayName: String { Project.normalizedText(name) ?? "Untitled Repository" } @@ -203,7 +203,7 @@ extension Project.SourceRepo { } } -extension Project.Tracker { +nonisolated extension Project.Tracker { var displayName: String { Project.normalizedText(name) ?? "Untitled Tracker" } @@ -221,13 +221,13 @@ extension Project.Tracker { } } -extension String { +nonisolated extension String { var srhtUsername: String { hasPrefix("~") ? String(dropFirst()) : self } } -extension Visibility { +nonisolated extension Visibility { var displayName: String { switch self { case .publicVisibility: diff --git a/Hutch/Models/RepositoryACL.swift b/Hutch/Models/RepositoryACL.swift index e5f5eb1..e5d93f6 100644 --- a/Hutch/Models/RepositoryACL.swift +++ b/Hutch/Models/RepositoryACL.swift @@ -1,12 +1,12 @@ import Foundation -struct RepositoryACLEntry: Codable, Sendable, Identifiable, Hashable { +nonisolated struct RepositoryACLEntry: Codable, Sendable, Identifiable, Hashable { let id: Int let mode: AccessMode let entity: Entity } -extension AccessMode { +nonisolated extension AccessMode { var shortLabel: String { rawValue } var displayName: String { diff --git a/Hutch/Models/RepositorySummary.swift b/Hutch/Models/RepositorySummary.swift index b41589b..40ec7a4 100644 --- a/Hutch/Models/RepositorySummary.swift +++ b/Hutch/Models/RepositorySummary.swift @@ -3,7 +3,7 @@ import Foundation /// Lightweight repository model matching the fields returned by the /// repositories list query. Avoids optionalizing all fields on the full /// `Repository` model. -struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { +nonisolated struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { let id: Int /// GraphQL resource identifier used by `repository(rid:)` queries. let rid: String @@ -15,13 +15,13 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { let owner: Entity let head: Reference? - enum CodingKeys: String, CodingKey { + nonisolated enum CodingKeys: String, CodingKey { case id, rid, service, name, description, visibility, updated, owner case head = "HEAD" } /// Grouped initializer fields (single parameter keeps APIs explicit without exceeding parameter-count limits). - struct Fields: Sendable, Hashable { + nonisolated struct Fields: Sendable, Hashable { let id: Int let rid: String let service: SRHTService @@ -59,7 +59,7 @@ struct RepositorySummary: Codable, Sendable, Identifiable, Hashable { } } -extension RepositorySummary { +nonisolated extension RepositorySummary { var defaultBranchName: String? { head?.name.replacingOccurrences(of: "refs/heads/", with: "") } diff --git a/Hutch/Models/SystemStatusModels.swift b/Hutch/Models/SystemStatusModels.swift index 9a664b0..ddd659c 100644 --- a/Hutch/Models/SystemStatusModels.swift +++ b/Hutch/Models/SystemStatusModels.swift @@ -1,6 +1,6 @@ import Foundation -enum StatusLevel: String, Codable, Sendable { +nonisolated enum StatusLevel: String, Codable, Sendable { case operational case degraded case majorOutage @@ -32,7 +32,7 @@ enum StatusLevel: String, Codable, Sendable { } } -struct StatusServiceState: Identifiable, Hashable, Codable, Sendable { +nonisolated struct StatusServiceState: Identifiable, Hashable, Codable, Sendable { let id: String let name: String let slug: String? @@ -40,7 +40,7 @@ struct StatusServiceState: Identifiable, Hashable, Codable, Sendable { let description: String? } -struct StatusIncident: Identifiable, Hashable, Codable, Sendable { +nonisolated struct StatusIncident: Identifiable, Hashable, Codable, Sendable { let id: String let title: String let summary: String? @@ -50,7 +50,7 @@ struct StatusIncident: Identifiable, Hashable, Codable, Sendable { let isActive: Bool? } -struct SystemStatusSnapshot: Hashable, Codable, Sendable { +nonisolated struct SystemStatusSnapshot: Hashable, Codable, Sendable { let services: [StatusServiceState] let activeIncidents: [StatusIncident] let lastUpdated: Date @@ -78,7 +78,7 @@ struct SystemStatusSnapshot: Hashable, Codable, Sendable { } } -struct SystemStatusPageData: Sendable { +nonisolated struct SystemStatusPageData: Sendable { let snapshot: SystemStatusSnapshot let recentIncidents: [StatusIncident] } diff --git a/Hutch/Models/TicketBulkAction.swift b/Hutch/Models/TicketBulkAction.swift index dcf7676..ea9cc01 100644 --- a/Hutch/Models/TicketBulkAction.swift +++ b/Hutch/Models/TicketBulkAction.swift @@ -1,6 +1,6 @@ import Foundation -enum TicketBulkActionKind: String, Sendable { +nonisolated enum TicketBulkActionKind: String, Sendable { case close case assign @@ -23,7 +23,7 @@ enum TicketBulkActionKind: String, Sendable { } } -struct TicketBulkActionResult: Identifiable, Sendable { +nonisolated struct TicketBulkActionResult: Identifiable, Sendable { let id = UUID() let action: TicketBulkActionKind let totalCount: Int @@ -82,7 +82,7 @@ struct TicketBulkActionResult: Identifiable, Sendable { } } -struct TicketBulkActionFailure: Sendable { +nonisolated struct TicketBulkActionFailure: Sendable { let ticketID: Int let message: String } diff --git a/Hutch/Models/Todo.swift b/Hutch/Models/Todo.swift index 129d4bf..d2ae889 100644 --- a/Hutch/Models/Todo.swift +++ b/Hutch/Models/Todo.swift @@ -3,7 +3,7 @@ import Foundation // MARK: - Enums /// Status of a ticket. -enum TicketStatus: String, Codable, Sendable, CaseIterable { +nonisolated enum TicketStatus: String, Codable, Sendable, CaseIterable { case reported = "REPORTED" case confirmed = "CONFIRMED" case inProgress = "IN_PROGRESS" @@ -30,7 +30,7 @@ enum TicketStatus: String, Codable, Sendable, CaseIterable { } /// Resolution of a ticket. -enum TicketResolution: String, Codable, Sendable { +nonisolated enum TicketResolution: String, Codable, Sendable { case unresolved = "UNRESOLVED" case fixed = "FIXED" case implemented = "IMPLEMENTED" @@ -64,7 +64,7 @@ enum TicketResolution: String, Codable, Sendable { } /// Authenticity of a ticket or comment. -enum Authenticity: String, Codable, Sendable { +nonisolated enum Authenticity: String, Codable, Sendable { case authentic = "AUTHENTIC" case tampered = "TAMPERED" case unauthenticated = "UNAUTHENTICATED" @@ -74,7 +74,7 @@ enum Authenticity: String, Codable, Sendable { /// A label that can be applied to tickets. /// Named `TicketLabel` to avoid collision with `SwiftUI.Label`. -struct TicketLabel: Codable, Sendable, Identifiable, Hashable { +nonisolated struct TicketLabel: Codable, Sendable, Identifiable, Hashable { let id: Int let name: String let backgroundColor: String @@ -83,7 +83,7 @@ struct TicketLabel: Codable, Sendable, Identifiable, Hashable { // MARK: - Tracker ACL -struct TrackerACLPermissions: Codable, Sendable, Hashable { +nonisolated struct TrackerACLPermissions: Codable, Sendable, Hashable { let browse: Bool let submit: Bool let comment: Bool @@ -91,7 +91,7 @@ struct TrackerACLPermissions: Codable, Sendable, Hashable { let triage: Bool } -struct TrackerACL: Codable, Sendable, Identifiable, Hashable { +nonisolated struct TrackerACL: Codable, Sendable, Identifiable, Hashable { let id: Int let created: Date let entity: Entity @@ -112,7 +112,7 @@ struct TrackerACL: Codable, Sendable, Identifiable, Hashable { } } -struct DefaultTrackerACL: Codable, Sendable, Hashable { +nonisolated struct DefaultTrackerACL: Codable, Sendable, Hashable { let browse: Bool let submit: Bool let comment: Bool @@ -133,7 +133,7 @@ struct DefaultTrackerACL: Codable, Sendable, Hashable { // MARK: - Tracker /// A bug tracker from todo.sr.ht. -struct Tracker: Codable, Sendable, Identifiable, Hashable { +nonisolated struct Tracker: Codable, Sendable, Identifiable, Hashable { let id: Int let created: Date let updated: Date @@ -154,7 +154,7 @@ struct Tracker: Codable, Sendable, Identifiable, Hashable { // MARK: - Tracker Summary (for list view) /// Lightweight tracker model matching the fields returned by the trackers list query. -struct TrackerSummary: Codable, Sendable, Identifiable, Hashable { +nonisolated struct TrackerSummary: Codable, Sendable, Identifiable, Hashable { let id: Int /// GraphQL resource identifier used by `tracker(id:)` queries. let rid: String @@ -168,7 +168,7 @@ struct TrackerSummary: Codable, Sendable, Identifiable, Hashable { // MARK: - Ticket Summary (for list view) /// Lightweight ticket model for the list query. -struct TicketSummary: Codable, Sendable, Identifiable, Hashable { +nonisolated struct TicketSummary: Codable, Sendable, Identifiable, Hashable { let id: Int let title: String let status: TicketStatus @@ -190,7 +190,7 @@ struct TicketSummary: Codable, Sendable, Identifiable, Hashable { // MARK: - Ticket Detail /// Full ticket model for the detail view. -struct TicketDetail: Codable, Sendable { +nonisolated struct TicketDetail: Codable, Sendable { let id: Int let created: Date let updated: Date @@ -207,7 +207,7 @@ struct TicketDetail: Codable, Sendable { // MARK: - Event /// A timeline event on a ticket. Each event contains one or more changes. -struct TicketEvent: Codable, Sendable, Identifiable { +nonisolated struct TicketEvent: Codable, Sendable, Identifiable { let id: Int let created: Date var changes: [EventChange] @@ -215,7 +215,7 @@ struct TicketEvent: Codable, Sendable, Identifiable { /// A single change within an event, decoded from the polymorphic EventDetail /// interface using inline fragments. -struct EventChange: Codable, Sendable, Identifiable { +nonisolated struct EventChange: Codable, Sendable, Identifiable { let id: UUID let eventType: String @@ -272,12 +272,12 @@ struct EventChange: Codable, Sendable, Identifiable { /// Decoded from the `mentioned` field which may be a Ticket (with `id`) or /// an Entity (with `canonicalName`) depending on the event type. -struct MentionTarget: Codable, Sendable { +nonisolated struct MentionTarget: Codable, Sendable { let id: Int? let canonicalName: String? } /// Label info as returned within a LabelUpdate event change. -struct EventLabel: Codable, Sendable { +nonisolated struct EventLabel: Codable, Sendable { let name: String } diff --git a/Hutch/Models/User.swift b/Hutch/Models/User.swift index 4ab7315..10576ad 100644 --- a/Hutch/Models/User.swift +++ b/Hutch/Models/User.swift @@ -1,7 +1,7 @@ import Foundation /// A Sourcehut user from meta.sr.ht. -struct User: Decodable, Sendable, Hashable { +nonisolated struct User: Decodable, Sendable, Hashable { let id: Int let created: String? let updated: String? diff --git a/Hutch/Views/Lists/MailingListListView.swift b/Hutch/Views/Lists/MailingListListView.swift index 1b159bf..b6ed2ff 100644 --- a/Hutch/Views/Lists/MailingListListView.swift +++ b/Hutch/Views/Lists/MailingListListView.swift @@ -100,7 +100,7 @@ final class MailingListListViewModel { query: Self.createMailingListMutation, variables: [ "name": trimmedName, - "description": trimmedDescription.isEmpty ? nil as String? as Any : trimmedDescription, + "description": trimmedDescription.isEmpty ? nil as String? as any Sendable : trimmedDescription, "visibility": visibility.rawValue ], responseType: Response.self diff --git a/Hutch/Views/More/TipStoreViewModel.swift b/Hutch/Views/More/TipStoreViewModel.swift index bc909c1..043b218 100644 --- a/Hutch/Views/More/TipStoreViewModel.swift +++ b/Hutch/Views/More/TipStoreViewModel.swift @@ -32,7 +32,9 @@ final class TipStoreViewModel { var errorMessage: String? var statusMessage: String? - private var transactionUpdatesTask: Task<Void, Never>? + // Internal task handle, not observable state; assigned only on the main + // actor and read once from the nonisolated deinit. + @ObservationIgnored nonisolated(unsafe) private var transactionUpdatesTask: Task<Void, Never>? init() { transactionUpdatesTask = Task.detached(priority: .background) { diff --git a/Hutch/Views/Repositories/RepositoryDetailViewModel.swift b/Hutch/Views/Repositories/RepositoryDetailViewModel.swift index 82e1592..62a8b12 100644 --- a/Hutch/Views/Repositories/RepositoryDetailViewModel.swift +++ b/Hutch/Views/Repositories/RepositoryDetailViewModel.swift @@ -557,7 +557,7 @@ final class RepositoryDetailViewModel { variables: [ "repoId": repository.id, "revspec": revspec, - "file": nil as String? as Any + "file": nil as String? as any Sendable ], file: MultipartUploadFile( variablePath: "file", diff --git a/Hutch/Views/Repositories/SyntaxHighlighter.swift b/Hutch/Views/Repositories/SyntaxHighlighter.swift index f8b2cbb..dfab443 100644 --- a/Hutch/Views/Repositories/SyntaxHighlighter.swift +++ b/Hutch/Views/Repositories/SyntaxHighlighter.swift @@ -208,7 +208,7 @@ nonisolated final class SyntaxHighlighter { ] } -private extension UIColor { +nonisolated private extension UIColor { /// `#rrggbb` for HTML inline styles, or `nil` if the color isn't RGB-convertible. var hexRGBString: String? { var red: CGFloat = 0 diff --git a/Hutch/Views/Settings/SettingsViewModel.swift b/Hutch/Views/Settings/SettingsViewModel.swift index 8536e60..2d57ba5 100644 --- a/Hutch/Views/Settings/SettingsViewModel.swift +++ b/Hutch/Views/Settings/SettingsViewModel.swift @@ -198,9 +198,9 @@ final class SettingsViewModel { do { let input: [String: any Sendable] = [ "email": email, - "url": url.isEmpty ? nil as String? as Any : url, - "location": location.isEmpty ? nil as String? as Any : location, - "bio": bio.isEmpty ? nil as String? as Any : bio + "url": url.isEmpty ? nil as String? as any Sendable : url, + "location": location.isEmpty ? nil as String? as any Sendable : location, + "bio": bio.isEmpty ? nil as String? as any Sendable : bio ] let result = try await client.execute( service: .meta, @@ -243,7 +243,7 @@ final class SettingsViewModel { do { // The input variable has avatar set to null; the actual file // is sent as a separate multipart part per graphql-multipart-request-spec. - let input: [String: any Sendable] = ["avatar": nil as String? as Any] + let input: [String: any Sendable] = ["avatar": nil as String? as any Sendable] let result = try await client.executeMultipart( service: .meta, query: Self.updateUserMutation, @@ -286,7 +286,7 @@ final class SettingsViewModel { error = nil do { - let input: [String: any Sendable] = ["avatar": nil as String? as Any] + let input: [String: any Sendable] = ["avatar": nil as String? as any Sendable] let result = try await client.execute( service: .meta, query: Self.updateUserMutation, |
