Repository navigation
fix: start the newest interval with the first message in a channel loaded empty - #2007
Merged
Merged
Conversation
…aded empty A channel whose newest page was loaded empty has no interval and no known latest message time, so `isNewNewestMessage()` returned false and the first message went into the logical head. The list then never read as at head, and `jumpToTheLatestMessage()` failed while the server did not have the message yet.
isekovanic
requested review from
MartinCupela,
NikolaGoranNaumovski,
oliverlaz and
szuperaz
as code owners
November 9, 2026 12:13
MartinCupela
approved these changes
Nov 9, 2026
2 of 6 tasks
github-actions Bot
pushed a commit
that referenced
this pull request
Nov 9, 2026
## [10.0.1](v10.0.0...v10.0.1) (2026-11-09) ### Bug Fixes * start the newest interval with the first message in a channel loaded empty ([#2007](#2007)) ([d31d789](d31d789))
|
🎉 This PR is included in version 10.0.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
isekovanic
added a commit
to GetStream/stream-chat-react-native
that referenced
this pull request
Nov 9, 2026
…3953) Ticket: https://linear.app/stream/issue/REACT-1537 ## 🎯 Goal Make the SampleApp start new conversations the way the v10 "Starting a conversation" UI cookbook does, so the sample exercises the path integrators follow. It also bumps `stream-chat` to 10.0.1, which has the fix for the first-message error (GetStream/stream-chat-js#2007). | Commit | Change | | --- | --- | | [`39a2be3b0`](39a2be3b0) | SampleApp new-conversation screen follows the UI cookbook | | [`caec3a002`](caec3a002) | `stream-chat` bumped to 10.0.1 | Its new-conversation screen had a custom send button that composed the message, called `channel.watch()` and the raw `channel.sendMessage()`, then replaced the screen with `ChannelScreen`. The message never showed as sending, and the cookbook's flow was never run in the sample, which is how the first-message error fixed in GetStream/stream-chat-js#2007 went unnoticed. ## 🛠 Implementation details - `utils/startConversation.ts` holds the cookbook's two helpers. `startLocalConversation()` gets the channel from its members and places an empty first page while it's provisional, so the list shows its empty state. `createOnFirstSend()` is a `sendMessageRequest` handler: for a provisional channel it calls `Channel.watch()`, which creates the channel, and sends on the instance `watch()` resolves with. Every other channel sends through the default request. - `useChatClient` registers the handler in its existing `client.config.set()` call, where the client is created. - `NewDirectMessagingScreen` builds its draft with `startLocalConversation()`, uses the default `MessageComposer`, and passes `overrideOwnCapabilities` (`sendMessage` and `sendLinks`) only while the channel is provisional. It stays open after the first send, and `<Channel>` moves to the created channel, or to the one that superseded it. - Once the first message creates a conversation started on this screen, `NewConversationHeader` replaces the recipient picker with the channel header. An existing conversation keeps the picker, so its recipients can still be changed. - `ChannelHeader` is exported from `ChannelScreen` with two optional props: `onBack`, so the new-conversation screen still resets its user search on back, and `inSafeArea`, since that screen already applies the top inset. `ChannelScreen` passes neither and is unchanged. - `NewDirectMessagingSendButton` is removed. The other example apps don't start conversations from members (ExpoChatGPTSample creates its channels with an id), so they need no change. `stream-chat` goes from `^10.0.0` to `^10.0.1` in the SDK and every example app, as in #3951. 10.0.1 is 10.0.0 plus GetStream/stream-chat-js#2007: the first message in a channel whose newest page was loaded empty now starts the newest interval, so `jumpToTheLatestMessage()` no longer fails right after the first send of a new conversation. ## 🎨 UI Changes The new-conversation screen keeps the conversation open after the first message and swaps the recipient picker for the channel header. Before, it navigated to `ChannelScreen`. ## 🧪 Testing Tested in SampleApp on iOS (debug build, iPhone 17 Pro simulator), starting new conversations with throwaway users. Each first send went through the superseded-channel path from the cookbook report. - With `stream-chat` 10.0.0-rc.34, a new direct conversation sends from the same screen, and `stream-chat` emits `api:message:jumpToLatest:failed`, the error the cookbook report describes. - With a local build of GetStream/stream-chat-js#2007 (the code 10.0.1 ships), the same flow emits no notifications and the channel the screen moves to is at the newest messages. - A new group of three: the empty state shows before the first message and the attach button is hidden while the channel is provisional. After the send, the channel header replaces the picker ("3 members, 1 online"), back returns to the channel list, and "New Chat" opens empty again. The created channels show in the channel list, and `ChannelScreen`'s header is unchanged. - On 10.0.1: the full unit suite, `yarn test:typecheck`, SampleApp `tsc` and `yarn lint` pass. ## ☑️ Checklist - [x] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [x] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket: https://linear.app/stream/issue/REACT-1535
CLA
Description of the changes, What, Why and How?
When a channel's newest page is loaded empty, the message paginator creates no interval. The first message ingested after that, a local send or a
message.newevent, went into__logical_head__instead of starting the newest interval.isActiveIntervalAtHeadthen stayedfalseandjumpToTheLatestMessage()found no head interval, although the list held the newest message.isNewNewestMessage()only treated a message as the newest whenlatestMessageAtwas known. A channel that has never had a message has nolast_message_atand no loaded message, so it returnedfalse. A second message fixed the state, because it saw the first one'screated_atand started the newest interval with both.It now also treats a message as the newest when
latestMessageAtisnulland the newest page was loaded empty (itemsis empty andhasMoreHeadisfalse), soingestAsNewestInterval()starts the newest interval with it. A paginator that has loaded nothing yet keeps its behaviour, since itsitemsisundefined.In RN this showed as a "Failed to jump to the latest message" notification on the first message of the "Starting a conversation" UI cookbook. When
watch()supersedes the provisional channel,<Channel>swaps to the stored instance, whose only message sat in the logical head.useJumpToLatestOnOpenread the list as not at head and calledjumpToTheLatestMessage(), and the query came back empty because the server did not have the message yet.Testing
MessagePaginator.test.ts. A single ownsendingmessage after an empty newest page starts the newest interval andisActiveIntervalAtHeadistrue. It fails without the fix.prepareExternalQuery().placePage([]),placeExternalQueryPage([], pageSize)andplaceExternalNewestPage([], ...), for active and inactive channels and for received and sending messages. All of them put the first message in the logical head before the fix.api:message:jumpToLatest:failed, with it there are no notifications.yarn test,yarn run types,eslintandprettierpass.Changelog
jumpToTheLatestMessage()fail.