diff --git a/.github/actions/ci-guard/action.yml b/.github/actions/ci-guard/action.yml
new file mode 100644
index 00000000..70a6473e
--- /dev/null
+++ b/.github/actions/ci-guard/action.yml
@@ -0,0 +1,23 @@
+name: Guard
+description: >
+ Loads the current PR title (not the stale event payload) and cancels the workflow when it
+ contains skip-ci, case-insensitive. No-op when the event is not pull_request.
+inputs:
+ github_token:
+ description: Token with actions:write and pull-requests:read
+ required: true
+runs:
+ using: composite
+ steps:
+ - run: |
+ set -euo pipefail
+ if [ "${{ github.event_name }}" != "pull_request" ]; then
+ exit 0
+ fi
+ PR_TITLE=$(gh pr view ${{ github.event.pull_request.number }} --repo "${{ github.repository }}" --json title --jq .title)
+ if printf '%s' "$PR_TITLE" | grep -Fqi 'skip-ci'; then
+ gh run cancel "${{ github.run_id }}" --repo "${{ github.repository }}"
+ fi
+ shell: bash
+ env:
+ GH_TOKEN: ${{ inputs.github_token }}
diff --git a/.github/workflows/cron-checks.yml b/.github/workflows/cron-checks.yml
index eeaac6f6..d66bcb63 100644
--- a/.github/workflows/cron-checks.yml
+++ b/.github/workflows/cron-checks.yml
@@ -21,7 +21,7 @@ jobs:
strategy:
matrix:
include:
- - ios: "26.1"
+ - ios: "26.2"
device: "iPhone 17 Pro"
setup_runtime: false
- ios: "18.5"
@@ -33,13 +33,10 @@ jobs:
- ios: "16.4"
device: "iPhone 14 Pro"
setup_runtime: true
- - ios: "15.5"
- device: "iPhone 13 Pro"
- setup_runtime: true
fail-fast: false
runs-on: macos-15
env:
- XCODE_VERSION: "26.1.1"
+ XCODE_VERSION: "26.2"
IOS_SIMULATOR_DEVICE: "${{ matrix.device }} (${{ matrix.ios }})"
steps:
- uses: actions/checkout@v4.1.1
@@ -74,7 +71,7 @@ jobs:
strategy:
matrix:
include:
- - xcode: 26.1.1 # swift 6.2
+ - xcode: 26.2 # swift 6.2
os: macos-15
- xcode: 16.4 # swift 6.1
os: macos-15
diff --git a/.github/workflows/sdk-size-metrics.yml b/.github/workflows/sdk-size-metrics.yml
index e71245a9..ec7e2d3c 100644
--- a/.github/workflows/sdk-size-metrics.yml
+++ b/.github/workflows/sdk-size-metrics.yml
@@ -16,17 +16,22 @@ jobs:
sdk_size:
name: Metrics
runs-on: macos-15
+ if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_GITHUB_TOKEN }}'
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}
steps:
+ - uses: actions/checkout@v3.1.0
+
+ - uses: ./.github/actions/ci-guard
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
- name: Connect Bot
uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}
- - uses: actions/checkout@v3.1.0
-
- uses: ./.github/actions/bootstrap
- name: Run General SDK Size Metrics
diff --git a/.github/workflows/smoke-checks.yml b/.github/workflows/smoke-checks.yml
index a5de8059..9869b343 100644
--- a/.github/workflows/smoke-checks.yml
+++ b/.github/workflows/smoke-checks.yml
@@ -20,14 +20,24 @@ concurrency:
env:
HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI
- IOS_SIMULATOR_DEVICE: "iPhone 17 Pro (26.1)"
+ IOS_SIMULATOR_DEVICE: "iPhone 17 Pro (26.2)"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}
jobs:
+ guard:
+ name: Guard
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4.1.1
+ - uses: ./.github/actions/ci-guard
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
test-llc:
name: Test LLC
runs-on: macos-15
+ needs: guard
steps:
- uses: actions/checkout@v4.1.1
with:
@@ -74,6 +84,7 @@ jobs:
automated-code-review:
name: Automated Code Review
runs-on: macos-15
+ needs: guard
env:
XCODE_VERSION: "16.0"
steps:
@@ -89,6 +100,7 @@ jobs:
build-apps:
name: Build Demo App
runs-on: macos-15
+ needs: guard
steps:
- uses: actions/checkout@v4.1.1
- uses: ./.github/actions/ruby-cache
@@ -99,6 +111,7 @@ jobs:
build-spm-integration:
name: Build SPM Integration App
runs-on: macos-15
+ needs: guard
steps:
- uses: actions/checkout@v4.1.1
- uses: ./.github/actions/ruby-cache
diff --git a/AGENTS.md b/AGENTS.md
index 7ed5382c..59d108cc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -25,7 +25,10 @@ DemoApp/ # The demo app for testing
Use the closest folderโs conventions when editing. Query actual target/product names from Package.swift before building.
-Local setup (SPM)
+### New files & target membership
+ โข When creating new source or resource files, add them to the correct Xcode target(s). Update the project (e.g. project.pbxproj) so each new file is included in the appropriate target's "Compile Sources" (or "Copy Bundle Resources" for assets). Match the target(s) used by sibling files in the same directory (e.g. Sources/StreamFeeds/ โ StreamFeeds; Tests/StreamFeedsTests/ โ StreamFeedsTests). Omitting target membership will cause build failures or unused files.
+
+### Local setup (SPM)
1. Clone the repository and open it in Xcode (root contains Package.swift).
2. Resolve packages.
3. Choose an iOS Simulator (e.g., iPhone 15) and Build.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 282fbced..a506882c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### ๐ Changed
+# [0.5.3](https://github.com/GetStream/stream-feeds-swift/releases/tag/0.5.3)
+_June 11, 2026_
+
+### ๐ Changed
+- Update StreamCore to 0.7.0, which fixes `Sendable` errors when building with Xcode 27 [#68](https://github.com/GetStream/stream-feeds-swift/pull/68)
+
# [0.5.2](https://github.com/GetStream/stream-feeds-swift/releases/tag/0.5.2)
_February 09, 2026_
diff --git a/Package.swift b/Package.swift
index 77faff1c..8bfae80e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -14,7 +14,7 @@ let package = Package(
)
],
dependencies: [
- .package(url: "https://github.com/GetStream/stream-core-swift.git", exact: "0.6.2")
+ .package(url: "https://github.com/GetStream/stream-core-swift.git", from: "0.7.0")
],
targets: [
.target(
diff --git a/Sources/StreamFeeds/Info.plist b/Sources/StreamFeeds/Info.plist
index cb2edbea..80fb0b9d 100644
--- a/Sources/StreamFeeds/Info.plist
+++ b/Sources/StreamFeeds/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 0.5.2
+ 0.5.3
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift
index a2743d3e..2e505e55 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift
@@ -220,7 +220,7 @@ public struct ActivitiesSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift
index 36eb445a..28e24e50 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift
@@ -157,7 +157,7 @@ public struct ActivityReactionsSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift
index e1a01130..460c4f01 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift
@@ -161,7 +161,7 @@ public struct BookmarkFoldersSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift
index 319b51c2..a6e9d3f9 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift
@@ -166,7 +166,7 @@ public struct BookmarksSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift
index 6048c72e..8ab1128a 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift
@@ -164,7 +164,7 @@ public struct CommentReactionsSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift
index 572dfae0..8dcb5ed3 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift
@@ -256,7 +256,7 @@ public struct FeedsSortField: SortField {
/// The string value representing the field name in the API for remote sorting.
public let rawValue: String
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift
index 0be07bb6..96c2bc92 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift
@@ -156,7 +156,7 @@ public struct FollowsSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift
index e00cb101..e71e9558 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift
@@ -175,7 +175,7 @@ public struct MembersSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift
index 781b6876..bc7f184f 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift
@@ -177,7 +177,7 @@ public struct ModerationConfigsSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift
index cd571640..84ae3a76 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift
@@ -187,7 +187,7 @@ public struct PollVotesSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift
index 37b9ad22..ff43463d 100644
--- a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift
+++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift
@@ -197,7 +197,7 @@ public struct PollsSortField: SortField {
/// - Parameters:
/// - rawValue: The string value representing the field name in the API.
/// - localValue: A closure that extracts the comparable value from the model.
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift b/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift
index a70aa648..40b42092 100644
--- a/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift
+++ b/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift
@@ -6,5 +6,5 @@ import Foundation
extension SystemEnvironment {
/// A Stream Feeds version.
- public static let version: String = "0.5.2"
+ public static let version: String = "0.5.3"
}
diff --git a/StreamFeeds.xcodeproj/project.pbxproj b/StreamFeeds.xcodeproj/project.pbxproj
index bb3da354..b6f08a2e 100644
--- a/StreamFeeds.xcodeproj/project.pbxproj
+++ b/StreamFeeds.xcodeproj/project.pbxproj
@@ -863,8 +863,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/GetStream/stream-core-swift.git";
requirement = {
- kind = exactVersion;
- version = 0.6.2;
+ kind = upToNextMajorVersion;
+ minimumVersion = 0.7.0;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift b/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift
index ed7e5179..1aaf9eac 100644
--- a/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift
+++ b/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift
@@ -23,7 +23,7 @@ struct ArrayExtensions_Tests {
public let comparator: AnySortComparator
public let rawValue: String
- public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable {
+ public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable {
comparator = AnySortComparator(localValue: localValue)
self.rawValue = rawValue
}
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 2ce6f8ce..1c69e892 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -6,7 +6,7 @@ require 'json'
require 'net/http'
import 'Sonarfile'
-xcode_version = ENV['XCODE_VERSION'] || '26.1.1'
+xcode_version = ENV['XCODE_VERSION'] || '26.2'
xcode_project = 'StreamFeeds.xcodeproj'
sdk_names = ['StreamFeeds']
github_repo = ENV['GITHUB_REPOSITORY'] || 'GetStream/stream-feeds-swift'
@@ -247,12 +247,12 @@ end
lane :sources_matrix do
{
- llc: ['Sources/StreamFeeds', 'Tests/StreamFeedsTests', 'Tests/Shared', xcode_project],
- sample_apps: ['Sources', 'DemoApp', xcode_project],
- integration: ['Sources', 'Integration', xcode_project],
- ruby: ['fastlane', 'Gemfile', 'Gemfile.lock'],
- size: ['Sources', xcode_project],
- public_interface: ['Sources']
+ llc: ['Sources/StreamFeeds', 'Tests/StreamFeedsTests', 'Tests/Shared', '.github/workflows/smoke-checks.yml', xcode_project],
+ sample_apps: ['Sources', 'DemoApp', '.github/workflows/smoke-checks.yml', xcode_project],
+ integration: ['Sources', 'Integration', '.github/workflows/smoke-checks.yml', xcode_project],
+ ruby: ['fastlane', 'Gemfile', 'Gemfile.lock', '.github/workflows/smoke-checks.yml'],
+ size: ['Sources', '.github/workflows/smoke-checks.yml', xcode_project],
+ public_interface: ['Sources', '.github/workflows/smoke-checks.yml']
}
end