Skip to content

fix(llc): keep a channel usable after the user hides it or is removed from it - #3128

Draft
xsahil03x wants to merge 27 commits into
masterfrom
sahil/flu-1085-open-channel-page-crashes-after-its-channel-is-disposed-user
Draft

xsahil03x wants to merge 27 commits into
masterfrom
sahil/flu-1085-open-channel-page-crashes-after-its-channel-is-disposed-user

Conversation

@xsahil03x

@xsahil03x xsahil03x commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

An open channel page crashed when its channel was hidden or the current user was removed from it. Blocking someone triggers the hide too: the backend hides 1-on-1 channels with the blocked user. The client disposed the Channel on channel.hidden and on the current user's member.removed / notification.removed_from_channel. The page kept the disposed instance and threw on its next read: the header's memberCount when going back, and channel.config when long-pressing a message.

Fixes FLU-1085.

Hiding the open channel from a second client signed in as the same user (sample app, Android):

Before After After: message actions After: back to the list

What changed (stream_chat)

  • The channel stays alive. Hiding or removal now only leaves the channel out of StreamChatClient.state.channels, without disposing it. client.state.channels and channelsStream contain exactly what they did before. channel.deleted still disposes.
  • One instance per channel. client.channel() and queryChannels look up every live channel, so they return the instance a page already holds instead of creating a second one.
  • The way back. watch(), a queryChannels result or channel.visible puts the channel back in client.state.channels. A paginated query() doesn't.
  • Accurate state while it's retained. Channel.hidden now follows channel.hidden and channel.visible, and the current user's membership is cleared when they're removed.
  • ChannelState.copyWith(membership: null) now clears the membership, as copyWith(draft: null) already did. Leaving membership out still keeps it, and channel updates without a membership still keep the current one.

The iOS, Android and JS SDKs don't dispose a channel on hide or removal either. They update its hidden flag and membership and keep the screen alive.

How it was verified

  • Unit tests: 25 new stream_chat tests, through client.handleEvent, client.state.channels and client.channel(). They cover hide, removal, deletion of a hidden channel, the way back, one instance per cid, membership, the hidden flag and the copyWith contract. Each of 19 guards was broken on purpose and a test caught it.
  • Suites: stream_chat (2,052) and stream_chat_flutter_core (398) pass. stream_chat_flutter passes apart from the local macOS goldens.
  • Emulator (Android, sample app):
    1. With a channel open, I hid it from a second client signed in as the same user.
    2. Long-pressing a message still opens the message actions.
    3. Going back doesn't crash, and the channel is gone from the list.
    4. Showing it again puts it back in the list, and it opens normally.
  • e2e: these tests are un-skipped and run in this PR's E2E workflow:
    • "app does not crash when the user goes back after being removed from the open channel";
    • "user blocks the participant in a direct message channel";
    • "direct message channel disappears when the user blocks the participant".

🤖 Generated with Claude Code

https://claude.ai/code/session_012vTgFwqhUc2wKCWrw635yC

testableapple and others added 22 commits October 23, 2026 14:55
* test(repo): assert the token date and signature policy (FLU-1083)

The SDK refreshes the token on code 40 only. A token used before its
issue date (42) is reconnected as-is until the server's clock catches
up, and a bad signature (43) is not retried with a new token. The four
tests now assert that instead of a refresh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(repo): tighten the token date and signature tests (FLU-1083)

Keep the invalid window open past the refresh so the refreshed token is
always rejected, count token requests through an env assertion, and fix
the step and file comments to match what the tests assert.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(repo): drop the token signature after login test (FLU-1083)

A signing secret doesn't go bad mid-session, and the test only timed the
SDK's reconnect backoff.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(repo): keep the login error across the loading screen (FLU-1083)

The chooser page is replaced while connecting, so an error it stored
itself was gone by the time the login failed. The auth state now carries
it, and the rebuilt page shows it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vTgFwqhUc2wKCWrw635yC

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(ui): keep the footer of a sent message before a failed one (FLU-1090)

A message that failed now starts its own group, so the sent message
before it keeps its timestamp and delivery status. Consecutive failed
messages still group together.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(ui): describe every failed message in the changelog (FLU-1090)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vTgFwqhUc2wKCWrw635yC

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… from it (FLU-1085)

Hiding a channel, or the current user being removed from it, disposed the
Channel, so a page still holding it crashed on its next read. The channel now
stays alive and only leaves StreamChatClient.state.channels; a later watch,
query or channel.visible brings it back. Channel.hidden follows the hidden and
visible events, and the current user's membership is cleared on removal.

ChannelState.copyWith(membership: null) now clears the membership.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vTgFwqhUc2wKCWrw635yC
@coderabbitai

coderabbitai Bot commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

Base automatically changed from test/e2e-native-parity to master October 27, 2026 17:37
xsahil03x and others added 5 commits October 27, 2026 18:39
Correct the comment on when a hidden channel comes back, drop a test that
guarded nothing, mention plain hiding in the UI changelog, and wrap long lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vTgFwqhUc2wKCWrw635yC
…-channel-page-crashes-after-its-channel-is-disposed-user

# Conflicts:
#	e2e_app/integration_test/channel_actions_test.dart
#	e2e_app/integration_test/message_actions_test.dart
#	e2e_app/integration_test/moderation_test.dart
#	packages/stream_chat_flutter/CHANGELOG.md
…-channel-page-crashes-after-its-channel-is-disposed-user

# Conflicts:
#	packages/stream_chat/CHANGELOG.md
#	packages/stream_chat_flutter/CHANGELOG.md
…-channel-page-crashes-after-its-channel-is-disposed-user

# Conflicts:
#	packages/stream_chat_flutter/CHANGELOG.md
…-channel-page-crashes-after-its-channel-is-disposed-user

# Conflicts:
#	packages/stream_chat/CHANGELOG.md
#	packages/stream_chat_flutter/CHANGELOG.md

This branch has not been deployed

No deployments
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