Skip to content

fix(llc, persistence): truncated channels sinking to the bottom of a last_updated sort - #2892

Merged
VelikovPetar merged 4 commits into
v9from
port/master-to-v9/FLU-689_fix_last_updated_calculation_logic
Aug 14, 2026
Merged

fix(llc, persistence): truncated channels sinking to the bottom of a last_updated sort#2892
VelikovPetar merged 4 commits into
v9from
port/master-to-v9/FLU-689_fix_last_updated_calculation_logic

Conversation

@VelikovPetar

Copy link
Copy Markdown
Contributor

Submit a pull request

Linear: FLU-689

Github Issue: #

CLA

  • I have signed the Stream CLA (required).
  • The code changes follow best practices
  • Code changes are tested (add some information if not applicable)

Description of the pull request

Backport of #2880 to v9 (merge commit 02ba6a239d8e0292729d9b6bd8e9d277e240fa57).

last_updated is not a wire field — every SDK derives it locally from last_message_at and created_at. Flutter derived it with a plain null-fallback:

DateTime get lastUpdatedAt => lastMessageAt ?? createdAt;

When a channel is truncated the server does not clear last_message_at — it sets it to a value older than created_at. Since the getter only falls back on null, it returned that stale date, so a truncated channel dropped to the bottom of a channel list sorted { last_updated: -1 } — below channels that had never been used at all. The default channel list sort is last_updated descending (StreamChannelListController, and the PredefinedFilter fallback), so this hits the default configuration on v9 exactly as it did on master.

Flutter was the only SDK without a guard here: Android's Channel.lastUpdated takes the max of the two dates, and iOS's ChannelDTO.defaultSortingAt has an explicit distantPast fallback naming the truncation case. This adopts Android's max() semantics, which covers any lastMessageAt < createdAt rather than just a sentinel value.

Changes

packages/stream_chatChannelModel.lastUpdatedAt now returns the later of the two dates. This is the value ChannelState.getComparableField(ChannelSortKey.lastUpdated) returns, so it fixes both the in-memory controller sort and the persistence comparator sort in one place. No behaviour change when last_message_at is newer or null.

packages/stream_chat_persistenceChannelDao.cids ranked channels with ORDER BY last_message_at DESC LIMIT 250. That result feeds StreamChatClient.sync(), which requests events missed while offline, so a truncated channel was the first to fall out of the 250-channel cap and silently stopped receiving missed events (only reproducible with more than 250 cached channels). It now orders by a CASE WHEN expression mirroring the Dart getter.

Worth noting for review: this is a selection cutoff, not a presentation order. The offline queryChannels read path (ChannelQueryDao.getChannels) has no ORDER BY/LIMIT and applies the caller's SortOrder comparator in Dart, so the sort passed to queryChannels was always respected.

created_at is non-nullable in the Drift schema, so the CASE WHEN always resolves. No schema change and no migration.

Port notes

Manual port rather than a clean cherry-pick, for two reasons — neither changes behaviour relative to master:

  • Formattingv9 formats at the default 80 columns (master uses 120), so the DAO helper and the if-case guard wrap across more lines. The if (x case final x? when ...) construct is idiomatic on v9 (156 uses in the LLC) and valid on its ^3.6.2 SDK floor.
  • drift ^2.22.1v9 pins an older drift than master's ^2.33.0. Verified against drift 2.22.1 directly: CaseWhenExpression({required cases, orElse}) and isBiggerThan have identical signatures there, so the DAO change is safe at v9's declared floor, not just at the resolved version.

CHANGELOG entries were added under a new ## Upcoming section in both packages, since v9's topmost section (## 9.27.0) is already released.

Tests

  • ChannelModel.lastUpdatedAt — newer lastMessageAt wins, null falls back to createdAt, older (truncated) falls back to createdAt
  • ChannelSortKey.lastUpdated via getComparableField — this key previously had no coverage at all
  • ChannelDao.cids ordering — the existing cids test now saves three channels and asserts one ordering. It discriminates the fix: the old ORDER BY last_message_at DESC yields active, stale, truncated, the new expression yields active, truncated, stale

Verified on this branch: melos run format clean, dart analyze --fatal-infos clean on all touched files, full stream_chat suite passes (1307 tests, 2 skipped), full stream_chat_persistence suite passes (260 tests).

Also ran as a negative control: with only the two lib/ changes reverted, the new lastUpdatedAt test and the cids test both fail — confirming the ported tests are not passing vacuously.

Screenshots / Videos

No UI changes.

…last_updated sort

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cf12b15-9e2a-4973-8836-5419137141ce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@VelikovPetar
VelikovPetar marked this pull request as ready for review August 14, 2026 13:42
@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (v9@fe5c6c9). Learn more about missing BASE report.

Additional details and impacted files
@@          Coverage Diff          @@
##             v9    #2892   +/-   ##
=====================================
  Coverage      ?   67.32%           
=====================================
  Files         ?      431           
  Lines         ?    27401           
  Branches      ?        0           
=====================================
  Hits          ?    18448           
  Misses        ?     8953           
  Partials      ?        0           

☔ View full report in Codecov by Harness.
📢 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.

@VelikovPetar
VelikovPetar merged commit 8a3ce36 into v9 Aug 14, 2026
24 checks passed
@VelikovPetar
VelikovPetar deleted the port/master-to-v9/FLU-689_fix_last_updated_calculation_logic branch August 14, 2026 14:35
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