Skip to content

feat: localized unread count#1787

Merged
isekovanic merged 12 commits into
masterfrom
feat/localized-unread-count
Jul 3, 2026
Merged

feat: localized unread count#1787
isekovanic merged 12 commits into
masterfrom
feat/localized-unread-count

Conversation

@isekovanic

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?

Spec: https://stream-wiki.notion.site/Local-Unread-Count-specification-38b6a5d7f9f68073a5e1c73298fe0d71

Adds an optin, client side unread count for channels that have read events disabled (e.g. livestreams). Those channels normally don't track unread at all and so _countMessageAsUnread bails out whenever own_capabilities lacks read-events, so integrators have no "N new messages" affordance to show while scrolled up.

With the new isLocalUnreadCountEnabled client option (default false), the SDK maintains a purely local unread count for those channels. It increments on incoming messages and is reset via the new channel.markReadLocally() method, which dispatches a client only message.local_read event. That event reuses the exact message.read state logic in _handleChannelEvent (so read state updates stay in one place) but skips syncDeliveredCandidates(), so the count is never sent to the backend. hasReadEvents() centralizes the capability check.

When offline support is enabled, the local read/unread state is persisted for channels with disabled read-events so the count survives a cold start. The offline DB handles message.local_read (guarded so it can only affect channels with disabled read-events channels with the option on) and falls back gracefully when state.read[userId] is absent, since the server never sends a read for these channels.

Behavior is fully gated behind the flag, so default off means no change for existing integrations.

Changelog

  • Add isLocalUnreadCountEnabled client option for a client side unread count on channels with disabled read-events (e.g. livestreams)
  • Add Channel.markReadLocally() to reset the local unread count with no backend call
  • Add Channel.hasReadEvents() helper
  • Add client only message.local_read event
  • Persist local unread state to the offline DB so it survives app restarts

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Size Change: +2.51 kB (+0.61%)

Total Size: 412 kB

📦 View Changed
Filename Size Change
dist/cjs/index.browser.js 137 kB +832 B (+0.61%)
dist/cjs/index.node.js 138 kB +836 B (+0.61%)
dist/esm/index.mjs 136 kB +840 B (+0.62%)

compressed-size-action

Comment thread src/types.ts
Comment thread src/events.ts Outdated
Comment thread src/channel.ts Outdated
@isekovanic isekovanic merged commit 0a40196 into master Jul 3, 2026
7 of 8 checks passed
@isekovanic isekovanic deleted the feat/localized-unread-count branch July 3, 2026 11:53
github-actions Bot pushed a commit that referenced this pull request Jul 3, 2026
## [9.50.0](v9.49.0...v9.50.0) (2026-07-03)

### Features

* localized unread count ([#1787](#1787)) ([0a40196](0a40196))
@stream-ci-bot

Copy link
Copy Markdown

🎉 This PR is included in version 9.50.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

isekovanic added a commit to GetStream/stream-chat-react-native that referenced this pull request Jul 3, 2026
## 🎯 Goal

SDK followup to this PR:
GetStream/stream-chat-js#1787.

All details can be read there.

## 🛠 Implementation details

<!-- Provide a description of the implementation -->

## 🎨 UI Changes

<!-- Add relevant screenshots -->

<details>
<summary>iOS</summary>


<table>
    <thead>
        <tr>
            <td>Before</td>
            <td>After</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <!--<img src="" /> -->
            </td>
            <td>
                <!--<img src="" /> -->
            </td>
        </tr>
    </tbody>
</table>
</details>


<details>
<summary>Android</summary>

<table>
    <thead>
        <tr>
            <td>Before</td>
            <td>After</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <!--<img src="" /> -->
            </td>
            <td>
                <!--<img src="" /> -->
            </td>
        </tr>
    </tbody>
</table>
</details>

## 🧪 Testing

<!-- Explain how this change can be tested (or why it can't be tested)
-->

## ☑️ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [ ] 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants