Skip to content

feat: add message/channel paginator stack with thread minimal-init and state stores#1795

Open
MartinCupela wants to merge 39 commits into
release-v10from
feat/message-paginator-master-merge
Open

feat: add message/channel paginator stack with thread minimal-init and state stores#1795
MartinCupela wants to merge 39 commits into
release-v10from
feat/message-paginator-master-merge

Conversation

@MartinCupela

@MartinCupela MartinCupela commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR is a broader SDK architecture update, not just a message pagination introduction.
In addition to the new paginator stack, it introduces minimal Thread initialization (without initial server thread payload), reactive StateStore-based state in ChannelState, new orchestration/event-pipeline utilities.

What’s included

1) New pagination foundation + message pagination

  • Introduces the new paginator system under src/pagination/paginators/*:
    • BasePaginator
    • ChannelPaginator
    • MessagePaginator
    • MessageReplyPaginator
    • ReminderPaginator
  • Adds supporting pagination utilities:
    • filter/sort compilers
    • cursor derivation helpers
    • index + normalization utilities
  • Adds MessagePaginator integration for channel and thread flows:
    • reply querying through paginator
    • message focus signal support
    • ingest/update/delete/reaction/truncation handling
    • jump-to-message state emission fixes

2) Multi-list channel orchestration

  • Adds ChannelPaginatorsOrchestrator for managing multiple channel lists/paginators.
  • Adds EventHandlerPipeline for composable/ordered event handling.
  • Supports ownership strategies when a channel matches multiple paginators (including priority-based ownership).
  • Handles list updates/removals/re-emits across relevant channel events.

3) Thread minimal-init support (no initial thread payload required)

  • Thread constructor now supports initialization from:
    • client + channel + parentMessage (with optional draft)
    • or existing threadData
  • Enables creating a usable thread instance before full hydration.
  • Keeps reload/hydration and pagination bootstrap behavior available after creation.
  • Includes broader thread event/state synchronization updates (participants/reply count/read/replies).

4) Reactive ChannelState stores

  • Adds StateStore-backed reactive slices in ChannelState:
    • watchers
    • typing
    • read
    • members (+ member count sync)
    • own capabilities
    • muted users
  • Preserves existing access patterns while introducing store-backed updates.

5) Message operations + local update flows

  • Adds MessageOperations and MessageOperationStatePolicy.
  • Adds/expands optimistic local update APIs for Channel/Thread message actions:
    • send
    • retry
    • update
    • delete
  • Adds message send retry cache behavior and related test coverage.

6) Config/setup and request customization improvements

  • Introduces and exports InstanceConfigurationService.
  • Moves setup/config state handling to dedicated configuration module and root exports.
  • Adds custom mark-read request support for both Channel and Thread.
  • Removes thread parameter from Channel custom mark-read request signature.

Testing

Large test expansion and updates across:

  • paginator units (Base, Channel, Message, MessageReply, compiler/index utilities)
  • orchestrator + event pipeline
  • channel/channel_state/thread behaviors
  • message operations + delivery tracking
  • composer/text composer interactions
  • merge utility behavior

Todo

  • internal docs
  • docs

Support setting paginator items directly, optional request retries, offline DB in ChannelPaginator, identification of pagination restart based on query shape change.
…eat/message-paginator

# Conflicts:
#	src/channel.ts
#	src/client.ts
….messages.deleted and message.deleted events
MartinCupela and others added 7 commits March 6, 2026 00:43
Resolve all 7 textual conflicts + hidden semantic conflicts from folding
PR #1674 into current master (release-v10). Types pass; 3462/3469 unit
tests pass.

Notable resolutions:
- member.added/removed: keep master fix #1761 (drop PR's reintroduced
  member_count double-count).
- UserGroupPaginator (#1743): ported to the new paginators/BasePaginator<T,Q>.
- client.ts / textComposer types / pagination barrel: unioned, dead code dropped.

Known remaining failures tracked in specs/.../decisions.md (own_capabilities
design decision + 3 items needing diagnosis). WIP checkpoint; not pushed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- own_capabilities (Option B): keep reactive getter/setter but return
  undefined until loaded, preserving #1732 unread on uninitialized channels;
  update PR's 'undefined ID no options' expectation.
- UserGroupPaginator: stable query shape + apply forward cursor in query()
  so pages accumulate (new base resets list on query-shape change).
- orchestrator test: expect item:undefined on notification.removed_from_channel
  (master #1788 evicts channel from activeChannels).
- messageComposer config test: add master's trackUploadProgress + commands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(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
## [9.50.0](v9.49.0...v9.50.0) (2026-07-03)

### Features

* localized unread count ([#1787](#1787)) ([0a40196](0a40196))
@MartinCupela MartinCupela changed the title feat: add MessagePaginator feat: add message/channel paginator stack with thread minimal-init and state stores Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Size Change: +96.3 kB (+23.54%) 🚨

Total Size: 505 kB

📦 View Changed
Filename Size Change
dist/cjs/index.browser.js 168 kB +32.1 kB (+23.57%) 🚨
dist/cjs/index.node.js 170 kB +32.2 kB (+23.39%) 🚨
dist/esm/index.mjs 167 kB +32 kB (+23.65%) 🚨

compressed-size-action

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.

3 participants