Skip to content

Conversation

@nuno-vieira
Copy link
Member

@nuno-vieira nuno-vieira commented Oct 16, 2025

🔗 Issue Links

https://linear.app/stream/issue/IOS-1152/message-delivery-status

🎯 Goal

Add information to the message whenever it has been delivered to the recipient.

📝 Summary

New Public API:

  • Adds ChatRemoteNotificationHandler.markMessageAsDelivered(deliveries:)
  • Adds ChatChannel.reads(message:) and ChatChannel.deliveredReads(message:)
  • Adds ChatChannelRead.lastDeliveredAt
  • Adds ChatChannelRead.lastDeliveredMessageId

New in UI SDK:

  • Adds support for double grey checkmark when message is delivered but not read

Demo App:

  • Updates the Demo App to show off the delivery feature:
    • Adds gray double checkmark to the message view when message is delivered but not read
    • Adds gray double checkmark to the channel item view when preview message is delivered but not read
    • Adds "Show messages reads info" action to the message to show all delivered and reads info

TODO:

  • Backend config flag to feature flag the feature
  • Update the NotificationService to not use the Tracker internally since it is useless
  • User Privacy Settings update

🛠 Implementation

This PR introduces a message delivery tracking system that automatically marks messages as delivered when they are received through WebSocket events, channel list synchronisation, or push notifications. The system includes throttling to prevent server spam and ensures efficient delivery status updates.

The message delivery tracking consists of four components that work together:

  1. ChannelDeliveryTracker (New) - Core component that manages pending deliveries and throttling.
  2. ChannelDeliveredMiddleware (New) - WebSocket event middleware that uses the ChannelDeliveryTracker to respond to the following events: message.new, message.delivered, message.read
  3. ChannelListController - Marks channels as delivered without throttling when fetching channels.
  4. ChatRemoteNotificationHandler - Handles delivery marking for push notifications, which internally uses the ChannelDeliveryTracker to make sure requests are throttled as well.
  5. MessageDeliveryCriteriaValidator: Responsible for validating if a message can be marked as delivered.

Tip

Debouncer vs Throttling vs Throttling-Trailing
If you are confused about these, here is a visual demo:
https://claude.ai/public/artifacts/8e0e8f6a-674e-4154-9b97-85a99f4956d0

ChannelDeliveryTracker

The ChannelDeliveryTracker is the central component that manages pending channel deliveries and implements throttling to prevent server spam.

Key Features:

  • Tracks channels that need delivery marking with their latest message IDs ([ChannelId: MessageId])
  • Uses a throttler to limit delivery requests to at most one per second. It uses broadcastLatest: true to make sure the last call is always executed.
  • Whenever a delivery call is executed, it only removes the channels from the pending delivery dictionary that still match the same message ID, so that new message IDs from the same channel are not discarded.
  • Thread Safety: All operations are performed on a concurrent queue with barrier flags

Rules to mark a message as delivered:

  • Must be from another user
  • Message hasn't been marked as delivered yet (createdAt > lastDeliveryAt)
  • Message hasn't been marked as read yet (createdAt > lastReadAt)

ChannelDeliveredMiddleware

The ChannelDeliveredMiddleware processes WebSocket events and automatically triggers delivery marking for new messages.

Event Handling:

  • MessageNewEvent: Adds channel to pending deliveries and executes the request if not throttled.
  • NotificationMarkReadEvent: Cancels pending deliveries for the given channel.
  • MessageDeliveredEvent: Updates local channel read data with delivery data. Useful to update the users who received the message.

ChannelListController

The ChannelListController automatically marks channels as delivered during synchronisation operations.

Integration Points:

  • Initial Sync: When synchronize() is called, channels are marked as delivered instantly after a successful fetch.
  • Load Next Channels: When loadNextChannels() is called, new channels are marked as delivered.

Note: Only the channels that respect the rules mentioned above are marked as delivered.

ChatRemoteNotificationHandler

The ChatRemoteNotificationHandler exposes an easy way to mark messages as delivered when they arrive via push notifications. It internally checks if the message was already delivered to avoid redundant API calls. If the App is active, once the push notification is handled, the message will already be marked as delivered.

Customers just need to call the function ChatRemoteNotificationHandler.markMessageAsDelivered() and everything will be handled automatically for them.

Note

The ChatRemoteNotificationHandler does not use the ChannelDeliveryTracker internally since the NotificationService is spawned for every push, which means we can't have a throttling mechanism here since everything is created from scratch.

Full flow with edge cases

1. New message from Channel A is added to pending delivery
2. Request is fired instantly since no throttling is happening
3. New message from Channel B is added before 1s (Request not fired)
4. New message from Channel C is added before 1s (Request not fired)
6. After 1s, Request from 4. is executed with Channel B & Channel C
7. While it is being executed, a new message from the same channel B is added
8. Request from 5. finishes and removes Channel C from pending delivery, not Channel B, since it changed the message meanwhile
9. After 1s again, the request from 6. is executed with Channel B, since it was not cleared by 7.

🎨 Showcase

demo.mov

🧪 Manual Testing Notes

Message is marked delivered when the user receives a new message:

  • Open Channel List with User A
  • Send a message with User B
  • User B should see a double grey checkmark on the new message
  • Long-tapping the new message should show the delivered user
  • Going to the Channel List with User B should show a double grey checkmark

Message is marked delivered when the user receives a push notification:

  • Close the app with User A
  • Send a message with User B
  • User A should receive a push notification
  • User B should see a double grey checkmark on the new message
  • Long-tapping the new message should show the delivered user
  • Going to the Channel List with User B should show a double grey checkmark

Message is marked delivered when the user loads the channel list:

  • Turn off push notifications with User A
  • Close the app with User A
  • Send a message with User B
  • User B should NOT see a double grey checkmark on the new message
  • Open the Channel List with User A
  • User B should see a double grey checkmark on the new message
  • Going to the Channel List with User B should show a double grey checkmark

Message is NOT marked delivered when the user is: (Follow mark read rules)

  • The channel has delivery feature disabled.
  • The channel is muted or hidden
  • The current user has delivery receipts disabled (Demo App User Profile View)
  • The message is not from a shadow-banned or muted user
  • The message is a thread reply
  • The message is from the current user

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

Summary by CodeRabbit

  • New Features

    • Message delivery tracking per channel and a delivered state.
    • Delivery receipts privacy setting to enable/disable delivery notifications.
    • "Show Message Reads" UI: per-user Delivered/Read view for messages.
    • Option to opt channels into delivery event tracking.
  • Improvements

    • UI shows a double grey checkmark for delivered messages.
    • Message status now distinguishes sent, delivered, and read.
  • Chores

    • Demo app settings and notification flow updated to surface delivery behavior.

@nuno-vieira nuno-vieira requested a review from a team as a code owner October 16, 2025 14:45
@nuno-vieira nuno-vieira added the ✅ Feature An issue or PR related to a feature label Oct 16, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 16, 2025

Walkthrough

Implements delivery receipts: new delivery models, event type/DTO, middleware and throttled tracker, API endpoint/payloads, CurrentUserUpdater wiring, database/CoreData fields, channel/controller/model/UI changes, demo/push integrations, and extensive tests and mocks.

Changes

