- 
                Notifications
    
You must be signed in to change notification settings  - Fork 227
 
Add support for message delivered info #3846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Expose markMessageAsDelivered in chat notification handler
…itting channels for delivery
          
WalkthroughImplements delivery-receipts: new delivery models, event types and middleware, a throttled ChannelDeliveryTracker, API endpoint/payloads, database fields and Core Data updates, CurrentUserUpdater wiring, validator logic, UI additions (reads view, delivery status), demo app toggles, push integration, and extensive tests/mocks. Changes
 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: detect/update channels
    C->>V: validate per-channel candidate message
    alt eligible
        C->>T: submitForDelivery(channelId, messageId)
    end
    Note right of T: Throttler batches (1s)
    T->>U: markMessagesAsDelivered([MessageDeliveryInfo])
    U->>API: POST /channels/delivered (ChannelDeliveredRequestPayload)
    API-->>U: EmptyResponse
    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
        CH-->>M: read -> return .read
    else has delivered
        CH-->>M: deliveredReads -> return .delivered
    else
        M-->>UI: .sent
    end
    Note over DB: message.delivered event -> ChannelDeliveredMiddleware updates ChannelRead and cancels pending tracker entries
    Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention: 
 Possibly related PRs
 Suggested labels
 Suggested reviewers
 Poem
 Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
 ✅ Passed checks (3 passed)
 ✨ Finishing touches
 🧪 Generate unit tests (beta)
 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. Comment   | 
    
There was a problem hiding this 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.demoAppConfigaccess 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
.deliveredand.readcases 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.idwhenuser.nameis 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.
getDeliveredTimestampandgetReadTimestampshare nearly identical logic, differing only in the keypath accessed (.lastDeliveredAtvs.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:
Empty array optimization: Add an early return if
messages.isEmptyto avoid unnecessary API calls:guard !messages.isEmpty else { completion?(nil) return }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 inputValidation 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 unusedcurrentUserIdparameter; fix call site closure
currentUserIdisn’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 flowAPI 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 eventsTo 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 trackerPlease 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 monotonicOut-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-nilStrengthen 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 NotificationMessageNewEventDTOEnsure 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.
📒 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.swiftTests/StreamChatTests/APIClient/Endpoints/ChannelEndpoints_Tests.swiftSources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swiftSources/StreamChat/Models/Channel.swiftTestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swiftDemoApp/StreamChat/Components/DemoChatChannelListItemView.swiftSources/StreamChat/Workers/CurrentUserUpdater.swiftSources/StreamChat/APIClient/Endpoints/EndpointPath.swiftSources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swiftTests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swiftTestTools/StreamChatTestTools/TestData/DummyData/ChatChannel.swiftTestTools/StreamChatTestTools/TestData/DummyData/ChannelPayload.swiftSources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swiftSources/StreamChat/ChatClient+Environment.swiftSources/StreamChat/Database/DTOs/ChannelReadDTO.swiftSources/StreamChat/APIClient/ChatRemoteNotificationHandler.swiftTests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swiftTests/StreamChatTests/Models/ChatChannel_Tests.swiftSources/StreamChat/Models/DeliveredMessageInfo.swiftSources/StreamChat/ChatClient.swiftDemoAppPush/NotificationService.swiftSources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swiftTests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swiftSources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swiftSources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swiftDemoApp/StreamChat/Components/DeliveredMessages/DemoChatMessageDeliveryStatusView.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swiftSources/StreamChat/WebSocketClient/Events/MessageEvents.swiftSources/StreamChat/ChatClientFactory.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swiftSources/StreamChat/WebSocketClient/Events/EventPayload.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swiftDemoApp/StreamChat/Components/DemoChatMessageActionsVC.swiftTests/StreamChatTests/StateLayer/Chat_Tests.swiftTestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swiftDemoApp/StreamChat/StreamChatWrapper+DemoApp.swiftTests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swiftSources/StreamChat/Controllers/ChannelListController/ChannelListController.swiftTests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swiftSources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swiftDemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swiftTestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swiftSources/StreamChat/WebSocketClient/Events/EventType.swiftTestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/ChannelDeliveryTracker_Mock.swiftSources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swiftTests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swiftDemoApp/Screens/AppConfigViewController/AppConfigViewController.swiftTests/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.swiftSources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swiftSources/StreamChat/Models/Channel.swiftSources/StreamChat/Workers/CurrentUserUpdater.swiftSources/StreamChat/APIClient/Endpoints/EndpointPath.swiftSources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swiftSources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swiftSources/StreamChat/ChatClient+Environment.swiftSources/StreamChat/Database/DTOs/ChannelReadDTO.swiftSources/StreamChat/APIClient/ChatRemoteNotificationHandler.swiftSources/StreamChat/Models/DeliveredMessageInfo.swiftSources/StreamChat/ChatClient.swiftSources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swiftSources/StreamChat/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware.swiftSources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swiftSources/StreamChat/WebSocketClient/Events/MessageEvents.swiftSources/StreamChat/ChatClientFactory.swiftSources/StreamChat/WebSocketClient/Events/EventPayload.swiftSources/StreamChat/Controllers/ChannelListController/ChannelListController.swiftSources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swiftSources/StreamChat/WebSocketClient/Events/EventType.swiftSources/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.swiftTests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swiftTests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swiftTests/StreamChatTests/Models/ChatChannel_Tests.swiftTests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swiftTests/StreamChatTests/StateLayer/Chat_Tests.swiftTests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swiftTests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swiftTests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swiftTests/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.swiftTests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swiftTests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swiftTests/StreamChatTests/Models/ChatChannel_Tests.swiftTests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swiftTests/StreamChatTests/StateLayer/Chat_Tests.swiftTests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swiftTests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swiftTests/StreamChatTests/Workers/CurrentUserUpdater_Tests.swiftTests/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.swiftTests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swiftTests/StreamChatTests/Models/ChatChannel_Tests.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swiftTests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelDeliveredMiddleware_Tests.swiftTests/StreamChatTests/StateLayer/Chat_Tests.swiftTests/StreamChatTests/WebSocketClient/ChannelDeliveryTracker_Tests.swiftTests/StreamChatTests/Controllers/CurrentUserController/CurrentUserController_Tests.swiftTests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelDeliveredPayload_Tests.swiftStreamChat.xcodeproj/project.pbxprojTests/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.swiftTests/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)
        
          
                DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift
          
            Show resolved
            Hide resolved
        
              
          
                Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift
              
                Outdated
          
            Show resolved
            Hide resolved
        
              
          
                Sources/StreamChat/WebSocketClient/ChannelDeliveryTracker.swift
              
                Outdated
          
            Show resolved
            Hide resolved
        
              
          
                TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift
          
            Show resolved
            Hide resolved
        
              
          
                TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/CurrentUserUpdater_Mock.swift
          
            Show resolved
            Hide resolved
        
      
          SDK Performance
  | 
    
          
 Generated by 🚫 Danger  | 
    
