Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import Foundation
import HealthKit

public struct OCKHealthKitOutcome: Codable, Equatable, Identifiable, OCKAnyOutcome {
public struct OCKHealthKitOutcome: Codable, Hashable, Identifiable, OCKAnyOutcome {

/// The UUID of the task to which this outcome belongs.
public var taskUUID: UUID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import Foundation
/// A store that allows for reading outcomes.
public protocol OCKReadableOutcomeStore: OCKAnyReadOnlyOutcomeStore {

associatedtype Outcome: OCKAnyOutcome, Equatable, Identifiable
associatedtype Outcome: OCKAnyOutcome, Hashable, Identifiable

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing OCKReadableOutcomeStore.Outcome from Equatable to Hashable is a source-breaking change for external outcome store implementations that only provided Equatable outcomes. If avoiding breaking changes is important, consider leaving this protocol as-is and adding a new/refinement protocol that requires Hashable (or a where Outcome: Hashable constrained extension) for SwiftUI-facing APIs.

Suggested change
associatedtype Outcome: OCKAnyOutcome, Hashable, Identifiable
associatedtype Outcome: OCKAnyOutcome, Equatable, Identifiable

Copilot uses AI. Check for mistakes.

/// An asynchronous sequence that produces outcomes.
associatedtype Outcomes: AsyncSequence & Sendable where Outcomes.Element == [Outcome]
Expand Down
2 changes: 1 addition & 1 deletion CareKitStore/CareKitStore/Protocols/Tasks/OCKAnyTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import Foundation

/// Conforming a type to `OCKAnyTask` allows it to be queried and displayed by CareKit.
public protocol OCKAnyTask: Sendable {
public protocol OCKAnyTask: Identifiable, Sendable {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have long-standing problem ever since the Identifiable was introduced - our id property isn't a stable ID for different versions of the same task, but our uuid is. I think that means we can't conform OCKAnyTask to Identifiable directly, the best you can do right now is wrap OCKAnyTask in an Identifiable type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I’ll make an update soon.


/// A user-defined unique identifier, typically human readable.
var id: String { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import Foundation
/// A store that allows for reading tasks.
public protocol OCKReadableTaskStore: OCKAnyReadOnlyTaskStore {

associatedtype Task: OCKAnyTask, Equatable
associatedtype Task: OCKAnyTask, Hashable

Comment on lines 35 to 37

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing OCKReadableTaskStore.Task from Equatable to Hashable is a source-breaking change for any external store implementations that previously only made their task Equatable. If the intent is to add SwiftUI support without breaking existing conformers, consider keeping the base protocol requirement as Equatable and introducing a separate/refinement protocol (or conditional extensions) for Hashable-specific APIs.

Copilot uses AI. Check for mistakes.
/// An asynchronous sequence that produces tasks.
associatedtype Tasks: AsyncSequence & Sendable where Tasks.Element == [Task]
Expand Down
6 changes: 3 additions & 3 deletions CareKitStore/CareKitStore/Structs/OCKEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ import Foundation
/// The event contains a copy of the task itself, the
/// schedule event, and an outcome that's non-`nil` if progress is made on the task.
public struct OCKEvent<
Task: OCKAnyTask & Equatable,
Outcome: OCKAnyOutcome & Equatable
>: Identifiable, Comparable, Sendable {
Task: OCKAnyTask & Hashable,
Outcome: OCKAnyOutcome & Hashable
>: Identifiable, Hashable, Comparable, Sendable {
Comment on lines 37 to +40

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCKEvent now conforms to Hashable, but the synthesized Hashable/Equatable implementation will include outcome (a mutable stored property). That means mutating outcome changes the hash/equality, which can break Set/Dictionary invariants and can cause surprising behavior when OCKEvent is used as a SwiftUI navigation value. Consider implementing custom ==/hash(into:) based only on stable identity (e.g., task UUID + occurrence) or making outcome immutable.

Copilot uses AI. Check for mistakes.

Comment on lines 37 to 41

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCKEvent.id is documented as a “stable identifier”, but it’s currently derived from Hasher.finalize(). Swift’s Hasher is intentionally seeded per-process, so this value is not stable across app launches and can also theoretically collide. For a stable ID (and to better support SwiftUI use-cases), prefer a deterministic string derived from task.uuid and scheduleEvent.occurrence rather than Hasher output.

Copilot uses AI. Check for mistakes.
/// The stable identifier to use.
public let id: String
Expand Down