Cohort / File(s) Summary
Core delivery types & validator
Sources/StreamChat/Models/MessageDelivery/DeliveredMessageInfo.swift, Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
New MessageDeliveryInfo model, MessageDeliveryCriteriaValidating protocol and MessageDeliveryCriteriaValidator implementation.
Delivery tracker
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift, TestTools/.../ChannelDeliveryTracker_Mock.swift
New ChannelDeliveryTracker that batches/throttles per-channel delivery submissions; test mock added.
Event model & middleware
Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift, .../EventType.swift, .../EventPayload.swift, .../EventMiddlewares/ChannelDeliveredMiddleware.swift
Adds message.delivered event, MessageDeliveredEvent/DTO, extends EventPayload with delivery fields, and new ChannelDeliveredMiddleware handling delivery events.
API endpoints & payloads
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift, .../EndpointPath.swift, .../EndpointPath+OfflineRequest.swift, .../Payloads/ChannelDeliveredPayload.swift
New Endpoint path channels/delivered, builder markChannelsDelivered(payload:), offline-queue eligibility update, and payload structs DeliveredMessagePayload/ChannelDeliveredRequestPayload.
CurrentUserUpdater & ChatClient wiring
Sources/StreamChat/Workers/CurrentUserUpdater.swift, Sources/StreamChat/ChatClient+Environment.swift, Sources/StreamChat/ChatClientFactory.swift, Sources/StreamChat/ChatClient.swift
New markMessagesAsDelivered on CurrentUserUpdater; environment builders for currentUserUpdater and channelDeliveryTracker; ChatClient wiring and middleware injection.
Channel/controller integration
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
Adds deliveryCriteriaValidator/currentUserUpdater wiring and calls to markChannelsAsDeliveredIfNeeded after channel updates.
Database & DTOs / CoreData
Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift, .../ChannelConfigDTO.swift, .../CurrentUserDTO.swift, StreamChatModel.xcdatamodeld/.../contents
New fields: lastDeliveredAt, lastDeliveredMessageId, deliveryEventsEnabled, isDeliveryReceiptsEnabled; mapping/persistence updates.
Models & payload mapping
Sources/StreamChat/Models/Channel.swift, Sources/StreamChat/Models/ChannelRead.swift, Sources/StreamChat/Models/ChatMessage.swift, Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift, Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift
New APIs: ChatChannel.reads(for:), ChatChannel.deliveredReads(for:); ChatChannelRead gains delivery fields; ChatMessage.deliveryStatus(for:) and new .delivered status; payload mapping updated.
WebSocket client & connect payload
Sources/StreamChat/WebSocketClient/WebSocketConnectPayload.swift, Sources/StreamChat/WebSocketClient/Events/*
WebSocket connect payload includes deliveryReceipts privacy; event DTO/class conversions updated to support delivered events and fields.
UI & Demo app
DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift, DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift, DemoApp/Screens/AppConfigViewController/*, DemoApp/Screens/UserProfile/UserProfileViewController.swift, Sources/StreamChatUI/...
New DemoMessageReadsInfoView; show message reads action; demo flags (isMessageDeliveredInfoEnabled) and user deliveryReceiptsEnabled toggle; UI mapping updated to surface delivered state.
Push notifications
DemoAppPush/NotificationService.swift, Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift
Push handling calls markMessageAsDelivered where applicable; ChatRemoteNotificationHandler gains currentUserUpdater and deliveryCriteriaValidator and exposes markMessageAsDelivered.
Tests, fixtures & mocks
Tests/StreamChatTests/**, TestTools/StreamChatTestTools/Mocks/**, TestTools/.../Fixtures/JSONs/Events/Message/MessageDelivered.json
New and updated tests for endpoints, payloads, tracker, middleware, validator, models, UI snapshots; mocks for ChannelDeliveryTracker, CurrentUserUpdater, MessageDeliveryCriteriaValidator; JSON fixture added.
Project & changelog
StreamChat.xcodeproj/project.pbxproj, CHANGELOG.md, TestMockServer/ChannelConfig.swift
Project file additions for new sources/tests, changelog updates, mock server delivery flag support.

Sequence Diagram(s)

sequenceDiagram
    participant C as ChannelListController
    participant V as MessageDeliveryCriteriaValidator
    participant T as ChannelDeliveryTracker
    participant U as CurrentUserUpdater
    participant API as APIClient

    C->>C: channels updated / loaded
    C->>V: canMarkMessageAsDelivered(message, currentUser, channel)?
    alt eligible
        C->>T: submitForDelivery(channelId, messageId)
    end

    Note right of T: Throttler batches (e.g., 1s) and aggregates per-channel latest messageId
    T->>U: markMessagesAsDelivered([MessageDeliveryInfo])
    U->>API: POST /channels/delivered (ChannelDeliveredRequestPayload)
    API-->>U: EmptyResponse / error
    U-->>T: completion
Loading
sequenceDiagram
    participant UI as UI layer
    participant M as ChatMessage
    participant CH as ChatChannel
    participant DB as Database

    UI->>M: deliveryStatus(for: CH)
    M->>CH: reads(for: message)
    alt has read (lastReadAt >= message.createdAt)
        CH-->>M: .read
    else has delivered (lastDeliveredAt >= message.createdAt)
        CH-->>M: .delivered
    else
        CH-->>M: .sent
    end

    Note over DB: incoming message.delivered event -> ChannelDeliveredMiddleware updates ChannelRead DTO and may cancel pending submissions in tracker
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • ChannelDeliveryTracker.swift — concurrency, throttling, correctness of batching and barrier writes.
  • ChannelDeliveredMiddleware.swift — accurate event-to-DB mapping, cancellation semantics, and session usage.
  • MessageDeliveryCriteriaValidator.swift — correctness of eligibility conditions (threads, muted users, timestamps).
  • ChatMessage.deliveryStatus(for:) and UI mappings — precedence (read > delivered), gating by channel config, and deprecation of old API.
  • Core Data model/DTO changes — migration risks and mapping consistency.
  • Tests/mocks — ensure new builders and mocks are correctly wired in test environments.

Possibly related PRs

Suggested labels

🌐 SDK: StreamChat (LLC), 🎨 SDK: StreamChatUI

Suggested reviewers

  • martinmitrevski
  • laevandus

Poem

🐰 I hopped through code with twitching nose,

Batching receipts where the delivery flows.
Double checks glint, a tiny UI cheer,
Throttled and tested — receipts appear! ✨

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add support for message delivered info' clearly and concisely summarizes the main change—implementing delivery status tracking for messages with delivered receipts.
Linked Issues check ✅ Passed All coding requirements from IOS-1152 are met: public APIs (markMessageAsDelivered, reads, deliveredReads, delivery metadata), automatic marking via WebSocket/notifications, throttled deduplication, and thread-safe concurrent updates.
Out of Scope Changes check ✅ Passed All changes directly support delivery receipts: new delivery-tracking infrastructure, database schema extensions, WebSocket events, API endpoints, UI components, and privacy settings are all in scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add/message-delivered-status

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f2fff9d and bf3e71a.

📒 Files selected for processing (3)
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift (1 hunks)
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift (2 hunks)
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Respect .swiftlint.yml rules; do not suppress SwiftLint rules broadly—scope and justify any exceptions
Adhere to the project’s zero warnings policy—fix new warnings and avoid introducing any

Files:

  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
🧠 Learnings (5)
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Prefer using provided test fakes/mocks in tests when possible

Applied to files:

  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatTests/**/*.swift : Ensure tests cover core models and API surface of StreamChat

Applied to files:

  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatUITests/**/*.swift : Ensure tests cover view controllers and UI behaviors of StreamChatUI

Applied to files:

  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Add or extend tests in the matching module’s Tests folder

Applied to files:

  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Sources/{StreamChat,StreamChatUI}/**/*.swift : When altering public API, update inline documentation comments in source

Applied to files:

  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
🧬 Code graph analysis (1)
StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift (1)
TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift (1)
  • setEnvironmentVariables (46-50)
🪛 Gitleaks (8.28.0)
StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift

[high] 11-11: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (4)
StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift (1)

11-11: LGTM: Test API key configuration.

The hardcoded API key is appropriate for test infrastructure. The Gitleaks warning on this line is a false positive—these are test/demo API keys intended to be version-controlled.

StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift (1)

11-11: LGTM: Migration to string-based API key.

The change correctly migrates from the boolean flag to the new string-based switchApiKey property. The Gitleaks warning is a false positive—this is a test API key for performance benchmarking.

StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift (2)

19-19: LGTM: More flexible API key configuration.

Changing switchApiKey from a boolean flag to an optional string allows tests to specify any backend API key, improving test infrastructure flexibility.


64-66: LGTM: Correct conditional logic.

The updated logic properly checks for a non-nil switchApiKey and passes its value directly to the .customApiKey environment variable.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (15)
DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (1)

12-14: LGTM!

Consolidating AppConfig.shared.demoAppConfig access into a computed property is a good refactor. This reduces duplication and improves testability.

DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift (3)

119-126: Differentiate delivered and read icons.

Both .delivered and .read cases return the same "checkmark" icon, making it difficult for users to distinguish between delivered and read status at a glance.

Apply this diff to use distinct icons:

 var icon: String {
     switch self {
     case .delivered:
-        return "checkmark"
+        return "checkmark.circle"
     case .read:
-        return "checkmark"
+        return "checkmark.circle.fill"
     }
 }

146-153: Improve fallback text for missing user names.

Line 149 falls back to user.id when user.name is nil. Displaying technical user IDs as avatar initials degrades the user experience.

Apply this diff to use a more user-friendly fallback:

 Circle()
     .fill(Color.gray.opacity(0.3))
     .overlay(
-        Text(user.name?.prefix(1).uppercased() ?? user.id)
+        Text(user.name?.prefix(1).uppercased() ?? "?")
             .font(.headline)
             .foregroundColor(.primary)
     )

95-105: Consider consolidating helper methods.

getDeliveredTimestamp and getReadTimestamp share nearly identical logic, differing only in the keypath accessed (.lastDeliveredAt vs .lastReadAt).

You could refactor using a generic helper:

private func getTimestamp(for user: ChatUser, keyPath: KeyPath<ChatChannelRead, Date?>) -> Date? {
    channel.reads
        .first { $0.user.id == user.id }
        .flatMap { $0[keyPath: keyPath] }
}

private func getDeliveredTimestamp(for user: ChatUser) -> Date? {
    getTimestamp(for: user, keyPath: \.lastDeliveredAt)
}

private func getReadTimestamp(for user: ChatUser) -> Date? {
    getTimestamp(for: user, keyPath: \.lastReadAt)
}

