Skip to content

fix: channel orchestrator fixes - #1815

Merged
isekovanic merged 8 commits into
feat/channel-orchestrator-to-channel-managerfrom
fix/channel-orchestrator-fixes
Aug 11, 2026
Merged

fix: channel orchestrator fixes#1815
isekovanic merged 8 commits into
feat/channel-orchestrator-to-channel-managerfrom
fix/channel-orchestrator-fixes

Conversation

@isekovanic

@isekovanic isekovanic commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

CLA

  • I have signed the Stream CLA (required).
  • Code changes are tested

Description of the changes, What, Why and How?

Follow-up fixes on top of the ChannelPaginator/orchestrator migration, all found via on-device testing of the RN SDK. They address reconnect and cold-start scenarios where the channel list would blank, collapse, or stop refreshing.

1. fix: properly respect reset and keepPreviousItems
A reload() (pull-to-refresh / reconnect) refetched the current page instead of page 1: the query shape (carrying offset/cursor) was derived before the reset restored the initial offset. reset and keepPreviousItems were also conflated, so a keepPreviousItems refresh paginated instead of resetting.
To resolve this, we restore the initial offset/cursor before deriving the query shape on reset: 'yes' and decouple reset from keepPreviousItems so a refresh can keep items visible without paginating.

2. fix: pending item flush update using intermediate state
Follow-up to #1, a keepPreviousItems refresh cleared the item index, so any item ingested during the refresh (i.e an offline-send replay's message.new on reconnect) rebuilt the list from empty and collapsed it to just that item.
To resolve this, we keep A keepPreviousItems refresh is now fully non-destructive so it surfaces loading without clearing the index/intervals, so concurrent ingests merge into the existing list. The index is cleared only on a genuine reset (reset without keepPreviousItems).

3. fix: cold start double loading state
On cold start, if a channel changed while the app was fully closed, the list flashed loading state into cached channels into a second loading state. The deferred post sync requery went through the first page reset path and repreloaded from the offline DB, but the sync had invalidated that cache, so the preload returned nothing and blanked the list.
To resolve this, we run the post sync re query as a non-destructive refresh (keepPreviousItems) so the cached channels stay visible while the fresh page loads.

4. fix: cold start with pending tasks wiping cached channel list
Cold start with pending offline sends collapsed the list to just the channel(s) with pending sends until the sends completed. The cold-start preload set the displayed items but never populated the item index, so the pending-send replay's message.new ingested into an empty index and rebuilt the list from scratch.
To fix this, we seed the preload through the path that populates the index (so a concurrent ingest merges instead of collapsing), and mark loading complete so the deferred query isn't rejected.

5. fix: race condition when the sync process finishes faster than preload
On cold start, if the offline sync finished during the offline-DB preload (it runs independently, off connection.changed), the deferred "run the real query after sync" callback was registered after the sync had already drained its callback queue — so it never fired. Channels showed from cache but were never queried with watch, so they stayed unwatched (list reordered on member-level events, but per-channel state froze).
To fix this, we make sure that after the preload, we recheck the sync status; if the sync already finished, run the real (watching) query directly instead of scheduling a callback that will never fire. Mirrors the legacy ChannelManager a bit more closely as this was something left out during the migration.

**6. fix: stop boosting channels on new messages so pins stay on top **
A new message in an unpinned channel jumped it above the pinned channels. updateLists boosted the messaged channel on message.new/notification.message_new and the boost aware comparator floats any boosted item above any non-boosted one before the sort is ever consulted and so the messaged channel jumped above the pinned (non-boosted) ones. The boost was a workaround from before 60566820 taught ingestItem to relocate an in-place mutated channel by its changed sort value; now that a message bumps last_message_at and the sort floats the channel to the top of its partition on its own, boosting it is redundant and, for a pinned/partitioned sort, wrong.
To fix this, we stop boosting on message.new / notification.message_new and let the sort relocate the messaged channel (to the top of its partition, below pinned). Boosting is kept for notification.added_to_channel / channel.visible, where the channel's last_message_at is stale and the sort can't surface it on its own. The boost primitive itself is untouched and stays available for integrators to selectively boost specific channels. I'm not sure if this was the right direction to go in, however boosting on new messages has the potential to completely break the contract of what the BE returns. We should honestly probably have no default boosting at all (as far as I understand it it's meant to be a temporary sorting mechanism that allows for example integrators to put a channel higher up in case we want to do this for certain channels, like for example if a user marks a channel as "notifiable" or something that would jump it to the top in case it was deeper down)

Changelog

  • Fixed reload() (pull-to-refresh / reconnect) re-fetching the current page instead of resetting to the first page in BasePaginator, and decoupled reset from keepPreviousItems.
  • Fixed the channel list collapsing to a single channel when an item is ingested during a keepPreviousItems refresh (e.g. an offline-send replay on reconnect).
  • Fixed a second loading skeleton on cold start when a channel changed while the app was fully closed.
  • Fixed cold start with pending offline sends wiping the cached channel list until the sends completed.
  • Fixed a race where the offline sync finishing during the preload left channels unwatched (list reordered but per-channel state stopped updating).

…nnel-manager' into fix/channel-orchestrator-fixes

# Conflicts:
#	src/pagination/paginators/BasePaginator.ts
@isekovanic
isekovanic merged commit 60917e8 into feat/channel-orchestrator-to-channel-manager Aug 11, 2026
4 checks passed
@isekovanic
isekovanic deleted the fix/channel-orchestrator-fixes branch August 11, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants