Skip to content

chore(llc): tolerate duplicate-keyed inputs in SortedListX.merge#2660

Merged
xsahil03x merged 2 commits into
masterfrom
fix/llc-merge-tolerate-receiver-dupes
May 19, 2026
Merged

chore(llc): tolerate duplicate-keyed inputs in SortedListX.merge#2660
xsahil03x merged 2 commits into
masterfrom
fix/llc-merge-tolerate-receiver-dupes

Conversation

@xsahil03x
Copy link
Copy Markdown
Member

@xsahil03x xsahil03x commented May 18, 2026

Summary

  • ChannelClientState.updateChannelState calls messages.merge(..., compare: _sortByCreatedAt), which previously asserted both inputs had unique keys. Pull-to-refresh with offline storage enabled could surface a legacy in-memory channel state with a duplicated message id and trip that assertion in queryChannelsOfflineupdateChannelState (list_extensions.dart:123).
  • Drop the unique-key and sortedness asserts (sortedness is now a documented contract — behavior is undefined if violated) and remove the non-overlapping append/prepend fast paths that could emit duplicates when keys collide despite non-overlapping compare ranges.
  • Everything now routes through the two-pointer merge, which already handles all these cases correctly: keys present in other take precedence over the receiver, and stray receiver duplicates are propagated rather than crashing.

Repro that motivated the fix

⚠️ 📡 Error querying channels offline
'package:stream_chat/src/core/util/list_extensions.dart': Failed assertion: line 123 pos 9:
'_hasUniqueKeys(this, key)': merge: receiver must have unique keys when `compare` is provided
…
#3 ChannelClientState.updateChannelState (package:stream_chat/.../channel.dart:3371:34)
#4 StreamChatClient._mapChannelStateToChannel (package:stream_chat/.../client.dart:837:24)
#5 StreamChatClient.queryChannelsOffline (package:stream_chat/.../client.dart:824:25)
#6 StreamChatClient.queryChannels (package:stream_chat/.../client.dart:670:25)
#7 StreamChannelListController.doInitialLoad (package:stream_chat_flutter_core/.../stream_channel_list_controller.dart:134:7)
#8 RefreshIndicatorState._show.… (package:flutter/.../refresh_indicator.dart:602:38)

Surfaces only when offline storage is enabled and a stale in-memory channel state contains a duplicate id.

Behaviour after the fix (no callers in channel.dart need to change)

Scenario Before After
Receiver has dup, key also in other Asserted ❌ Receiver copies dropped, other wins
Receiver has dup, key not in other Asserted ❌ Propagated (sorted order preserved)
other has dup Asserted ❌ Propagated (sorted order preserved)
Non-overlapping append (livestream) Concatenate fast path Two-pointer (same result)
Non-overlapping prepend (pagination) Concatenate fast path Two-pointer (same result)

Test plan

  • dart test packages/stream_chat/test/src/core/util/list_extensions_test.dart — 33/33 pass (added 3 new duplicate-tolerance cases, dropped 4 assert-trip cases)
  • dart test packages/stream_chat/test/src/client/channel_test.dart — 276/276 pass
  • dart test (full stream_chat suite) — 1225/1225 pass (2 pre-existing skipped)
  • dart analyze clean on changed files
  • Manual verification: trigger pull-to-refresh on a channel list with offline storage enabled and a stale in-memory state

Made with Cursor

Summary by CodeRabbit

  • Improvements

    • Simplified list merging logic to gracefully handle duplicate values in input data.
  • Tests

    • Expanded test coverage for merge operations handling edge cases with duplicates.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba7dbbd5-dc6f-4d73-a0b3-39ffa9d5b8bc

📥 Commits

Reviewing files that changed from the base of the PR and between 06aff57 and dfe0a69.

📒 Files selected for processing (2)
  • packages/stream_chat/lib/src/core/util/list_extensions.dart
  • packages/stream_chat/test/src/core/util/list_extensions_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/stream_chat/test/src/core/util/list_extensions_test.dart
  • packages/stream_chat/lib/src/core/util/list_extensions.dart

📝 Walkthrough

Walkthrough

SortedListX.merge is simplified to always use its two-pointer algorithm when a comparator is provided. Debug assertions on sortedness and key uniqueness are removed, non-overlapping fast paths are eliminated, and the method now documents and tolerates duplicate keys within inputs while giving precedence to the other list when keys overlap.

Changes

SortedListX.merge duplicate-key tolerance

Layer / File(s) Summary
Contract and documentation updates
packages/stream_chat/lib/src/core/util/list_extensions.dart
File-level migration comments and the merge method's doc comment are updated to remove precondition details around uniqueness, document that duplicates within each input are tolerated, and describe output precedence when keys overlap.
Core implementation simplification
packages/stream_chat/lib/src/core/util/list_extensions.dart
Debug-only _isSorted and _hasUniqueKeys assertions are removed from the compare != null branch, non-overlapping append and prepend fast paths are deleted, and the method always delegates to _mergeSortedTwoPointer. Private helper functions _isSorted and _hasUniqueKeys are deleted.
Test coverage updates
packages/stream_chat/test/src/core/util/list_extensions_test.dart
Test naming is updated to reflect the two-pointer merge path; pagination prepend test formatting is adjusted; prior assertions that unsorted or duplicate-keyed inputs should throw are replaced with new tests confirming duplicate-key tolerance and precedence behavior when the same key exists in both inputs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • Brazol

Poem

🐰 Two pointers merge with grace,
No duplicates left to trace,
Preconditions fade away,
Simpler code to build today,
Sorted lists now find their place! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title states 'chore(llc): tolerate duplicate-keyed inputs in SortedListX.merge', but the PR objectives clarify this is labeled a 'fix' addressing a runtime assertion failure in ChannelClientState.updateChannelState during offline pull-to-refresh. Consider whether 'chore' or 'fix' is more accurate. The PR fixes a bug (duplicate-key assertions crashing), not a maintenance task. Recommend updating the title prefix from 'chore' to 'fix' to align with conventional commit semantics and PR intent.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/llc-merge-tolerate-receiver-dupes

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

❤️ Share

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

`ChannelClientState.updateChannelState` calls `messages.merge(...,
compare: _sortByCreatedAt)`, which previously asserted both inputs had
unique keys. Pull-to-refresh with offline storage on could surface a
legacy in-memory state with a duplicated message id and trip that
assertion in `queryChannelsOffline` → `updateChannelState`.

Drop the unique-key and sortedness asserts (sortedness is now a
documented contract — behavior is undefined if violated) and remove the
non-overlapping append/prepend fast paths that could emit duplicates
when keys collide despite non-overlapping compare ranges. Everything
routes through the two-pointer merge, which already handles all these
cases correctly: keys present in `other` take precedence over the
receiver, and stray receiver duplicates are propagated rather than
crashing.

Co-authored-by: Cursor <cursoragent@cursor.com>
@xsahil03x xsahil03x force-pushed the fix/llc-merge-tolerate-receiver-dupes branch from 06aff57 to dfe0a69 Compare May 18, 2026 16:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/stream_chat/lib/src/core/util/list_extensions.dart (1)

4-6: ⚡ Quick win

Narrow the migration-equivalence claim for duplicate-key inputs.

Line 6 currently says both implementations produce the same result for sorted inputs, but this file now explicitly tolerates/propgates some duplicate-key cases. That can diverge from keyed-map merge behavior and may mislead the later import swap.

Suggested doc tweak
-// keyed-map-merge-and-sort. Both produce the same result for sorted inputs.
+// keyed-map-merge-and-sort. They produce the same result for sorted inputs
+// when keys are unique within each input.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stream_chat/lib/src/core/util/list_extensions.dart` around lines 4 -
6, Update the top-of-file comment that compares the two-pointer merge to the
keyed-map-merge-and-sort to explicitly narrow the equivalence: state that both
produce the same result for sorted inputs that do not contain conflicting
duplicate keys (or when duplicate-key handling is equivalent), and note that
this file's two-pointer merge may tolerate/propagate certain duplicate-key cases
while the keyed-map approach deduplicates/overwrites; adjust the sentence
referencing `merge` and "keyed-map-merge-and-sort" accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/stream_chat/lib/src/core/util/list_extensions.dart`:
- Around line 4-6: Update the top-of-file comment that compares the two-pointer
merge to the keyed-map-merge-and-sort to explicitly narrow the equivalence:
state that both produce the same result for sorted inputs that do not contain
conflicting duplicate keys (or when duplicate-key handling is equivalent), and
note that this file's two-pointer merge may tolerate/propagate certain
duplicate-key cases while the keyed-map approach deduplicates/overwrites; adjust
the sentence referencing `merge` and "keyed-map-merge-and-sort" accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f718eaa2-a867-44fa-88f7-667f0f47c167

📥 Commits

Reviewing files that changed from the base of the PR and between d9d4ff4 and 06aff57.

📒 Files selected for processing (3)
  • packages/stream_chat/CHANGELOG.md
  • packages/stream_chat/lib/src/core/util/list_extensions.dart
  • packages/stream_chat/test/src/core/util/list_extensions_test.dart

@xsahil03x xsahil03x changed the title fix(llc): tolerate duplicate-keyed inputs in SortedListX.merge chore(llc): tolerate duplicate-keyed inputs in SortedListX.merge May 18, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.27%. Comparing base (5b8209b) to head (9a878b2).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2660      +/-   ##
==========================================
- Coverage   65.29%   65.27%   -0.02%     
==========================================
  Files         423      423              
  Lines       26635    26622      -13     
==========================================
- Hits        17390    17377      -13     
  Misses       9245     9245              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xsahil03x xsahil03x merged commit d07f633 into master May 19, 2026
18 checks passed
@xsahil03x xsahil03x deleted the fix/llc-merge-tolerate-receiver-dupes branch May 19, 2026 19:49
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