However, given the simplicity of the existing methods, this refactor is optional.

Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)

283-294: Consider adding empty array check and async variant.

While the implementation is correct, consider these optional improvements:

  1. Empty array optimization: Add an early return if messages.isEmpty to avoid unnecessary API calls:

    guard !messages.isEmpty else {
        completion?(nil)
        return
    }
  2. Async/await variant: For consistency with other methods in this class (see lines 298-376), consider adding an async version in the extension below.

Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1)

579-600: New public API looks correct; consider no-op on empty input

Validation and delegation are consistent with existing patterns. Optionally, early-return on empty messages to avoid a needless request.

 func markMessagesAsDelivered(
     _ messages: [DeliveredMessageInfo],
     completion: ((Error?) -> Void)? = nil
 ) {
+    guard !messages.isEmpty else {
+        completion?(nil)
+        return
+    }
     guard client.currentUserId != nil else {
         completion?(ClientError.CurrentUserDoesNotExist())
         return
     }
Sources/StreamChat/ChatClientFactory.swift (1)

116-119: Remove or use the unused currentUserId parameter; fix call site closure

currentUserId isn’t used here, and the call site passes { nil }. Either:

  • Remove the parameter from the factory and update call sites, or
  • Use the provided closure where needed.

Recommend removing to keep the API clean.

-    func makeEventNotificationCenter(
-        databaseContainer: DatabaseContainer,
-        currentUserId: @escaping () -> UserId?,
-        currentUserUpdater: CurrentUserUpdater,
-        channelDeliveryTracker: ChannelDeliveryTracker
-    ) -> EventNotificationCenter {
+    func makeEventNotificationCenter(
+        databaseContainer: DatabaseContainer,
+        currentUserUpdater: CurrentUserUpdater,
+        channelDeliveryTracker: ChannelDeliveryTracker
+    ) -> EventNotificationCenter {

And adjust the call site (see ChatClient.swift suggestion).

Sources/StreamChat/ChatClient.swift (1)

169-175: Pass an actual currentUserId provider instead of { nil }

If the factory continues to accept currentUserId, pass a real provider to prevent future regressions.

-        let eventNotificationCenter = factory.makeEventNotificationCenter(
-            databaseContainer: databaseContainer,
-            currentUserId: {
-                nil
-            },
-            currentUserUpdater: currentUserUpdater,
-            channelDeliveryTracker: channelDeliveryTracker
-        )
+        let eventNotificationCenter = factory.makeEventNotificationCenter(
+            databaseContainer: databaseContainer,
+            currentUserUpdater: currentUserUpdater,
+            channelDeliveryTracker: channelDeliveryTracker
+        )

If you keep the parameter, use:

-            currentUserId: {
-                nil
-            },
+            currentUserId: { [weak self] in self?.currentUserId },

Follow up with the matching factory signature change (see ChatClientFactory.swift). [Based on learnings]

Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift (1)

991-1048: Good coverage for delivered-messages flow

API call shape and completion propagation are well tested. Consider renaming the MARK from “Mark Channels Delivered” to “Mark Messages Delivered” for consistency.

Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swift (3)

21-33: Handle NotificationMessageNew and Mark-All-Read events

To cover push-style events and global read, extend the switch:

  • Submit deliveries on NotificationMessageNewEventDTO for messages not from current user.
  • Optionally clear pending on NotificationMarkAllReadEventDTO.

Apply this diff to add NotificationMessageNew handling:

 case let messageNewEvent as MessageNewEventDTO:
   if session.currentUser?.user.id == messageNewEvent.message.user.id {
     break
   }
   handleMessageNewEvent(messageNewEvent)
+case let notificationMessageNewEvent as NotificationMessageNewEventDTO:
+  if session.currentUser?.user.id != notificationMessageNewEvent.message.user.id {
+      deliveryTracker.submitForDelivery(
+          channelId: notificationMessageNewEvent.cid,
+          messageId: notificationMessageNewEvent.message.id
+      )
+  }
 case let notificationMarkReadEvent as NotificationMarkReadEventDTO:
   handleNotificationMarkReadEvent(notificationMarkReadEvent)
+// Optional:
+// case _ as NotificationMarkAllReadEventDTO:
+//   deliveryTracker.cancelAll() // if you add this to the tracker

Please confirm whether a push-notification path already calls ChannelDeliveryTracker; if yes, duplicating here may be redundant.


39-41: Skip ephemeral/system messages (optional)

Avoid submitting delivery for non-regular message types (ephemeral/system/error).

Example:

guard event.message.type == .regular else { return }
deliveryTracker.submitForDelivery(channelId: event.cid, messageId: event.message.id)

55-67: Make deliveredAt monotonic

Out-of-order events can regress deliveredAt. Update only if the incoming timestamp is newer or equal.

-// Update the delivered message information
-channelRead.lastDeliveredAt = event.lastDeliveredAt.bridgeDate
-channelRead.lastDeliveredMessageId = event.lastDeliveredMessageId
+// Update delivered info only if newer
+let newDeliveredAt = event.lastDeliveredAt.bridgeDate
+if let current = channelRead.lastDeliveredAt {
+    if let newDeliveredAt, newDeliveredAt >= current {
+        channelRead.lastDeliveredAt = newDeliveredAt
+        channelRead.lastDeliveredMessageId = event.lastDeliveredMessageId
+    }
+} else {
+    channelRead.lastDeliveredAt = newDeliveredAt
+    channelRead.lastDeliveredMessageId = event.lastDeliveredMessageId
+}
Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift (2)

114-119: Assert deliveredAt equality, not just non-nil

Strengthen the check to prove correct value persisted.

-let channelRead = channelDTO.reads.first { $0.user.id == userId }
-XCTAssertNotNil(channelRead)
-XCTAssertEqual(channelRead?.lastDeliveredMessageId, messageId)
-XCTAssertNotNil(channelRead?.lastDeliveredAt)
+let channelRead = channelDTO.reads.first { $0.user.id == userId }
+XCTAssertNotNil(channelRead)
+XCTAssertEqual(channelRead?.lastDeliveredMessageId, messageId)
+XCTAssertEqual(channelRead?.lastDeliveredAt, deliveredAt)

As per coding guidelines


36-52: Add coverage for NotificationMessageNewEventDTO

Ensure the middleware submits for delivery on notificationMessageNew as well (if implemented).

+func test_handleNotificationMessageNewEvent_callsSubmitForDelivery() throws {
+    // GIVEN
+    let channelId = ChannelId.unique
+    let messageId = MessageId.unique
+    let event = try createNotificationMessageNewEvent(channelId: channelId, messageId: messageId)
+
+    // WHEN
+    _ = middleware.handle(event: event, session: database.viewContext)
+
+    // THEN
+    XCTAssertEqual(deliveryTracker.submitForDelivery_callCount, 1)
+    XCTAssertEqual(deliveryTracker.submitForDelivery_channelId, channelId)
+    XCTAssertEqual(deliveryTracker.submitForDelivery_messageId, messageId)
+}
+
+private func createNotificationMessageNewEvent(
+    channelId: ChannelId,
+    messageId: MessageId
+) throws -> NotificationMessageNewEventDTO {
+    let user = UserPayload.dummy(userId: .unique)
+    let message = MessagePayload.dummy(messageId: messageId, authorUserId: user.id)
+    let channel = ChannelDetailPayload.dummy(cid: channelId)
+    let payload = EventPayload(
+        eventType: .notificationMessageNew,
+        cid: channelId,
+        user: user,
+        channel: channel,
+        message: message,
+        createdAt: message.createdAt
+    )
+    return try NotificationMessageNewEventDTO(from: payload)
+}

As per coding guidelines

Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (1)

72-103: Ensure throttler triggers trailing executions (pending items remain delivered)

If Throttler.execute is leading-edge only, entries added during an in-flight request may remain pending without a subsequent execute. Ensure trailing-edge behavior or re-invoke markMessagesAsDelivered in the success path when pendingDeliveredChannels is not empty.

Option: in the success block, after clearing, check if pendingDeliveredChannels is non-empty and call markMessagesAsDelivered().

Please verify Throttler’s semantics (leading vs trailing). If leading-only, this change is necessary to avoid stuck pending deliveries.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c0bde8a and 4e19cdf.

📒 Files selected for processing (53)
  • DemoApp/Screens/AppConfigViewController/AppConfigViewController.swift (5 hunks)
  • DemoApp/StreamChat/Components/DeliveredMessages/DemoChatMessageDeliveryStatusView.swift (1 hunks)
  • DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift (1 hunks)
  • DemoApp/StreamChat/Components/DemoChatChannelListItemView.swift (1 hunks)
  • DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (5 hunks)
  • DemoApp/StreamChat/StreamChatWrapper+DemoApp.swift (1 hunks)
  • DemoAppPush/NotificationService.swift (4 hunks)
  • Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift (2 hunks)
  • Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift (2 hunks)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swift (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift (2 hunks)
  • Sources/StreamChat/ChatClient+Environment.swift (1 hunks)
  • Sources/StreamChat/ChatClient.swift (3 hunks)
  • Sources/StreamChat/ChatClientFactory.swift (2 hunks)
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (4 hunks)
  • Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1 hunks)
  • Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift (3 hunks)
  • Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents (2 hunks)
  • Sources/StreamChat/Models/Channel.swift (1 hunks)
  • Sources/StreamChat/Models/ChannelRead.swift (1 hunks)
  • Sources/StreamChat/Models/DeliveredMessageInfo.swift (1 hunks)
  • Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift (1 hunks)
  • Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (1 hunks)
  • Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swift (1 hunks)
  • Sources/StreamChat/WebSocketClient/Events/EventPayload.swift (5 hunks)
  • Sources/StreamChat/WebSocketClient/Events/EventType.swift (2 hunks)
  • Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift (1 hunks)
  • Sources/StreamChat/Workers/CurrentUserUpdater.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (34 hunks)
  • TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Message/MessageDelivered.json (1 hunks)
  • TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift (1 hunks)
  • TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift (1 hunks)
  • TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (3 hunks)
  • TestTools/StreamChatTestTools/TestData/DummyData/ChannelPayload.swift (1 hunks)
  • TestTools/StreamChatTestTools/TestData/DummyData/ChatChannel.swift (1 hunks)
  • TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift (2 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swift (1 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift (2 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swift (1 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift (2 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift (2 hunks)
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (8 hunks)
  • Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift (9 hunks)
  • Tests/StreamChatTests/Models/ChatChannel_Tests.swift (1 hunks)
  • Tests/StreamChatTests/StateLayer/Chat_Tests.swift (3 hunks)
  • Tests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swift (1 hunks)
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift (1 hunks)
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift (1 hunks)
  • Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Respect .swiftlint.yml rules; do not suppress SwiftLint rules broadly—scope and justify any exceptions
Adhere to the project’s zero warnings policy—fix new warnings and avoid introducing any

Files:

  • Sources/StreamChat/Models/ChannelRead.swift
  • Tests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swift
  • Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift
  • Sources/StreamChat/Models/Channel.swift
  • TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift
  • DemoApp/StreamChat/Components/DemoChatChannelListItemView.swift
  • Sources/StreamChat/Workers/CurrentUserUpdater.swift
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift
  • Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift
  • TestTools/StreamChatTestTools/TestData/DummyData/ChatChannel.swift
  • TestTools/StreamChatTestTools/TestData/DummyData/ChannelPayload.swift
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swift
  • Sources/StreamChat/ChatClient+Environment.swift
  • Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift
  • Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift
  • Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift
  • Tests/StreamChatTests/Models/ChatChannel_Tests.swift
  • Sources/StreamChat/Models/DeliveredMessageInfo.swift
  • Sources/StreamChat/ChatClient.swift
  • DemoAppPush/NotificationService.swift
  • Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift
  • Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift
  • Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swift
  • Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift
  • DemoApp/StreamChat/Components/DeliveredMessages/DemoChatMessageDeliveryStatusView.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift
  • Sources/StreamChat/ChatClientFactory.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift
  • Sources/StreamChat/WebSocketClient/Events/EventPayload.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift
  • DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift
  • Tests/StreamChatTests/StateLayer/Chat_Tests.swift
  • TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift
  • DemoApp/StreamChat/StreamChatWrapper+DemoApp.swift
  • Tests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swift
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
  • Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift
  • DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift
  • TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swift
  • Sources/StreamChat/WebSocketClient/Events/EventType.swift
  • TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift
  • Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift
  • Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift
  • DemoApp/Screens/AppConfigViewController/AppConfigViewController.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

When altering public API, update inline documentation comments in source

Files:

  • Sources/StreamChat/Models/ChannelRead.swift
  • Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift
  • Sources/StreamChat/Models/Channel.swift
  • Sources/StreamChat/Workers/CurrentUserUpdater.swift
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swift
  • Sources/StreamChat/ChatClient+Environment.swift
  • Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift
  • Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift
  • Sources/StreamChat/Models/DeliveredMessageInfo.swift
  • Sources/StreamChat/ChatClient.swift
  • Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift
  • Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swift
  • Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift
  • Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift
  • Sources/StreamChat/ChatClientFactory.swift
  • Sources/StreamChat/WebSocketClient/Events/EventPayload.swift
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift
  • Sources/StreamChat/WebSocketClient/Events/EventType.swift
  • Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift
Tests/{StreamChatTests,StreamChatUITests}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Tests/{StreamChatTests,StreamChatUITests}/**/*.swift: Add or extend tests in the matching module’s Tests folder
Prefer using provided test fakes/mocks in tests when possible

Files:

  • Tests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift
  • Tests/StreamChatTests/Models/ChatChannel_Tests.swift
  • Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift
  • Tests/StreamChatTests/StateLayer/Chat_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swift
  • Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swift
  • Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift
Tests/StreamChatTests/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Ensure tests cover core models and API surface of StreamChat

Files:

  • Tests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift
  • Tests/StreamChatTests/Models/ChatChannel_Tests.swift
  • Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift
  • Tests/StreamChatTests/StateLayer/Chat_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swift
  • Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swift
  • Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift
🧠 Learnings (3)
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
PR: GetStream/stream-chat-swift#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatTests/**/*.swift : Ensure tests cover core models and API surface of StreamChat

Applied to files:

  • Tests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift
  • Tests/StreamChatTests/Models/ChatChannel_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift
  • Tests/StreamChatTests/StateLayer/Chat_Tests.swift
  • Tests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swift
  • Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
PR: GetStream/stream-chat-swift#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatUITests/**/*.swift : Ensure tests cover view controllers and UI behaviors of StreamChatUI

Applied to files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
PR: GetStream/stream-chat-swift#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Add or extend tests in the matching module’s Tests folder

Applied to files:

  • StreamChat.xcodeproj/project.pbxproj
🧬 Code graph analysis (35)
Sources/StreamChat/Models/ChannelRead.swift (1)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • lastReadMessageId (2224-2236)
Tests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swift (1)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • markChannelsDelivered (295-303)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift (1)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • lastReadMessageId (2224-2236)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (5)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1)
  • markMessagesAsDelivered (586-600)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • markMessagesAsDelivered (117-128)
TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift (1)
  • request (131-144)
Sources/StreamChat/APIClient/APIClient.swift (1)
  • request (80-86)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • markChannelsDelivered (295-303)
Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift (1)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • markChannelsDelivered (295-303)
Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift (1)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • markChannelsDelivered (295-303)
Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift (1)
TestTools/StreamChatTestTools/TestData/DummyData/ChannelPayload.swift (1)
  • dummy (11-41)
TestTools/StreamChatTestTools/TestData/DummyData/ChatChannel.swift (1)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • lastReadMessageId (2224-2236)
TestTools/StreamChatTestTools/TestData/DummyData/ChannelPayload.swift (1)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • lastReadMessageId (2224-2236)
Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swift (1)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • encode (419-423)
Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift (2)
Sources/StreamChat/Utils/Logger/Logger.swift (2)
  • log (277-314)
  • debug (347-362)
Sources/StreamChat/Models/Channel.swift (1)
  • latestMessageNotMarkedAsDelivered (395-402)
Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift (3)
TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift (4)
  • user (100-102)
  • saveChannel (73-80)
  • saveChannel (394-401)
  • saveUser (82-85)
Sources/StreamChat/Database/DatabaseSession.swift (2)
  • saveChannel (713-716)
  • saveUser (718-721)
Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift (5)
  • toDomainEvent (50-66)
  • toDomainEvent (102-115)
  • toDomainEvent (161-185)
  • toDomainEvent (227-246)
  • toDomainEvent (300-313)
Tests/StreamChatTests/Models/ChatChannel_Tests.swift (3)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift (3)
  • mock (10-50)
  • mock (55-71)
  • mock (76-149)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2)
  • mock (11-104)
  • mock (108-126)
Sources/StreamChat/Models/Channel.swift (2)
  • latestMessageNotMarkedAsDelivered (395-402)
  • readState (390-392)
Sources/StreamChat/ChatClient.swift (1)
Sources/StreamChat/ChatClientFactory.swift (1)
  • makeEventNotificationCenter (114-146)
DemoAppPush/NotificationService.swift (1)
Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift (2)
  • handleNotification (141-148)
  • markMessageAsDelivered (151-166)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (3)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • markMessagesAsDelivered (117-128)
Sources/StreamChat/StateLayer/ConnectedUser.swift (1)
  • currentUserId (205-208)
Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift (1)
TestTools/StreamChatTestTools/Extensions/Unique/Date+Unique.swift (3)
  • unique (13-15)
  • unique (18-20)
  • unique (28-30)
Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swift (3)
Sources/StreamChat/WebSocketClient/Events/EventType.swift (1)
  • event (186-267)
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (2)
  • submitForDelivery (50-60)
  • cancel (65-70)
TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift (2)
  • submitForDelivery (32-36)
  • cancel (38-41)
Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift (2)
Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift (1)
  • asModel (74-74)
Sources/StreamChat/Models/Payload+asModel/MessagePayload+asModel.swift (2)
  • asModel (14-143)
  • asModel (149-159)
DemoApp/StreamChat/Components/DeliveredMessages/DemoChatMessageDeliveryStatusView.swift (1)
DemoApp/StreamChat/Components/DemoChatChannelListItemView.swift (1)
  • updateContent (35-40)
Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (5)
Tests/StreamChatTests/StateLayer/Chat_Tests.swift (1)
  • cleanUp (1875-1881)
TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift (4)
  • cleanUp (111-126)
  • mock (138-171)
  • mock (303-317)
  • mock (321-335)
TestTools/StreamChatTestTools/SpyPattern/Spy/ChannelListUpdater_Spy.swift (1)
  • cleanUp (32-44)
TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift (1)
  • setMockToken (127-135)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (1)
  • loadNextChannels (169-191)
Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift (7)
Sources/StreamChat/Database/DTOs/UserDTO.swift (3)
  • user (34-40)
  • user (138-140)
  • asModel (194-194)
Sources/StreamChat/Database/DataStore.swift (2)
  • user (34-36)
  • channel (56-58)
Sources/StreamChat/Database/DTOs/ChannelDTO.swift (2)
  • channel (427-429)
  • asModel (506-508)
Sources/StreamChat/WebSocketClient/Events/EventPayload.swift (2)
  • value (253-259)
  • value (262-272)
Sources/StreamChat/WebSocketClient/Events/UserEvents.swift (3)
  • toDomainEvent (27-34)
  • toDomainEvent (57-64)
  • toDomainEvent (104-114)
Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift (12)
  • toDomainEvent (40-53)
  • toDomainEvent (81-90)
  • toDomainEvent (155-166)
  • toDomainEvent (192-206)
  • toDomainEvent (229-236)
  • toDomainEvent (273-286)
  • toDomainEvent (320-332)
  • toDomainEvent (355-362)
  • toDomainEvent (396-408)
  • toDomainEvent (445-458)
  • toDomainEvent (495-508)
  • toDomainEvent (536-543)
Sources/StreamChat/Models/Payload+asModel/UserPayload+asModel.swift (1)
  • asModel (10-29)
Sources/StreamChat/ChatClientFactory.swift (1)
Sources/StreamChat/StateLayer/ConnectedUser.swift (1)
  • currentUserId (205-208)
Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swift (3)
TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift (1)
  • cleanUp (44-50)
Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swift (1)
  • handle (19-34)
TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift (1)
  • writeSynchronously (175-182)
DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (1)
Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift (1)
  • messageActions (616-644)
TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift (1)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • lastReadMessageId (2224-2236)
Tests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swift (2)
TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift (3)
  • cleanUp (44-50)
  • submitForDelivery (32-36)
  • cancel (38-41)
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (2)
  • submitForDelivery (50-60)
  • cancel (65-70)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (5)
Sources/StreamChat/Utils/Logger/Logger.swift (2)
  • error (395-410)
  • log (277-314)
Sources/StreamChat/Models/Channel.swift (1)
  • latestMessageNotMarkedAsDelivered (395-402)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1)
  • markMessagesAsDelivered (586-600)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • markMessagesAsDelivered (117-128)
Tests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swift (7)
TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift (2)
  • setMockToken (127-135)
  • logOutUser (99-101)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1)
  • markMessagesAsDelivered (586-600)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • markMessagesAsDelivered (117-128)
Sources/StreamChat/Repositories/AuthenticationRepository.swift (1)
  • logOutUser (207-212)
TestTools/StreamChatTestTools/Wait/WaitFor.swift (1)
  • waitFor (32-59)
TestTools/StreamChatTestTools/Assertions/AssertTestQueue.swift (1)
  • AssertTestQueue (12-16)
Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift (1)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • markChannelsDelivered (295-303)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (2)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1)
  • markMessagesAsDelivered (586-600)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swift (1)
Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swift (2)
  • encode (17-21)
  • encode (37-40)
TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift (1)
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (2)
  • submitForDelivery (50-60)
  • cancel (65-70)
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (1)
TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swift (2)
  • submitForDelivery (32-36)
  • cancel (38-41)
Tests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swift (5)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (1)
  • markMessagesAsDelivered (586-600)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • markMessagesAsDelivered (117-128)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • markChannelsDelivered (295-303)
TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift (1)
  • test_simulateResponse (109-112)

@Stream-SDK-Bot
Copy link
Collaborator

SDK Performance

target metric benchmark branch performance status
MessageList Hitches total duration 10 ms 3.34 ms 66.6% 🔼 🟢
Duration 2.6 s 2.54 s 2.31% 🔼 🟢
Hitch time ratio 4 ms per s 1.32 ms per s 67.0% 🔼 🟢
Frame rate 75 fps 78.07 fps 4.09% 🔼 🟢
Number of hitches 1 0.4 60.0% 🔼 🟢

@github-actions
Copy link

github-actions bot commented Oct 16, 2025

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e19cdf and ed69298.

📒 Files selected for processing (3)
  • CHANGELOG.md (1 hunks)
  • DemoApp/StreamChat/Components/DemoChatChannelVC.swift (1 hunks)
  • Sources/StreamChat/Models/Channel.swift (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Respect .swiftlint.yml rules; do not suppress SwiftLint rules broadly—scope and justify any exceptions
Adhere to the project’s zero warnings policy—fix new warnings and avoid introducing any

Files:

  • DemoApp/StreamChat/Components/DemoChatChannelVC.swift
  • Sources/StreamChat/Models/Channel.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

When altering public API, update inline documentation comments in source

Files:

  • Sources/StreamChat/Models/Channel.swift
🧬 Code graph analysis (1)
DemoApp/StreamChat/Components/DemoChatChannelVC.swift (2)
Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift (1)
  • eventsController (566-594)
DemoApp/StreamChat/Components/DemoChatThreadVC.swift (1)
  • eventsController (75-82)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Build Test App and Frameworks
  • GitHub Check: Metrics
🔇 Additional comments (2)
Sources/StreamChat/Models/Channel.swift (2)

394-412: LGTM! Documentation added as requested.

The comprehensive inline documentation has been added to address the previous review feedback. The method logic correctly implements the delivery tracking rules: it returns delivery information only when a read state exists, a latest message is present, the message is from another user, and the message is newer than both the last read and last delivered timestamps.


387-392: Add inline documentation for the new public method.

The coding guidelines require updating inline documentation comments when altering public API. This new public method lacks documentation explaining its purpose, the userId parameter, and the return value.

As per coding guidelines, apply this diff to add documentation:

+    /// Returns the user's read state for this channel.
+    ///
+    /// - Parameter userId: The ID of the user.
+    /// - Returns: The read state, or `nil` if not found.
     public func readState(for userId: UserId) -> ChatChannelRead? {
         reads.first { $0.user.id == userId }
     }

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)

65-71: Clarify documentation for missing read state behavior.

The implementation returns true when channel.read(for: currentUser.id) is nil (line 70), treating a missing read state as allowing delivery. However, the documentation doesn't explicitly state this behavior. Per past review comments, this should be clarified.

Consider updating the function documentation to clarify the missing read state behavior:

     /// - Parameters:
     ///   - message: The message to check for delivery status.
     ///   - currentUser: The current user who would mark the message as delivered.
     ///   - channel: The channel containing the message.
+    /// - Note: If the current user has no read state in the channel, the message is allowed to be marked as delivered.
     /// - Returns: `true` if the message can be marked as delivered, `false` otherwise.

Alternatively, verify via tests that this behavior is intentional:

#!/bin/bash
# Search for tests covering the missing read state scenario
ast-grep --pattern 'func test_$$$_whenUserHasNoReadState$$$() {
  $$$
}'
rg -n "read.*nil|no.*read.*state" Tests/StreamChatTests/Models/MessageDeliveryCriteriaValidator_Tests.swift
🧹 Nitpick comments (4)
CHANGELOG.md (1)

7-8: Consider expanding the changelog entry to follow established patterns.

The current entry is minimal compared to similar multi-API additions elsewhere in the CHANGELOG (e.g., lines 41–45 for push preferences, lines 75–78 for location sharing, lines 204–208 for message reminders). Given the PR introduces multiple new public APIs (ChatRemoteNotificationHandler.markMessageAsDelivered(), ChatChannel.reads(), ChatChannel.deliveredReads(), ChatChannelRead.lastDeliveredAt, ChatChannelRead.lastDeliveredMessageId), consider adding sub-bullets to document the specific new capabilities:

  ### ✅ Added
- - Add support for message delivered info [#3846](https://github.com/GetStream/stream-chat-swift/pull/3846)
+ - Add support for message delivered info [#3846](https://github.com/GetStream/stream-chat-swift/pull/3846)
+   - Add `ChatRemoteNotificationHandler.markMessageAsDelivered(deliveries:)`
+   - Add `ChatChannel.reads(message:)` and `ChatChannel.deliveredReads(message:)`
+   - Add `ChatChannelRead.lastDeliveredAt` and `ChatChannelRead.lastDeliveredMessageId`

Additionally, the PR objectives mention UI changes (double grey checkmark for delivered-but-not-read messages). Consider whether a corresponding entry under ## StreamChatUI is warranted. ,

Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift (2)

225-269: Clarify or simplify the lastDeliveredAt setup in this test.

This test sets lastDeliveredAt to 10 seconds before the message was created (line 254: messageCreatedAt.addingTimeInterval(-10)), which differs from the enabled test where it's set after.

If the intent is to verify that delivery status is not displayed when deliveryEventsEnabled: false, consider using the same delivery data as the first test (delivered state) to clearly demonstrate that the feature flag controls visibility. The existing read tests (lines 151-177) follow this pattern—they use identical message data and only toggle readEventsEnabled.

Alternatively, if this setup is intentional to test handling of stale/inconsistent data, add a comment explaining the rationale.

Apply this diff to align with the enabled test and the pattern used in read tests:

                 reads: [
                     .mock(
                         lastReadAt: Date.distantPast,
                         lastReadMessageId: nil,
                         unreadMessagesCount: 0,
                         user: otherUser,
-                        lastDeliveredAt: messageCreatedAt.addingTimeInterval(-10),
+                        lastDeliveredAt: messageCreatedAt.addingTimeInterval(10),
                         lastDeliveredMessageId: deliveredMessage.id
                     )
                 ],

179-269: Consider adding test coverage for "sent but not delivered" state.

The new tests cover delivered messages with delivery events enabled/disabled. To complete the delivery state spectrum (analogous to the existing read tests), consider adding a test for a message that is sent but has not yet been delivered, with deliveryEventsEnabled: true. This would verify the UI correctly displays the "sent" status when delivery tracking is active but no delivery receipt has been recorded yet.

Example setup:

  • Message from current user at time 100
  • deliveryEventsEnabled: true
  • lastDeliveredAt set to a time before the message (e.g., 90) or no matching lastDeliveredMessageId
Sources/StreamChat/Models/ChatMessage.swift (1)

535-535: Consider refining the deprecation message.

The deprecation message refers to "The function" but could be more precise: "The method deliveryStatus(for:) is preferred" since this is an instance method, not a function.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 78f2b9f and 0449171.

⛔ Files ignored due to path filters (10)
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusCheckmarkView_Tests/test_appearance_delivered.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusCheckmarkView_Tests/test_appearance_delivered.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInDirectMessagesChannel.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInDirectMessagesChannel.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInGroupChannel.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInGroupChannel.default-light.png is excluded by !**/*.png
📒 Files selected for processing (17)
  • CHANGELOG.md (1 hunks)
  • DemoApp/Shared/DemoUsers.swift (1 hunks)
  • Sources/StreamChat/ChatClient.swift (3 hunks)
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (4 hunks)
  • Sources/StreamChat/Models/ChatMessage.swift (3 hunks)
  • Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1 hunks)
  • Sources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swift (2 hunks)
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView.swift (1 hunks)
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift (2 hunks)
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (44 hunks)
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (18 hunks)
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift (5 hunks)
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift (1 hunks)
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift (1 hunks)
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift (1 hunks)
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Respect .swiftlint.yml rules; do not suppress SwiftLint rules broadly—scope and justify any exceptions
Adhere to the project’s zero warnings policy—fix new warnings and avoid introducing any

Files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • DemoApp/Shared/DemoUsers.swift
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • Sources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swift
  • Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
  • Sources/StreamChat/Models/ChatMessage.swift
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift
  • Sources/StreamChat/ChatClient.swift
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
Tests/{StreamChatTests,StreamChatUITests}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Tests/{StreamChatTests,StreamChatUITests}/**/*.swift: Add or extend tests in the matching module’s Tests folder
Prefer using provided test fakes/mocks in tests when possible

Files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
Tests/StreamChatUITests/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Ensure tests cover view controllers and UI behaviors of StreamChatUI

Files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

When altering public API, update inline documentation comments in source

Files:

  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView.swift
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swift
  • Sources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swift
  • Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
  • Sources/StreamChat/Models/ChatMessage.swift
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift
  • Sources/StreamChat/ChatClient.swift
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
Tests/StreamChatTests/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Ensure tests cover core models and API surface of StreamChat

Files:

  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Update CHANGELOG for user-visible SDK changes

Files:

  • CHANGELOG.md
🧠 Learnings (6)
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatTests/**/*.swift : Ensure tests cover core models and API surface of StreamChat

Applied to files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • DemoApp/Shared/DemoUsers.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
  • StreamChat.xcodeproj/project.pbxproj
  • CHANGELOG.md
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatUITests/**/*.swift : Ensure tests cover view controllers and UI behaviors of StreamChatUI

Applied to files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Add or extend tests in the matching module’s Tests folder

Applied to files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Prefer using provided test fakes/mocks in tests when possible

Applied to files:

  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to CHANGELOG.md : Update CHANGELOG for user-visible SDK changes

Applied to files:

  • DemoApp/Shared/DemoUsers.swift
  • CHANGELOG.md
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Sources/{StreamChat,StreamChatUI}/**/*.swift : When altering public API, update inline documentation comments in source

Applied to files:

  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
  • Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
  • Sources/StreamChat/Models/ChatMessage.swift
  • Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift
  • StreamChat.xcodeproj/project.pbxproj
  • CHANGELOG.md
  • Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift
🧬 Code graph analysis (12)
Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift (3)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2)
  • mock (11-104)
  • mock (108-126)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatUser_Mock.swift (1)
  • mock (10-46)
Sources/StreamChat/Models/Channel.swift (1)
  • reads (403-407)
Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift (1)
Sources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swift (1)
  • updateContent (319-369)
Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift (4)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2)
  • mock (11-104)
  • mock (108-126)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatUser_Mock.swift (1)
  • mock (10-46)
Sources/StreamChat/Models/Channel.swift (1)
  • reads (403-407)
Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swift (1)
  • optionsForMessage (33-117)
Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swift (1)
Sources/StreamChat/Models/ChatMessage.swift (1)
  • deliveryStatus (558-581)
Tests/StreamChatTests/Models/ChatMessage_Tests.swift (4)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift (3)
  • mock (10-52)
  • mock (57-73)
  • mock (78-151)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2)
  • mock (11-104)
  • mock (108-126)
Sources/StreamChat/Models/ChatMessage.swift (1)
  • deliveryStatus (558-581)
Sources/StreamChat/Models/Channel.swift (2)
  • reads (403-407)
  • read (390-392)
Sources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swift (2)
Sources/StreamChat/Models/ChatMessage.swift (1)
  • deliveryStatus (558-581)
Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift (1)
  • channel (1815-1833)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)
Sources/StreamChat/Models/Channel.swift (1)
  • read (390-392)
Sources/StreamChat/Models/ChatMessage.swift (1)
Sources/StreamChat/Models/Channel.swift (2)
  • deliveredReads (418-425)
  • read (390-392)
Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift (1)
Sources/StreamChat/Models/ChatMessage.swift (1)
  • deliveryStatus (558-581)
Sources/StreamChat/ChatClient.swift (1)
Sources/StreamChat/ChatClientFactory.swift (1)
  • makeEventNotificationCenter (114-146)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (5)
Sources/StreamChat/Utils/Logger/Logger.swift (2)
  • error (395-410)
  • log (277-314)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (6)
  • currentUserController (13-15)
  • currentUserController (745-748)
  • currentUserController (750-753)
  • currentUserController (755-758)
  • currentUserController (768-772)
  • currentUserController (774-777)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)
  • canMarkMessageAsDelivered (35-71)
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (1)
  • markMessagesAsDelivered (73-103)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (2)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • cleanUp (131-165)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (2)
  • synchronize (157-162)
  • loadNextChannels (173-195)
🔇 Additional comments (21)
DemoApp/Shared/DemoUsers.swift (1)

8-8: Verify if this API key change is intentional.

The default API key has been changed from DemoApiKeys.frankfurtC1 to DemoApiKeys.usEastC6. This change is not mentioned in the PR description or objectives, which focus on message delivery status functionality.