There was a problem hiding this 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.
📒 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.swiftSources/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
userIdparameter, 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (6)
Sources/StreamChat/Models/ChannelRead.swift (1)
21-25: Consider documenting nil semantics for the new properties.The documentation for the new delivery properties could be more complete. For consistency with the existing
lastReadMessageIddocumentation (line 13), consider explaining whatnilvalues indicate (e.g., "never delivered", "delivery tracking unavailable", or "delivery feature not enabled").Apply this diff to enhance the documentation:
- /// The last time a message has been delivered to this user. + /// The last time a message has been delivered to this user. Nil means no messages have been delivered yet or delivery tracking is unavailable. public let lastDeliveredAt: Date? - /// The last message ID that has been delivered to this user. + /// The last message ID that has been delivered to this user. Nil means no messages have been delivered yet or delivery tracking is unavailable. public let lastDeliveredMessageId: MessageId?Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (5)
31-33: Include deliveryEventsEnabled in the documented channel criterion.
canBeMarkedAsDeliveredalso checksconfig.deliveryEventsEnabled, but the doc bullet only mentions mute/hidden. Add it for clarity.Doc tweak:
-/// - The channel can be marked as delivered (not muted, not hidden) +/// - The channel can be marked as delivered (`config.deliveryEventsEnabled` is true, not muted, not hidden)Also applies to: 85-88
72-74: Avoid intermediate array frommapin mute check.Use
contains(where:)to skip allocation and be clearer.- if currentUser.mutedUsers.map(\.id).contains(message.author.id) { + if currentUser.mutedUsers.contains(where: { $0.id == message.author.id }) { return false }
76-79: Add parentheses to clarify??precedence.Parentheses avoid precedence ambiguity and improve readability.
- return message.createdAt > userRead.lastReadAt - && message.createdAt > userRead.lastDeliveredAt ?? .distantPast + return message.createdAt > userRead.lastReadAt + && message.createdAt > (userRead.lastDeliveredAt ?? .distantPast)
85-89: Make the computed property explicitly private.Top-level
private extensionsemantics can be confusing. Make the member’s access explicit.-private extension ChatChannel { - var canBeMarkedAsDelivered: Bool { +private extension ChatChannel { + private var canBeMarkedAsDelivered: Bool { config.deliveryEventsEnabled && !isMuted && !isHidden } }
46-82: Boundary tests: equality and missing read/delivery timestamps.Please ensure tests cover:
- createdAt equal to
 lastReadAtandlastDeliveredAt(should not deliver with strict>).- missing read state (current behavior returns
 true).- thread replies with/without
 showReplyInChannel.- muted/hidden channel and
 deliveryEventsEnabled = false.Based on learnings
📜 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.
📒 Files selected for processing (3)
Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift(2 hunks)Sources/StreamChat/Models/ChannelRead.swift(2 hunks)Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift
 
🧰 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/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftSources/StreamChat/Models/ChannelRead.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift
📄 CodeRabbit inference engine (AGENTS.md)
When altering public API, update inline documentation comments in source
Files:
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftSources/StreamChat/Models/ChannelRead.swift
🧠 Learnings (2)
📚 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:
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.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 Sources/{StreamChat,StreamChatUI}/**/*.swift : When altering public API, update inline documentation comments in source
Applied to files:
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftSources/StreamChat/Models/ChannelRead.swift
🧬 Code graph analysis (1)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (3)
TestTools/StreamChatTestTools/Mocks/StreamChat/MessageDeliveryCriteriaValidator_Mock.swift (1)
canMarkMessageAsDelivered(16-27)TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift (1)
canMarkMessageAsDelivered(16-27)Sources/StreamChat/Models/Channel.swift (1)
read(390-392)
🔇 Additional comments (2)
Sources/StreamChat/Models/ChannelRead.swift (1)
27-41: LGTM!The initializer correctly incorporates the new delivery parameters and assigns them to the corresponding properties. The parameter ordering maintains backward compatibility.
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)
9-27: Confirm intended access level for protocol/struct.Both
MessageDeliveryCriteriaValidatingandMessageDeliveryCriteriaValidatorare internal; is that intentional? If these are part of the public API (per PR scope), mark the typespublicto align with their usage; otherwise keep internal. If making public, update docs per guidelines. As per coding guidelines.Option if public:
-protocol MessageDeliveryCriteriaValidating { +public protocol MessageDeliveryCriteriaValidating { @@ -struct MessageDeliveryCriteriaValidator: MessageDeliveryCriteriaValidating { +public struct MessageDeliveryCriteriaValidator: MessageDeliveryCriteriaValidating { - init() {} + public init() {} @@ - func canMarkMessageAsDelivered( + public func canMarkMessageAsDelivered(
        
          
                Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
              
                Outdated
          
            Show resolved
            Hide resolved
        
      a8f4c8b    to
    60d24a8      
    Compare
  
    There was a problem hiding this 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
♻️ Duplicate comments (1)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)
30-40: Documentation inconsistency with read state handling.The documentation states "The current user has a read state in the channel" as a requirement (line 37), but the implementation at line 81 returns
truewhen the read state is missing. This contradicts the documented criteria. Either update the documentation to reflect that a missing read state is treated as allowed, or enforce the requirement in the implementation.
🧹 Nitpick comments (1)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (1)
46-82: Validation logic is comprehensive and sound.The implementation correctly validates all the key criteria for message delivery eligibility. The timestamp comparisons and privacy checks are well-structured.
Optional: Line 72 could use
contains(where:)instead ofmap(_:).contains(_:)to avoid creating an intermediate array:-if currentUser.mutedUsers.map(\.id).contains(message.author.id) { +if currentUser.mutedUsers.contains(where: { $0.id == message.author.id }) { return false }
📜 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.
📒 Files selected for processing (2)
Sources/StreamChat/Models/Channel.swift(1 hunks)Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Sources/StreamChat/Models/Channel.swift
 
🧰 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/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
Sources/{StreamChat,StreamChatUI}/**/*.swift
📄 CodeRabbit inference engine (AGENTS.md)
When altering public API, update inline documentation comments in source
Files:
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
🧠 Learnings (2)
📚 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:
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.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 Sources/{StreamChat,StreamChatUI}/**/*.swift : When altering public API, update inline documentation comments in source
Applied to files:
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift
🧬 Code graph analysis (1)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (3)
TestTools/StreamChatTestTools/Mocks/StreamChat/MessageDeliveryCriteriaValidator_Mock.swift (1)
canMarkMessageAsDelivered(16-27)TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift (1)
canMarkMessageAsDelivered(16-27)Sources/StreamChat/Models/Channel.swift (1)
read(390-392)
🔇 Additional comments (2)
Sources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swift (2)
9-22: Protocol definition is clean and well-documented.The protocol provides a clear contract for delivery validation. Access control is now consistent with the struct implementation.
85-89: Clean encapsulation of channel-level delivery criteria.The private extension appropriately groups the channel state checks, making the validator logic more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me ✅  Left few small comments.
"Should we provide the double grey checkmark as part of the UI SDK if deliveries are enabled?"
Yes, we already have it, right? It just needs to consider the delivery events from now on.
        
          
                Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelDeliveredPayload.swift
          
            Show resolved
            Hide resolved
        
      There was a problem hiding this 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
truewhenchannel.read(for: currentUser.id)isnil(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
## StreamChatUIis warranted. ,Tests/StreamChatUITests/SnapshotTests/ChatChannelList/ChatChannelListItemView_Tests.swift (2)
225-269: Clarify or simplify thelastDeliveredAtsetup in this test.This test sets
lastDeliveredAtto 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 togglereadEventsEnabled.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: truelastDeliveredAtset to a time before the message (e.g., 90) or no matchinglastDeliveredMessageIdSources/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.
⛔ Files ignored due to path filters (10)
Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusCheckmarkView_Tests/test_appearance_delivered.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusCheckmarkView_Tests/test_appearance_delivered.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInDirectMessagesChannel.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInDirectMessagesChannel.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInGroupChannel.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/__Snapshots__/ChatMessageDeliveryStatusView_Tests/test_appearance_whenMessageIsDeliveredInGroupChannel.default-light.pngis 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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftDemoApp/Shared/DemoUsers.swiftSources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftSources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swiftTests/StreamChatTests/Models/ChatMessage_Tests.swiftSources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swiftSources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftSources/StreamChat/Models/ChatMessage.swiftSources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swiftSources/StreamChat/ChatClient.swiftSources/StreamChat/Controllers/ChannelListController/ChannelListController.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swiftTests/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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftTests/StreamChatTests/Models/ChatMessage_Tests.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swiftTests/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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftTests/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.swiftSources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver.swiftSources/StreamChatUI/ChatChannelList/ChatChannelListItemView.swiftSources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftSources/StreamChat/Models/ChatMessage.swiftSources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swiftSources/StreamChat/ChatClient.swiftSources/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.swiftTests/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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftDemoApp/Shared/DemoUsers.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftTests/StreamChatTests/Models/ChatMessage_Tests.swiftSources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftStreamChat.xcodeproj/project.pbxprojCHANGELOG.mdTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swiftTests/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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftTests/StreamChatTests/Models/ChatMessage_Tests.swiftStreamChat.xcodeproj/project.pbxprojTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swiftTests/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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftTests/StreamChatTests/Models/ChatMessage_Tests.swiftStreamChat.xcodeproj/project.pbxprojTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swiftTests/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.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView_Tests.swiftTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageLayoutOptionsResolver_Tests.swiftTests/StreamChatTests/Models/ChatMessage_Tests.swiftStreamChat.xcodeproj/project.pbxprojTests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView_Tests.swiftTests/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.swiftCHANGELOG.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.swiftSources/StreamChat/Models/MessageDelivery/MessageDeliveryCriteriaValidator.swiftSources/StreamChat/Models/ChatMessage.swiftSources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swiftStreamChat.xcodeproj/project.pbxprojCHANGELOG.mdTests/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.frankfurtC1toDemoApiKeys.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
lastDeliveredAtafter the message creation time anddeliveryEventsEnabled: 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
deliveryEventsEnabledconfiguration flag, following the same pattern as read events.Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusCheckmarkView.swift (1)
45-45: LGTM!Grouping
.deliveredwith.readfor 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
deliveryEventsEnabledconfiguration:
- 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
.deliveredstatus when they have been delivered but not read. The test setup properly creates channel reads withlastDeliveredAtandlastDeliveredMessageIdto 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
currentUserUpdateranddeliveryCriteriaValidatorare correctly initialized via environment builders, enabling dependency injection for testing while maintaining the same pattern used by the existingworkerproperty.
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
currentUseris nil and the empty deliveries check are good defensive practices.
187-189: LGTM! Integration points correctly trigger automatic delivery marking.The calls to
markChannelsAsDeliveredIfNeededare appropriately placed after successful channel fetches in bothloadNextChannelsandupdateChannelList, 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
currentUserUpdateranddeliveryCriteriaValidatormocks, following the established patterns for dependency injection. The cleanup intearDownensures 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
markChannelsAsDeliveredis called with correct messages after successful channel updates and that only messages meeting delivery criteria are marked. BothsynchronizeandloadNextChannelspaths are well covered.The implementation already handles edge cases gracefully: nil currentUser is guarded against with an early return, and errors from
markMessagesAsDeliveredare 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.
        
          
                Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageDeliveryStatusView.swift
          
            Show resolved
            Hide resolved
        
      There was a problem hiding this 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
logis 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.
📒 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.swiftTests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swiftSources/StreamChat/Controllers/ChannelListController/ChannelListController.swiftTests/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.swiftSources/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.swiftTests/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.swiftTests/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.swiftTests/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.swiftTests/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.swiftTests/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.swiftTests/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.swiftTests/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:)returnsnilfor 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: cidto 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
CurrentUserUpdaterinteractions and validate the delivered message details.
2113-2148: LGTM! Proper environment wiring for new dependencies.The
TestEnvironmentcorrectly extends the environment with builders forcurrentUserUpdateranddeliveryCriteriaValidator, enabling mock injection for testing delivery marking behavior. The pattern matches the existingchannelListUpdatersetup.
1774-1850: Test data updated for new delivery fields.The channel read payloads are properly extended with
lastDeliveredAtandlastDeliveredMessageIdfields to match the updatedChannelReadmodel. Usingnilvalues 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
currentUserUpdateranddeliveryCriteriaValidatorproperties are correctly initialized via environment builders, enabling testability through mock injection.
188-188: LGTM! Delivery marking properly integrated after channel updates.The calls to
markChannelsAsDeliveredIfNeededare correctly placed after successful channel list updates in bothloadNextChannelsandupdateChannelList(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
deliveryCriteriaValidatoris correctly initialized from the environment builder during controller construction.
Co-authored-by: Stream Bot <[email protected]>
There was a problem hiding this 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.
⛔ Files ignored due to path filters (6)
Tests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsDisabled.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-dark.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/ChatChannelList/__Snapshots__/ChatChannelListItemView_Tests/test_appearance_deliveredPreviewMessageFromCurrentUser_deliveryEventsEnabled.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/Gallery/__Snapshots__/GalleryVC_Tests/test_snapshotWithMessageTimestampOlderDate.default-light.pngis excluded by!**/*.pngTests/StreamChatUITests/SnapshotTests/Gallery/__Snapshots__/GalleryVC_Tests/test_snapshotWithMessageTimestampToday.default-light.pngis 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
 
          SDK Size
  | 
    
          StreamChat XCSize
 Show 45 more objects
  | 
    
          StreamChatUI XCSize
  | 
    
          
 | 
    
| 
               | 
          ||
| guard !deliveredMessages.isEmpty else { return } | ||
| 
               | 
          ||
| self?.currentUserUpdater.markMessagesAsDelivered(deliveredMessages) { [weak self] error in | 
There was a problem hiding this comment.
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.



🔗 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:
ChatRemoteNotificationHandler.markMessageAsDelivered(deliveries:)ChatChannel.reads(message:)andChatChannel.deliveredReads(message:)ChatChannelRead.lastDeliveredAtChatChannelRead.lastDeliveredMessageIdNew in UI SDK:
Demo App:
TODO:
🛠 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:
ChannelDeliveryTrackerto respond to the following events:message.new,message.delivered,message.readwhich internally uses the.ChannelDeliveryTrackerto make sure requests are throttled as wellTip
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
ChannelDeliveryTrackeris the central component that manages pending channel deliveries and implements throttling to prevent server spam.Key Features:
[ChannelId: MessageId])broadcastLatest: trueto make sure the last call is always executed.Rules to mark a message as delivered:
ChannelDeliveredMiddleware
The
ChannelDeliveredMiddlewareprocesses WebSocket events and automatically triggers delivery marking for new messages.Event Handling:
ChannelListController
The
ChannelListControllerautomatically marks channels as delivered during synchronisation operations.Integration Points:
synchronize()is called, channels are marked as delivered instantly after a successful fetch.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
ChatRemoteNotificationHandlerexposes 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
ChatRemoteNotificationHandlerdoes not use theChannelDeliveryTrackerinternally since theNotificationServiceis 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
🎨 Showcase
demo.mov
🧪 Manual Testing Notes
Message is marked delivered when the user receives a new message:
Message is marked delivered when the user receives a push notification:
Message is marked delivered when the user loads the channel list:
Message is NOT marked delivered when the user is: (Follow mark read rules)
☑️ Contributor Checklist
docs-contentrepoSummary by CodeRabbit
New Features
Improvements