Please confirm:

  • Is this change intentional or an accidental testing artifact?
  • If intentional, should it be documented in the PR description and does it need to be part of this delivery status PR?
  • Could this affect existing demo app users, tests, or documentation?
Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift (1)

179-223: LGTM! Clear test for delivered message with delivery events enabled.

The test correctly sets up a delivered-but-not-read message scenario with lastDeliveredAt after the message creation time and deliveryEventsEnabled: true, ensuring the snapshot captures the delivery UI state.

Sources/StreamChat/ChatClient.swift (3)

81-82: LGTM!

Internal property declaration is clean and appropriately documented for infrastructure wiring.


167-180: LGTM!

Dependency construction follows the correct order and the environment builder pattern used throughout the codebase. The channelDeliveryTracker correctly receives currentUserUpdater as a dependency.


239-239: LGTM!

Assignment is consistent with the initialization pattern for other repository properties.

Sources/StreamChat/Models/ChatMessage.swift (1)

731-737: LGTM!

The enum documentation clearly distinguishes between sent (server received), delivered (device received), and read states. The semantic progression is well-defined.

Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swift (1)

63-78: LGTM!

Test coverage for the delivered status follows the established pattern and covers both appearance and accessibility concerns.

Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swift (1)

209-217: LGTM!

Migration to the channel-aware API is correct, and the delivered status visibility is appropriately gated by the deliveryEventsEnabled configuration flag, following the same pattern as read events.

Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView.swift (1)

45-45: LGTM!

Grouping .delivered with .read for the same checkmark image is correct. The tint color differentiation (lines 56-62) ensures delivered shows as grey while read shows as accent color, matching the intended UX.

Sources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swift (1)

216-229: LGTM!

The migration to the channel-aware delivery status API is correct, and the delivered case is appropriately gated by deliveryEventsEnabled, consistent with how other delivery statuses are handled.

Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swift (1)

117-197: LGTM!

Comprehensive test coverage for the delivered status in both direct and group channels. The tests properly configure channel delivery events and channel reads to exercise the delivered state rendering.

Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift (1)

84-84: LGTM!

Correctly uses the new facade property, simplifying access to delivery status.

Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swift (1)

1758-1846: LGTM! Well-structured tests for delivery events feature flag.

The two new test methods correctly verify that the delivery status indicator respects the deliveryEventsEnabled configuration:

  • The first test confirms that when delivery events are enabled and a message is delivered (but not read), the indicator is included in layout options.
  • The second test ensures the indicator is properly gated when delivery events are disabled, even with the same delivered message state.

Both tests follow existing patterns in the file, use appropriate mocks, and complement the existing delivery status test coverage.

StreamChat.xcodeproj/project.pbxproj (1)

1467-1476: LGTM! Project structure is well-organized.

All new delivery-related files are properly registered in the Xcode project with correct build phases, target memberships, and group organization. The structure follows project conventions with appropriate separation of sources, tests, mocks, and resources.

Also applies to: 1668-1672, 1734-1735, 1766-1766, 1796-1798, 1818-1819, 4361-4362, 4367-4368, 4497-4499, 4547-4548, 4572-4572, 4595-4596, 4611-4611, 5953-5953, 5976-5976, 6089-6089, 6138-6138, 6759-6759, 7003-7003, 7219-7219, 7229-7229, 7272-7272, 7335-7335, 7507-7507, 7529-7529, 8766-8773, 9130-9138, 10585-10585, 11386-11386, 11459-11459, 11604-11604, 11811-11811, 11843-11843, 11856-11856, 12114-12114, 12124-12124, 12175-12175, 12246-12246, 12364-12364, 12417-12417, 12806-12806, 12863-12863, 12940-12940, 13065-13065, 13094-13094

Tests/StreamChatTests/Models/ChatMessage_Tests.swift (1)

289-359: LGTM! Comprehensive test coverage for delivered status.

The tests correctly verify that messages return .delivered status when they have been delivered but not read. The test setup properly creates channel reads with lastDeliveredAt and lastDeliveredMessageId to establish the delivered state, and both regular messages and thread replies are covered.

Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (3)

60-69: LGTM! Dependency injection follows established patterns.

The lazy properties for currentUserUpdater and deliveryCriteriaValidator are correctly initialized via environment builders, enabling dependency injection for testing while maintaining the same pattern used by the existing worker property.


238-263: LGTM! Defensive implementation with appropriate error handling.

The method correctly filters channels based on delivery criteria, avoids unnecessary API calls when there are no deliveries, and logs errors for debugging. The early return when currentUser is nil and the empty deliveries check are good defensive practices.


187-189: LGTM! Integration points correctly trigger automatic delivery marking.

The calls to markChannelsAsDeliveredIfNeeded are appropriately placed after successful channel fetches in both loadNextChannels and updateChannelList, enabling automatic delivery marking as intended by the feature design.

Also applies to: 226-229

Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (2)

37-37: LGTM! Test infrastructure correctly wired.

The test environment properly integrates the new currentUserUpdater and deliveryCriteriaValidator mocks, following the established patterns for dependency injection. The cleanup in tearDown ensures mocks are properly reset between tests.

Also applies to: 2110-2111, 2134-2145


868-956: Tests look good—they thoroughly cover the delivery-marking functionality.

The tests verify that markChannelsAsDelivered is called with correct messages after successful channel updates and that only messages meeting delivery criteria are marked. Both synchronize and loadNextChannels paths are well covered.

The implementation already handles edge cases gracefully: nil currentUser is guarded against with an early return, and errors from markMessagesAsDelivered are logged but don't break the flow. The tests focus appropriately on the success path and filtering logic, which is the core business behavior.

Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)

9-26: LGTM! Access control is appropriate for internal usage.

The protocol and struct correctly use internal (default) access since they are only consumed by internal components like ChannelListController.Environment. The access control issue mentioned in past reviews has been resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (1)

237-263: Consider weak self capture in completion handler.

The implementation correctly filters channels and builds delivery info, but the completion handler at line 258 doesn't use [weak self]. If the controller is deallocated before the API call completes, this prevents the error from being logged. While this is acceptable, using weak self would be more defensive.

Consider this adjustment:

-        currentUserUpdater.markMessagesAsDelivered(deliveries) { error in
+        currentUserUpdater.markMessagesAsDelivered(deliveries) { [weak self] error in
             if let error = error {
-                log.error("Failed to mark channels as delivered: \(error)")
+                self?.log.error("Failed to mark channels as delivered: \(error)")
             }
         }

Note: If log is a global function rather than an instance method, the weak self capture would only apply if additional instance state is accessed in future enhancements.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2474ea0 and 496ec1d.

📒 Files selected for processing (5)
  • CHANGELOG.md (1 hunks)
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (5 hunks)
  • Sources/StreamChat/Models/ChatMessage.swift (3 hunks)
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (19 hunks)
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Respect .swiftlint.yml rules; do not suppress SwiftLint rules broadly—scope and justify any exceptions
Adhere to the project’s zero warnings policy—fix new warnings and avoid introducing any

Files:

  • Sources/StreamChat/Models/ChatMessage.swift
  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

When altering public API, update inline documentation comments in source

Files:

  • Sources/StreamChat/Models/ChatMessage.swift
  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
Tests/{StreamChatTests,StreamChatUITests}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Tests/{StreamChatTests,StreamChatUITests}/**/*.swift: Add or extend tests in the matching module’s Tests folder
Prefer using provided test fakes/mocks in tests when possible

Files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
Tests/StreamChatTests/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Ensure tests cover core models and API surface of StreamChat

Files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
🧠 Learnings (6)
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Sources/{StreamChat,StreamChatUI}/**/*.swift : When altering public API, update inline documentation comments in source

Applied to files:

  • Sources/StreamChat/Models/ChatMessage.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatTests/**/*.swift : Ensure tests cover core models and API surface of StreamChat

Applied to files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatUITests/**/*.swift : Ensure tests cover view controllers and UI behaviors of StreamChatUI

Applied to files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Prefer using provided test fakes/mocks in tests when possible

Applied to files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/{StreamChatTests,StreamChatUITests}/**/*.swift : Add or extend tests in the matching module’s Tests folder

Applied to files:

  • Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift
  • Tests/StreamChatTests/Models/ChatMessage_Tests.swift
📚 Learning: 2025-11-03T10:25:07.731Z
Learnt from: laevandus
Repo: GetStream/stream-chat-swift PR: 3863
File: Sources/StreamChat/Repositories/SyncOperations.swift:124-126
Timestamp: 2025-11-03T10:25:07.731Z
Learning: In Sources/StreamChat/Repositories/SyncOperations.swift, the SyncEventsOperation deliberately limits syncing to a maximum of 100 channels (Array(channelIds.prefix(100))) to avoid server-side errors. Channels beyond the first 100 are intentionally not synced as part of this design decision.

Applied to files:

  • Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift
🧬 Code graph analysis (4)
Sources/StreamChat/Models/ChatMessage.swift (1)
Sources/StreamChat/Models/Channel.swift (2)
  • deliveredReads (418-425)
  • read (390-392)
Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (3)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (1)
  • loadNextChannels (173-195)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • cleanUp (131-165)
TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift (1)
  • writeSynchronously (175-182)
Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (6)
Sources/StreamChat/Utils/Logger/Logger.swift (2)
  • error (395-410)
  • log (277-314)
Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (6)
  • currentUserController (13-15)
  • currentUserController (745-748)
  • currentUserController (750-753)
  • currentUserController (755-758)
  • currentUserController (768-772)
  • currentUserController (774-777)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)
  • canMarkMessageAsDelivered (35-71)
Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift (1)
  • markMessagesAsDelivered (73-103)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift (1)
  • markMessagesAsDelivered (117-128)
Sources/StreamChat/Workers/CurrentUserUpdater.swift (1)
  • markMessagesAsDelivered (283-294)
Tests/StreamChatTests/Models/ChatMessage_Tests.swift (3)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift (3)
  • mock (10-52)
  • mock (57-73)
  • mock (78-151)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2)
  • mock (11-104)
  • mock (108-126)
Sources/StreamChat/Models/ChatMessage.swift (1)
  • deliveryStatus (563-586)
🔇 Additional comments (15)
Sources/StreamChat/Models/ChatMessage.swift (3)

535-555: Clear deprecation strategy.

The deprecation annotation properly guides users to the new channel-aware API while preserving backward compatibility.


557-586: Well-documented channel-aware delivery status implementation.

The documentation comprehensively addresses the past review comment by including behavior description, parameter details, and return value conditions. The implementation correctly integrates channel.deliveredReads(for:) to determine delivered status and maintains proper precedence: read > delivered > sent.

Based on past review comments.


736-742: Clear documentation for delivery status states.

The updated documentation properly distinguishes between .sent (on server but not yet delivered) and the new .delivered (reached user's device) states.

Tests/StreamChatTests/Models/ChatMessage_Tests.swift (4)

127-183: Comprehensive coverage of nil cases.

The tests properly verify that deliveryStatus(for:) returns nil for messages authored by others and for non-regular/non-reply message types. The consistent pattern of creating a channel and matching message CID to channel CID is correct.


185-287: Thorough coverage of local states and sent status.

The tests properly verify pending, failed, and sent statuses for both regular messages and thread replies, with correct channel context and CID matching throughout.


289-359: Excellent coverage of delivered status scenarios.

The tests properly verify the delivered status for both regular messages and thread replies. The thread reply test at Line 352 correctly accounts for the 1-second error interval used in the deliveredReads(for:) implementation.


361-428: Comprehensive read status tests with correct precedence validation.

The tests properly verify read status and that read status takes precedence over delivered status. The CID consistency issue flagged in the past review comment has been resolved—Line 402 now correctly uses cid: cid to match the channel.

Based on past review comments.

Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift (4)

23-28: LGTM! Proper test setup for mock injection.

Moving controller initialization to setUp() enables the test environment to inject mocks via the environment parameter, which is essential for testing the delivery marking behavior.


869-958: LGTM! Comprehensive test coverage for delivery marking.

The three new tests properly cover:

  • Delivery marking after synchronize() success
  • Delivery marking after loadNextChannels() success
  • Selective marking based on delivery criteria validation

The tests correctly verify the CurrentUserUpdater interactions and validate the delivered message details.


2113-2148: LGTM! Proper environment wiring for new dependencies.

The TestEnvironment correctly extends the environment with builders for currentUserUpdater and deliveryCriteriaValidator, enabling mock injection for testing delivery marking behavior. The pattern matches the existing channelListUpdater setup.


1774-1850: Test data updated for new delivery fields.

The channel read payloads are properly extended with lastDeliveredAt and lastDeliveredMessageId fields to match the updated ChannelRead model. Using nil values is appropriate for these tests that don't focus on delivery functionality.

Sources/StreamChat/Controllers/ChannelListController/ChannelListController.swift (4)

59-68: LGTM! Proper dependency injection for delivery tracking.

The new currentUserUpdater and deliveryCriteriaValidator properties are correctly initialized via environment builders, enabling testability through mock injection.


188-188: LGTM! Delivery marking properly integrated after channel updates.

The calls to markChannelsAsDeliveredIfNeeded are correctly placed after successful channel list updates in both loadNextChannels and updateChannelList (synchronize path). The fire-and-forget approach with error logging is appropriate since delivery marking failures shouldn't block the main channel list flow.

Also applies to: 226-228


299-306: LGTM! Environment builders properly structured.

The new builders follow the existing pattern and provide sensible defaults for production while enabling mock injection in tests.


153-153: LGTM! Validator properly initialized.

The deliveryCriteriaValidator is correctly initialized from the environment builder during controller construction.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 523aa0f and cc2cf64.

⛔ Files ignored due to path filters (6)
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-dark.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/Gallery/__Snapshots__/GalleryVC_Tests/test_snapshotWithMessageTimestampOlderDate.default-light.png is excluded by !**/*.png
  • Tests/StreamChatUITests/SnapshotTests/Gallery/__Snapshots__/GalleryVC_Tests/test_snapshotWithMessageTimestampToday.default-light.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Respect .swiftlint.yml rules; do not suppress SwiftLint rules broadly—scope and justify any exceptions
Adhere to the project’s zero warnings policy—fix new warnings and avoid introducing any

Files:

  • Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

When altering public API, update inline documentation comments in source

Files:

  • Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift
🧠 Learnings (2)
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Sources/{StreamChat,StreamChatUI}/**/*.swift : When altering public API, update inline documentation comments in source

Applied to files:

  • Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift
📚 Learning: 2025-09-18T10:00:24.878Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T10:00:24.878Z
Learning: Applies to Tests/StreamChatUITests/**/*.swift : Ensure tests cover view controllers and UI behaviors of StreamChatUI

Applied to files:

  • Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift
🧬 Code graph analysis (1)
Sources/StreamChatUI/ChatChannel/ChatChannelVC.swift (3)
Sources/StreamChat/WebSocketClient/Events/EventType.swift (1)
  • event (186-267)
Sources/StreamChat/WebSocketClient/Events/EventPayload.swift (1)
  • event (208-210)
Tests/StreamChatUITests/Mocks/ChatMessageList/ChatMessageListView_Mock.swift (1)
  • reloadRows (22-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build LLC + UI (Xcode 15)
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Metrics


guard !deliveredMessages.isEmpty else { return }

self?.currentUserUpdater.markMessagesAsDelivered(deliveredMessages) { [weak self] error in
Copy link
Member

Choose a reason for hiding this comment

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

The current API only supports upto 100 messages in payload. So you might also want to do it in a batch of 100 messages.

@testableapple testableapple added the 🟢 QAed A PR that was QAed label Nov 4, 2025
@nuno-vieira nuno-vieira merged commit 5b89e49 into develop Nov 4, 2025
1 of 2 checks passed
@nuno-vieira nuno-vieira deleted the add/message-delivered-status branch November 4, 2025 16:50
@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title develop branch diff status
StreamChat 8.28 MB 7.88 MB -409 KB 🚀
StreamChatUI 4.89 MB 4.89 MB 0 KB 🟢

@Stream-SDK-Bot
Copy link
Collaborator

StreamChat XCSize

Object Diff (bytes)
MessageEvents.o -461607
ChatRemoteNotificationHandler.o +13546
MemberController.o -11376
ChannelDeliveredPayload.o +7086
ChannelDeliveryTracker.o +6146
Show 45 more objects
Object Diff (bytes)
MessageEditor.o -5988
MessageDeliveryCriteriaValidator.o +2640
ChannelListController.o +2617
ChannelDeliveredMiddleware.o +2505
WebSocketConnectPayload.o +2355
Channel.o +2112
EndpointPath.o +1965
ChannelRead.o +1834
ChannelListPayload.o +1771
ChatMessage.o +1560
Logger.o +1322
ChatClient+Environment.o +1287
EventPayload.o +1275
CurrentUserController.o -1235
DeliveredMessageInfo.o +1166
ChannelController.o +1144
ErrorPayload.o +836
ThreadQuery.o +716
ChannelReadDTO.o +714
CurrentUserUpdater.o +612
LivestreamChannelController.o -536
ReminderPayloads.o +464
Chat.o +436
MemberPayload.o -412
UserInfo.o +381
Deprecations.o +316
MessageSender.o -258
ChatMessageVideoAttachment.o +256
ManualEventHandler.o -252
ConnectionRepository.o +228
AnyAttachmentPayload.o +220
CurrentUserDTO.o +218
ChatClient.o +207
EventType.o +198
ChatClientFactory.o +172
ChannelConfigDTO.o +162
EventsController.o +160
ChannelPayload+asModel.o +136
MessageController.o +104
ChannelListLinker.o +92
UserPayloads.o +92
ChannelDTO.o +88
ChannelList.o +88
CurrentUser.o +68
PollVoteListController.o +48

@Stream-SDK-Bot
Copy link
Collaborator

StreamChatUI XCSize

Object Diff (bytes)
ChatChannelVC.o +332
ChatChannelListItemView.o +136
ChatMessageLayoutOptionsResolver.o +92
ChatThreadVC.o -84
ChatMessageDeliveryStatusCheckmarkView.o +52

@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 4, 2025

@Stream-SDK-Bot Stream-SDK-Bot mentioned this pull request Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✅ Feature An issue or PR related to a feature 🟢 QAed A PR that was QAed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants