Skip to content

feat: migrate components from ChannelActionContext and ChannelState context to dedicated StateStore instances and add layout control API#3237

Draft
MartinCupela wants to merge 890 commits into
release-v10from
feat/message-paginator-master-merge
Draft

feat: migrate components from ChannelActionContext and ChannelState context to dedicated StateStore instances and add layout control API#3237
MartinCupela wants to merge 890 commits into
release-v10from
feat/message-paginator-master-merge

Conversation

@MartinCupela

Copy link
Copy Markdown
Contributor

Depends on

GetStream/stream-chat-js#1795

Summary

This PR is a major architecture migration, not just message pagination.

It moves chat/thread runtime behavior from React-owned channel contexts to instance APIs in stream-chat-js, introduces a slot-based ChatView layout controller, and rewires navigation/state to reactive StateStore-driven sources (messagePaginator, configState, threads.state, etc.).

Scope

  • Introduces a new ChatView layout control API with slot-based entity binding and view-aware state.
  • Replaces ChannelActionContext and ChannelStateContext runtime usage with instance APIs from stream-chat-js.
  • Migrates message/thread interactions to messagePaginator and client instance APIs.
  • Adds React adapters/hooks for slot-resolved channel/thread rendering and navigation.
  • Reworks Channel/Thread request handler wiring to instance configState.requestHandlers.
  • Updates components/stories/tests across the SDK to use the new contracts.

What Changed

1) New ChatView Layout Control API

  • Added LayoutController state model for slot topology, bindings, visibility, and per-slot history.
  • Added ChatViewNavigation API:
    • openView(view, { slot? })
    • openChannel(channel, { slot? })
    • closeChannel({ slot? })
    • openThread(threadOrTarget, { slot? })
    • closeThread({ slot? })
    • hideChannelList({ slot? })
    • unhideChannelList({ slot? })
  • Added slot-oriented primitives:
    • ChannelSlot
    • ThreadSlot
    • ChannelListSlot
    • ThreadListSlot
    • useSlotEntity, useSlotChannel, useSlotThread
  • Added layout state serialization helpers:
    • serializeLayoutState
    • restoreLayoutState
  • Added resolver utilities in layoutSlotResolvers for deterministic slot targeting.

2) Channel/Thread Ownership Moved to stream-chat-js Instances

  • Channel/thread message state now comes from instance-level messagePaginator and thread manager state.
  • Channel and Thread flows are instance-driven and reactive via StateStore.
  • Thread open/close behavior is routed through useChatViewNavigation() (with legacy fallback deactivation in Thread).

3) Context Removal and Replacement

  • Removed runtime/public usage of:
    • ChannelActionContext
    • ChannelStateContext
    • TypingContext provider path from Channel runtime
  • Added ChannelInstanceContext + useChannel() as the channel resolution contract.
  • useChannel() resolves from:
    • active thread context first
    • otherwise ChannelInstanceContext

4) Message Pagination + Unread/Focus Migration

  • Added public hook:
    • useMessagePaginator()
  • Message list/thread operations now use:
    • messagePaginator.jumpToMessage(...)
    • messagePaginator.jumpToTheFirstUnreadMessage(...)
    • messagePaginator.jumpToTheLatestMessage()
    • messagePaginator.toHead()/toTail()
    • messagePaginator.ingestItem(...)
    • messagePaginator.removeItem(...)
    • messagePaginator.unreadStateSnapshot
  • Unread UI controls now use instance APIs and client.messageDeliveryReporter instead of context actions.

5) Request Handler Customization Moved to Instance Config

  • Channel and Thread now register custom request handlers into:
    • channel.configState.requestHandlers
    • thread.configState.requestHandlers
  • Covers custom send/update/delete/markRead paths without ChannelActionContext callbacks.

API Changes and Migration Guide

Navigation: setActiveChannel/context actions -> ChatView navigation

// Before
setActiveChannel(channel);
openThread(message);
closeThread();

// After
const { openChannel, openThread, closeThread } = useChatViewNavigation();
openChannel(channel);
openThread({ channel, message });
closeThread();

### Message list updates: context mutation helpers -> paginator reconciliation

```js
// Before
updateMessage(message);
removeMessage(message);

// After
const paginator = useMessagePaginator();
paginator.ingestItem(message);
paginator.removeItem({ item: message });

Message jump/pagination: context methods -> paginator methods

// Before
jumpToMessage(id);
jumpToFirstUnreadMessage();
loadMore();
loadMoreNewer();

// After
const paginator = useMessagePaginator();
paginator.jumpToMessage(id);
paginator.jumpToTheFirstUnreadMessage();
paginator.toTail();
paginator.toHead();

Channel access: ChannelState/Action context -> useChannel()

// Before
const { channel } = useChannelStateContext();

// After
const channel = useChannel();

Slot-based rendering (new recommended pattern)

<ChatView>
  <ChatView.Channels slots={['list', 'main', 'thread']}>
    <ChannelListSlot slot='list' />
    <ChannelSlot slot='main' />
    <ThreadSlot slot='thread' />
  </ChatView.Channels>
</ChatView>

Behavioral Notes

  • Active entity routing is now layout/slot-driven instead of ChatContext active-channel ownership.
  • Thread/channel can coexist as sibling slot entities.
  • Channel list “open on mount” flows now open via navigation API.
  • Unread and notification behavior is now aligned with instance stores/reporters.

Testing

  • Broad test migration to the new contracts across Channel, Thread, MessageActions, MessageList, ChatView -navigation/layout, and slot hooks/components.
  • Added focused tests for:
  • useChannelRequestHandlers
  • useThreadRequestHandlers
  • ChatViewNavigation
  • layout controller behavior
  • slot resolution helpers

Breaking/Important for Integrators

  • Stop relying on ChannelActionContext and ChannelStateContext APIs.
  • Use useChatViewNavigation() for open/close channel/thread flows.
  • Use useMessagePaginator() + instance APIs for list mutations/jump/pagination.
  • Use slot adapters (ChannelSlot, ThreadSlot, list slots) for deterministic multi-pane ChatView layouts.
  • Prefer instance-scoped request handler overrides (Channel/Thread props wired to configState.requestHandlers).

🎨 UI Changes

No planned UI changes

oliverlaz and others added 30 commits March 12, 2026 13:38
## 🎯 Goal

Add dark theme support to `stream-chat-react` and wire the Vite example
so the SDK can be exercised in both light and dark mode.

Linear ticket:
https://linear.app/stream/issue/REACT-798/add-support-for-dark-theme

## 🛠 Implementation details

- add dark semantic token overrides for the React SDK and scope them to
`str-chat__theme-dark`
- make `Chat`/portal rendering respect the dark theme class and update
the Vite example with a theme toggle
- align loading shimmers and image-loading overlays with the theme-aware
skeleton tokens

## 🎨 UI Changes


https://github.com/user-attachments/assets/5e05301c-47c7-421f-8d1a-5af4eb778ef5

---------

Co-authored-by: Anton Arnautov <arnautov.anton@gmail.com>
### 🎯 Goal

Fix two UI regressions:
- the attachment preview dismiss/remove icon was not respecting dark
mode tokens
- ChatView channel/thread toggling caused width jumps because the
channel and thread sidebars used different width contracts

### 🛠 Implementation details

- Remove the nested SVG color override from
`src/components/MessageInput/styling/RemoveAttachmentPreviewButton.scss`
so the icon inherits the existing close-control tokens.
- Restore the failing `AttachmentPreviewList` test suite by fixing the
missing React import in `PlayButton`, aligning the tests with the
current media preview test IDs/override props, and seeding
edited-message state through `messageComposer`.
- Give `ThreadList` the same sidebar width/flex contract as
`ChannelList`, and ensure the thread detail pane flexes correctly inside
`ChatView.Threads`.
- In the Vite example, define a shared `350px` sidebar baseline for both
channel and thread views instead of separate ad hoc widths.

### 🎨 UI Changes

- Attachment preview remove icons now inherit the correct dark mode
colors.
- Channel and thread sidebars now stay width-aligned when toggling in
ChatView.
- The sample app uses a shared `350px` sidebar baseline for both views.
### 🎯 Goal

Align the media gallery viewer with the REACT-847 mock so the modal
chrome, controls, and layout match the updated design.

Linear ticket:
https://linear.app/stream/issue/REACT-847/mediagallery-adjustments

### 🛠 Implementation details

- Added a dedicated `GalleryHeader` component for the title, timestamp,
download action, and close action.
- Updated the gallery styling to use the full available modal area with
an edge-to-edge blurred stage, overlay header controls, centered media
index, and refined navigation buttons.
- Added the new circular download icon and switched the gallery header
to use it.
- Removed the modal overlay close button so the gallery owns its close
control.
- Expanded gallery tests to cover the updated header actions and
position indicator copy.

### 🎨 UI Changes

Reference mock:
https://linear.app/stream/issue/REACT-847/mediagallery-adjustments

Local comparison screenshot was kept out of the commit and PR branch.
### 🎯 Goal

Implement the dynamic panel resizing ticket in the Vite example app as
the first iteration before extracting the behavior into SDK components.
Reference: the dynamic panel resizing ticket covering the channel list,
message list, and thread panel behavior.

Resolves:
https://linear.app/stream/issue/REACT-828/layout-panel-resizing

### 🛠 Implementation details

- persist left and thread panel layout in the existing example state
store
- split `examples/vite/src/App.tsx` layout concerns into focused
`ChatLayout` modules for panel rendering, resize behavior, URL/state
sync, and shared constants
- add desktop drag resizing for the left sidebar and thread panel,
including collapse and restore behavior, width constraints, and sidebar
state synchronization with `navOpen`
- keep the chat view selector stable on desktop and tune transition
handling so drag remains responsive while automatic sidebar collapse
still animates

### 🎨 UI Changes

Example app layout behavior update:


https://github.com/user-attachments/assets/e3e34721-d55a-44a7-a505-927f9df3ba2f
### 🎯 Goal

Keep the React v14 migration audit in source control while the release
is still moving, so follow-up breaking changes and doc rewrites can
continue from a fixed baseline.

### 🛠 Implementation details

- add `breaking-changes.md` as the confirmed v13 to v14 breaking-change
tracker
- add `docs-plan.md` as the page-by-page docs debt tracker and execution
plan
- record the currently audited SDK commit
(`6ea7a78e4184fce6066f7318f9ebd57a5ff1474a`) in both files so future
mining can start from `6ea7a78e..HEAD`
- include the confirmed docs fallout and migration-guide inputs gathered
during the audit
- no tests run; tracker/docs files only

### 🎨 UI Changes

N/A
### 🎯 Goal

Notes for the updated v14 docs plan.
### 🎯 Goal

Enable `exitSearchOnBlur` in the demo application, adjust the behavior
accordingly (keep the search open if user clicks on filters or clear
button as that could be still considered within the bounds of the search
input behavior-wise).
BREAKING CHANGE: ChannelSearch component set has been dropped, use
Search instead.
### 🎯 Goal

Remove the deprecated `useAudioController` hook from the public API and
eliminate the remaining source-level dependency on its file.

BREAKING CHANGE: `useAudioController` has been removed from the public
API. Migrate custom audio playback code to `useAudioPlayer`.

### 🛠 Implementation details

- delete `src/components/Attachment/hooks/useAudioController.ts`
- remove the public export from `src/components/Attachment/index.ts`
- decouple `WaveProgressBar` from the deleted hook file by giving it its
own UI-level seek callback type
- keep the current audio playback components compatible with
`audioPlayer.seek`
- validated with `yarn types`

### 🎨 UI Changes

None.
### 🎯 Goal


https://linear.app/stream/issue/REACT-891/user-should-be-able-to-quote-a-message-with-a-voice-message

Depends on: GetStream/stream-chat-js#1705

### 🛠 Implementation details

_Provide a description of the implementation_

### 🎨 UI Changes

_Add relevant screenshots_
### 🎯 Goal

Fixes QA issues for edited indicator:
https://linear.app/stream/issue/REACT-895/im-not-sure-i-understand-how-this-second-edited-label-works-or-should
Docs: GetStream/docs-content#1085

### 🛠 Implementation details

- Removes the old `MessageEditedTimestamp` -> couldn't find a reference
for it in docs
- Adds a new component `MessageEditedIndicator` -> follows existing
pattern to allow component overrides

### 🎨 UI Changes

_Add relevant screenshots_
BREAKING CHANGE: replaced addNotification from ChannelStateContenxt with
client.notifications API
BREAKING CHANGE: NotificationList was moved with new parent now being MessageListMainPanel
BREAKING CHANGE: rename MessageListNotificationsProps to NotificationListProps
BREAKING CHANGE: the BaseImage component uses ImageFallback component instead of CSS masl to display fallback on error
ndhuutai and others added 24 commits June 4, 2026 12:37
…#3207)

PR #3128 renamed all SDK CSS custom properties with a --str-chat__
prefix immediately after PR #3133 synced the tutorial examples to the
semantic token names. Update all five tutorial layout.css files
accordingly.

### 🎯 Goal

PR #3128 added the `--str-chat__` prefix to all SDK CSS custom
properties, but the tutorial examples (steps 3–7) were not updated at
the same time. As a result, the `stream-overrides` layer in each
tutorial's `layout.css` sets properties that no longer exist, making the
custom blue theme have no visible effect.

### 🛠 Implementation details

Renamed all 13 CSS custom properties in the `.custom-theme` block across
the five byte-identical `layout.css` files (tutorial steps 3–7) to match
the current `--str-chat__` prefix convention (e.g. `--chat-bg-outgoing`
→ `--str-chat__chat-bg-outgoing`).

### 🎨 UI Changes

CSS-only rename — no visual change intended. The theming was already
broken before this fix; after the fix it renders as originally designed
(navy outgoing bubbles, light-blue incoming bubbles, pale-blue panel
backgrounds).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated tutorial example files across multiple sections to use
standardized CSS variable naming conventions for theming and styling
consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Tai Nguyen <tainguyen@Tais-MacBook-Pro-M4.local>
### 🎯 Goal

The vite example could only take the Stream API key from the
`VITE_STREAM_API_KEY` env var, while the user `token` was already
overridable via a query param. This makes it awkward to point the
running example at a different Stream app without editing `.env` and
restarting. This lets you pass `?api_key=…` (and combine it with
`?token=…`) directly in the URL, matching how `token` already works.

### 🛠 Implementation details

- `apiKey` now reads from the `api_key` query param first, falling back
to `VITE_STREAM_API_KEY` — mirroring the existing `token` resolution.
- `userId` now derives from the provided `token` (via
`parseUserIdFromToken`) when present, slotted just below the explicit
`user_id` query param and above the env/localStorage/random fallbacks.
This makes `?api_key=…&token=…` self-sufficient: previously the pronto
token generator was only skipped when the resolved `userId` matched the
token's embedded `user_id`, so omitting `user_id` silently fell back to
pronto with the wrong environment/app.
- `parseUserIdFromToken` now returns `undefined` instead of throwing on
a malformed/empty token, since it now runs during render; this also
hardens the existing `tokenProvider` call site.

User-id precedence: `?user_id=` → token-derived → `VITE_USER_ID` →
`localStorage` → random. The explicit `user_id` param still wins,
preserving the "connect as someone else, regenerate via pronto" path.

### 🎨 UI Changes

No visual changes — this only affects how the example app sources its
credentials. Verified by driving the running example with Playwright:
- `?api_key=test_key_AAA&token=<jwt for "bob">` → Stream WS connects
with `api_key=test_key_AAA` and `user_id=bob`; pronto skipped.
- `?api_key=test_key_BBB` (no token) → pronto `create-token` called
(fallback intact).
- `?api_key=xzwhhgtazy6h` (public key) → real connection succeeds, chat
UI renders, zero console errors.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* API key can now be optionally specified via query parameter, with
fallback to environment configuration
* Enhanced token parsing with improved error handling that gracefully
manages malformed tokens

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
… rendering (#3209)

### 🎯 Goal

Reactions sent via `channel.sendReaction` did not include `emoji_code`,
so mobile push notification templates (e.g. for `reaction.new`) had no
emoji to render. This wires the React SDK to include `emoji_code` in the
reaction request payload so mobile SDKs can render the correct emoji in
push notifications.

Linear:
[REACT-880](https://linear.app/stream/issue/REACT-880/add-emoji-code-to-reaction-request-payload)

### 🛠 Implementation details

- New exported helper `getEmojiCodeByReactionType(reactionOptions,
type)` in `reactionOptions.tsx` resolves the native emoji character
(e.g. `👍`) for a reaction type from the option's existing `unicode`
field via `unicodeToEmoji()`. Returns `undefined` for legacy array
options or options that omit `unicode`, so we never send a garbage code.
- `useReactionHandler` now reads `reactionOptions` from
`ComponentContext` (falling back to `defaultReactionOptions`, like other
consumers) and includes `emoji_code` in the `sendReaction` payload when
one resolves:
  ```ts
channel.sendReaction(id, { type, ...(emojiCode && { emoji_code:
emojiCode }) });
  ```
The optimistic reaction preview is stamped with the same value for
consistency. The public `handleReaction(type, event)` signature is
**unchanged**.
- `mapEmojiMartData` now stores `unicode` on each mapped entry, so emoji
picked from the **extended** (emoji-mart) list also resolve an
`emoji_code`.
- The `stream-chat` `Reaction` type already supports the optional
`emoji_code` field — only stream-chat-react needed to populate it.

**Behavior:** default reactions (`like`, `love`, `haha`, `sad`, `wow`,
`fire`) and any custom reactions defining `unicode` now ship
`emoji_code` automatically. Legacy array-form reaction options carry no
unicode data and continue to send no `emoji_code` (graceful, unchanged
otherwise).

**Tests:** new `reactionOptions.test.ts` covering the helper
(quick/extended/legacy-array/unknown/missing-unicode) and
`mapEmojiMartData`; `useReactionHandler` tests for
default/custom/no-unicode payloads and the optimistic preview; updated
the existing `Message.test.tsx` send assertion to the new contract.

### 🎨 UI Changes

None — this only affects the network payload sent with reactions (and
the local optimistic reaction object). No visual changes.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Reactions now support and transmit emoji code data for improved emoji
handling.

* **Tests**
* Expanded test coverage for reaction emoji code derivation with custom
reaction options.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## [14.4.0](v14.3.0...v14.4.0) (2026-06-05)

### Bug Fixes

* **compat:** restore React 17/18 compatibility in certain components ([#3197](#3197)) ([b513b69](b513b69))
* general performance and bug fixes ([#3201](#3201)) ([57c5795](57c5795))
* **interop:** unwrap CJS default exports (react-player, @emoji-mart/react) ([#3199](#3199)) ([4cddb02](4cddb02))
* maintain topmost modal non-inert ([#3206](#3206)) ([7ad98fa](7ad98fa))

### Features

* allow to stack modals on top of each other ([#3203](#3203)) ([4c934ae](4c934ae))
* display notifications above modals ([#3200](#3200)) ([0433090](0433090))
* **Reactions:** send emoji_code with reactions for push notification rendering ([#3209](#3209)) ([2faa620](2faa620))

### Performance Improvements

* **Message:** hoist regex compilation in message text rendering ([#3202](#3202)) ([8c018a4](8c018a4))
* **VideoPlayer:** lazy-load react-player to keep it out of the main bundle ([#3204](#3204)) ([18dc966](18dc966))
…3211)

### 🎯 Goal

`postinstall` ran `husky` unconditionally. npm executes a dependency's
`postinstall` for every consumer, but `husky` is a `devDependency` and
isn't installed in a consumer's tree, so `npm install stream-chat-react`
failed:

```
npm error code 127
npm error sh: husky: command not found
```

This ports the same fix already merged for the JS SDK in
[GetStream/stream-chat-js#1764](GetStream/stream-chat-js#1764)
— `stream-chat-react` had the identical `"postinstall": "husky"`.

### 🛠 Implementation details

**Why husky has to stay in `postinstall`:** Yarn Berry (this repo's
package manager) runs the **root workspace's `postinstall`** on `yarn
install` but **not** its `prepare`. Moving husky to `prepare` would
silently stop installing contributors' git hooks. husky must stay in
`postinstall`; the fix is to stop *running* it for consumers.

- `postinstall` now invokes a dev-only `scripts/install-husky.mjs`
**only when it is present** — and that file is intentionally **not** in
`package.json#files` (which publishes only `dist`, `package.json`,
`README.md`, `AI.md`), so consumers never receive it and the guard
short-circuits to a no-op.
- The invocation runs via `node` (not a shell `|| true`) so it's
cross-platform — npm runs consumer postinstalls through `cmd.exe` on
Windows, where `||`/`true` aren't valid.
- `scripts/install-husky.mjs` runs husky only inside a git checkout, and
lets genuine husky setup errors **surface** (not swallowed) so
contributors learn if hook installation actually fails.

> Note: unlike the JS SDK PR (which left `prepare: yarn run build`
untouched), this repo uses `prepack: yarn build` and has no `prepare`
script, so nothing there needed changing — the husky fix is independent
of it.

**Verified locally:**

| Scenario | Result |
| --- | --- |
| Consumer (no `scripts/install-husky.mjs`) | exit 0 · husky never
executed |
| Contributor (`.git` + script present) | `core.hooksPath=.husky/_` ·
hooks installed · exit 0 |

`yarn prettier` and `yarn eslint` clean on both changed files.

### 🎨 UI Changes

None — build/install tooling only.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved post-install script configuration to make Git hook
initialization more reliable and conditional based on repository
presence.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## [14.4.1](v14.4.0...v14.4.1) (2026-06-05)

### Bug Fixes

* only run husky in a git checkout so npm consumers don't break ([#3211](#3211)) ([640bf4b](640bf4b))
…ine predicate (#3213)

## Summary

- In `5-custom-attachment-type/App.tsx`, `hasProductMessage` used an
inline predicate `(attachment) => 'type' in attachment &&
attachment.type === 'product'` that duplicated the existing
`isProductAttachment` type guard defined earlier in the same file.
- Replaced the inline predicate with a direct reference to
`isProductAttachment`.

## Test plan

- [ ] No behaviour change — purely a refactor to reuse the existing type
guard.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Documentation

* **Examples**: Updated tutorial to demonstrate improved patterns for
detecting and handling custom attachment types in message processing
workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Tai Nguyen <tainguyen@Tais-MacBook-Pro-M4.local>
### 🎯 Goal

Message timestamps were not fully localized. Most visibly, the
message-reminder due-date label rendered in **English for every
non-English locale** — e.g. Spanish showed `Today at 15:00` / `Yesterday
at 09:30` instead of `Hoy a las 15:00` / `Ayer a las 09:30`.

Root cause: `timestamp/ReminderNotification` was a verbatim English copy
of the `en` value in all 11 non-English locale files. Every *other*
calendar key (`DateSeparator`, `ChannelPreviewTimestamp`) was properly
translated; this one was missed when the reminder feature landed.
`scripts/validate-translations.js` only fails on **empty** values, so an
English-leak passes CI — which is why it went unnoticed.

A broader pass also surfaced two more English-leaking timestamp keys
(`relativeDaysAgo` / `relativeWeeksAgo`, the compact "ago" form used
e.g. for poll-vote ages) and a few stray untranslated strings.

### 🛠 Implementation details

- **`timestamp/ReminderNotification`** (all 11 non-English locales):
localized the `calendarFormats` literals.
- Day-words taken from each locale's already-shipped
`timestamp/DateSeparator` translation.
- "at"-connectors taken from the dayjs `updateLocale` calendar configs
in `Streami18n.ts` (es `a las`, de `um`, fr `à`, it `alle`, pt `às`, nl
`om`, ru `в`, tr `saat`; ja/ko/hi use none).
- `lastWeek` patterns mirror the grammatically-correct forms already
defined in `Streami18n.ts` (e.g. de `letzten dddd`, fr `dddd dernier`,
pt `dddd passada`, it `lo scorso dddd`; ru drops the un-agreeable
prefix, matching the source which leaves ru `lastWeek` undefined).
- **`timestamp/relativeDaysAgo` / `timestamp/relativeWeeksAgo`** (all
11): localized the compact "N days/weeks ago" forms per language — e.g.
es `hace {{ count }} d` / `sem`, de `vor {{ count }} Tg.` / `Wo.`, fr
`il y a {{ count }} j` / `sem`, ja `{{ count }}日前` / `週間前`, ru `{{ count
}} дн.` / `нед. назад`.
- **Remaining gaps:** de `Back`→`Zurück`, `Instant
commands`→`Sofortbefehle`, `Search GIFs`→`GIFs suchen`, capitalized
German plural nouns (`{{ count }} Links`, `{{ count }} Videos`); tr
`language/ha`→`Hausaca` (the only break in Turkish's systematic
`-ca/-ce` language-name pattern).
- **Left unchanged:** correct cognates/loanwords that merely coincide
with English (fr `Menu`/`Image`/`Photo`/`Question`/`Votes`, de/nl
`Online`/`Offline`/`Link`/`Video`/`Original`, and the `language/*` names
that are genuinely identical in those languages).

**Verification:** all 12 locale JSONs valid · `validate-translations`
passes · prettier clean · `i18n` + `ReminderNotification` +
`MessageTimestamp` tests pass (130) · rendered output spot-checked
through dayjs `.calendar()` per locale. Reviewed via Codex; both
findings (lastWeek grammar, audit-helper) addressed.

### 🎨 UI Changes

Text-only — no structural or visual changes, so no screenshots. Example
before → after:

| Surface | Locale | Before | After |
| --- | --- | --- | --- |
| Reminder due date | es | `Today at 15:30` | `Hoy a las 15:30` |
| Reminder due date | de | `Today at 15:30` | `Heute um 15:30` |
| Reminder (last week) | fr | `Last Sunday at 15:30` | `dimanche dernier
à 15:30` |
| Poll-vote age | es | `2d ago` | `hace 2 d` |
| Poll-vote age | ja | `2d ago` | `2日前` |


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Enhanced internationalization with corrected and improved translations
across 11 languages (German, Spanish, French, Hindi, Italian, Japanese,
Korean, Dutch, Portuguese, Russian, and Turkish).
* Updated date and time formatting to display more naturally and
consistently in each language's locale.
* Fixed translation inconsistencies for UI labels and relative time
expressions throughout the interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal

A mirror of [this
PR](GetStream/stream-chat-react-native#3648)
from the React Native SDK.

### 🛠 Implementation details

_Provide a description of the implementation_

### 🎨 UI Changes

_Add relevant screenshots_


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* The message composer now uses the shared client-side cache provided by
the chat client, improving consistency across views and avoiding
duplicate cache setup.

* **Chores**
* Bumped `stream-chat` to a newer patch version in the main project and
example packages.
* Updated package manager configuration to explicitly allow
`stream-chat`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Anton Arnautov <arnautov.anton@gmail.com>
Co-authored-by: Oliver Lazoroski <oliver.lazoroski@gmail.com>
Co-authored-by: Anton Arnautov <43254280+arnautov-anton@users.noreply.github.com>
## [14.5.0](v14.4.1...v14.5.0) (2026-06-19)

### Bug Fixes

* **i18n:** localize message timestamps across all locales ([#3220](#3220)) ([c1b65b6](c1b65b6))
* **MediaRecorder:** guard pause/resume against inactive recorder to prevent InvalidStateError ([#3218](#3218)) ([a7e9006](a7e9006))
* use client mandated composer queue ([#3222](#3222)) ([57906bb](57906bb))

### Features

* add enhanced mentions ([#3189](#3189)) ([7172ebc](7172ebc))

### Refactors

* **tutorial:** use isProductAttachment type guard instead of inline predicate ([#3213](#3213)) ([0b95202](0b95202))
## [14.6.0](v14.5.0...v14.6.0) (2026-06-25)

### Features

* add ChannelDetail component ([#3221](#3221)) ([2312d89](2312d89))
## Summary

URLs written with an uppercase scheme (e.g. `HTTPS://example.com`) were
rendered incorrectly in message text:

- The anchor was emitted **without** the `str-chat__message-url-link`
class, so the link looked like plain, unstyled text (effectively "not
recognized as a link").
- The scheme was **not stripped** from the displayed text, so users saw
`HTTPS://example.com` instead of `example.com`.

The lowercase equivalent (`https://example.com`) renders correctly, so
the bug is purely about case sensitivity in two helpers.

## Root cause

Two case-sensitive helpers in the `renderText` pipeline:

1. **`componentRenderers/Anchor.tsx`** decided whether to apply the link
class with `href?.startsWith('http')`. An `HTTPS://` href fails this
check, so `isUrl` was `false` and the `str-chat__message-url-link` class
was dropped.
2. **`regex.ts`** strips the scheme for display text via `detectHttp =
/(http(s?):\/\/)?(www\.)?/`. Without the `i` flag it doesn't match
`HTTPS://`, so the scheme stayed in the display text.

`linkifyjs` (which detects the URL) and `react-markdown` (which renders
it) are both already case-insensitive, so they are not at fault — they
correctly identify and preserve the uppercase-scheme URL. The
display/styling layer was the only case-sensitive part.

## The fix

```diff
- const isUrl = href?.startsWith('http');
+ const isUrl = href?.toLowerCase().startsWith('http');
```

```diff
- export const detectHttp = /(http(s?):\/\/)?(www\.)?/;
+ export const detectHttp = /(http(s?):\/\/)?(www\.)?/i;
```

Both changes are minimal and preserve the existing behavior for
lowercase schemes.

## Reproduction

Before the fix:

- `https://en.wikipedia.org/wiki/Apple` -> styled link, display text
`en.wikipedia.org/wiki/Apple`
- `HTTPS://en.wikipedia.org/wiki/Apple` -> unstyled `<a>` (no
`str-chat__message-url-link`), display text
`HTTPS://en.wikipedia.org/wiki/Apple`

After the fix, the uppercase variant renders identically to the
lowercase one: a styled link with the scheme stripped from the display
text, while the `href` keeps its original casing.

## Tests

Added a regression test in `renderText.test.tsx` asserting that `"see
HTTPS://en.wikipedia.org/wiki/Apple"` produces an anchor with:

- class `str-chat__message-url-link`
- `href="HTTPS://en.wikipedia.org/wiki/Apple"` (original casing
preserved)
- link text `en.wikipedia.org/wiki/Apple` (scheme stripped)

The test fails on `master` (the input renders as plain text with no
anchor) and passes with this change. The full `renderText` suite (61
tests) passes; no existing snapshots changed.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* URLs with uppercase or mixed-case schemes are now detected and linked
correctly.
* Link rendering now treats URL schemes case-insensitively, improving
consistency for pasted links.
  * Added test coverage for uppercase URL matching and link behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal

Fixes three Tier‑1 issues from the bug‑bash triage. Each is an
independent fix kept as its own self‑contained commit (with tests):

- **Closes #2474** — a `message.new` for a not‑yet‑watched channel
inserts an uninitialized channel into the list; rendering it called
`channel.muteStatus()`, which throws `"hasn't been initialized yet"` and
**crashed the whole app**.
- **Closes #2441** — `ChannelList` issued a `queryChannel`
(`channel.watch()`) for **every** `notification.message_new`, even when
the result was discarded, exhausting the rate limit during bulk channel
activity.
- **Closes #2393** — once the shared client is disconnected,
`channel.lastRead()` (called during render in
`useCreateChannelStateContext`) throws `"You can't use a channel after
client.disconnect() was called"`, **crashing the render** with no error
boundary to catch it.

> **#2599 — fixed via the `stream-chat` upstream fix + this PR's
dependency bump.** An earlier `stopWatching`-on-removal attempt here was
implemented and then reverted: it did **not** make removal durable.
`channel.stopWatching()` does not evict the channel from
`client.activeChannels`, so a reconnect (`recoverState` re-watches every
active channel) or a queued/in-flight `message.new` re-added the removed
channel — the exact reported symptom. The real fix evicts
`activeChannels[cid]` on `notification.removed_from_channel` (mirroring
the existing `channel.deleted` handling); it shipped in
**`stream-chat@9.49.0`** (GetStream/stream-chat-js#1788). This PR bumps
the `stream-chat` requirement to `^9.49.0` and adds a regression test —
with the bump in place, `ChannelList` removal is durable and the test
passes.

### 🛠 Implementation details

**#2474 — `fix(ChannelListItem): guard muteStatus against an
uninitialized/disconnected channel`**
- `useIsChannelMuted` reads the mute status through a helper that calls
`channel.muteStatus()` only when the channel is initialized **and not
disconnected**, falling back to a not‑muted status otherwise.
`muteStatus()` throws on an uninitialized channel (`#2474`) and also
after the client disconnects — via `channel.getClient()` — even though
the channel stays initialized (the #2393 failure class on the
channel‑list path).
- The mute‑update subscription now depends on `[channel, client]` and
cleans up via the `unsubscribe` handle returned by `client.on()`. It
previously depended on `[muted]`, so it re‑subscribed on its own output
and could capture a stale channel.

**#2441 — `fix(ChannelList): do not query unfiltered channels on
notification.message_new`**
- Moved the `allowNewMessagesFromUnfilteredChannels` guard **before**
the `getChannel()` call in `handleNotificationMessageNew`, so the flag
actually suppresses the per‑event `watch()` instead of querying first
and throwing the result away. Behaviour with the flag enabled is
unchanged.

**#2393 — `fix(Channel): guard channel methods against a disconnected
client`**
- Guarded `channel.lastRead()` in `useCreateChannelStateContext` with
`!channel.disconnected` so a disconnect while the channel is mounted
does not crash the render.
- `loadMore` now early‑returns when `channel.disconnected`, mirroring
the existing `markRead` guard, to avoid a doomed pagination query.
- Note: the shared‑client race cannot be fully eliminated (the existing
`try/catch` backstops on the async paths remain); this removes the
uncaught **render‑time** crash, which is the part with no safety net.

**#2599 — `fix(deps): require stream-chat ^9.49.0 to evict removed
channels`**
- Bumped `stream-chat` to `^9.49.0` in `peerDependencies` and
`devDependencies`. No `ChannelList` runtime change is required: the
existing `handleNotificationRemovedFromChannel` removes the channel from
the list, and `stream-chat@9.49.0` now evicts it from
`client.activeChannels`, so `recoverState()` won't re‑watch it on
reconnect. The two compose into durable removal.

**Tests** — added test‑first (each test was confirmed to fail before the
fix):
- `useIsChannelMuted` does not throw on an uninitialized channel, nor on
an initialized‑but‑disconnected channel.
- `notification.message_new` does not call `watch()` when
`allowNewMessagesFromUnfilteredChannels={false}`.
- rendering does not crash when the client disconnects while the channel
is mounted.
- `loadMore` does not query when the client is disconnected.
- **#2599:** `notification.removed_from_channel` evicts the channel from
`client.activeChannels` (so reconnect cannot re‑watch it). Verified
red→green — red on `stream-chat@9.47.0`, green after the `^9.49.0` bump.

Verification: `yarn test` (full suite — 2589 passing, incl. ChannelList,
ChannelListItem, Channel, MessageList, Thread), `yarn types`, and ESLint
(`--max-warnings 0`) all clean. #2474, #2441 and #2393 were additionally
confirmed in a real browser against the example app; the #2599
regression test passes against the bumped `stream-chat@9.49.0`.

### 🎨 UI Changes

None — these are crash / correctness / rate‑limit fixes in `ChannelList`
and `Channel` event handling, with no visual changes.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed message-new handling when unfiltered channels aren’t allowed so
unlisted/unqueried channels aren’t watched or added to the list.
* Made mute status lookups safe when a channel isn’t initialized or is
disconnected.
* Prevented pagination and last-read handling from running while the
client/channel is disconnected to avoid crashes.

* **Tests**
* Added coverage for message-new filtering with unfiltered channels
disabled.
  * Expanded mute hook tests for uninitialized and disconnected cases.
  * Added disconnected-client scenarios for rendering and pagination.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
… ESM (#3231)

### 🎯 Goal

The published ESM bundle imported dayjs plugins and locales via
**extensionless subpaths** (e.g. `import 'dayjs/plugin/calendar'`,
`import 'dayjs/locale/de'`). dayjs ships no `exports` map, so Node's
**native ESM resolver requires the file extension** and rejects these
specifiers with `ERR_MODULE_NOT_FOUND`.

Bundlers (Turbopack, Webpack, Vite) resolve extensionless subpaths fine,
so the bug is invisible in most setups. But Node's native loader does
not — and Next.js 16 loads `stream-chat-react` as a **server external**
during "Collecting page data", so the consumer build fails:

```
Failed to load external module stream-chat-react...: ERR_MODULE_NOT_FOUND
Cannot find module '.../dayjs/plugin/calendar' imported from .../useNotificationApi.<hash>.mjs
Did you mean to import "dayjs/plugin/calendar.js"?
```

**Regressed in #3188 — first shipped in `14.4.0`.** Verified by loading
each published `dist/es/index.mjs` under Node's native ESM loader:
`14.3.0` loads cleanly, `14.4.0` (and every release since) throws
`ERR_MODULE_NOT_FOUND → dayjs/plugin/calendar`. #3188 broadened the Vite
`external` subpath regex from `(\/[\w-]+)?` (one segment) to `(\/.+)?`
(any depth). `dayjs/plugin/calendar` is a two-segment subpath, so the
old regex didn't match it and Vite **bundled** the plugin inline (no
bare import in the output); the new regex **externalizes** it, emitting
the extensionless specifier verbatim. The extensionless imports had
always been in source — the build used to bundle them away.

Note that broadening the regex was itself a deliberate ESM-correctness
fix (keeping CJS `require()` glue out of the ESM output), so
externalizing subpaths is intended. This PR keeps the externalization
and makes the specifiers valid, rather than reverting it and
reintroducing the CJS-glue problem.

### 🛠 Implementation details

**The fix** — add `.js` to every dayjs subpath import in shipped source,
so the specifiers Vite externalizes (emits verbatim) are valid ESM:

- `src/i18n/Streami18n.ts` — 8 plugin imports + 12 locale side-effect
imports
- `src/context/TranslationContext.tsx` — 2 plugin imports
- `src/i18n/utils.ts` — 1 type-only import (keeps the emitted `.d.ts`
consistent)
- also corrected a stale JSDoc example and the `registerTranslation` log
message

`.js` is safe across every consumer: Node ESM (the fix), bundlers, and
TS types (`moduleResolution: "bundler"` maps `.js` → `.d.ts`). The CJS
output is unaffected.

**Regression guard** — the existing `validate-cjs` smoke test loads the
bundle with `require()`, whose CJS resolver tolerates extensionless
imports, so it never caught this. Added the missing ESM counterpart:

- `scripts/validate-esm-node-bundle.mjs` — imports `dist/es/index.mjs`
under Node's native loader
- `yarn validate-esm` script + a CI step in `ci.yml` right after
`validate-cjs`

Verified: `validate-esm` **passes** on a healthy build and **fails with
`ERR_MODULE_NOT_FOUND`** when a `.js` is stripped from a dayjs import in
the built output. `yarn build`, `yarn types`, ESLint, and the i18n tests
all pass.

**Also included — toolchain bump** (independent of the fix, bundled
here): `vite` 8.0.14 → 8.1.3, `vitest` + `@vitest/coverage-v8` 4.1.7 →
4.1.9, `@vitest/eslint-plugin` 1.6.18 → 1.6.20 (pulls rolldown 1.0.2 →
1.1.3); the `examples/*` workspaces bumped to match; `.yarnrc.yml`
lowers `npmMinimalAgeGate` to `1d` and preapproves `vite`, and drops
`enableHardenedMode`. Diffing `dist/es` + `dist/cjs` before/after the
bump shows **no behavioral or API-surface change** — only additional `/*
@__PURE__ */` annotations and one internal import alias, both from the
rolldown bump.

### 🎨 UI Changes

None — packaging/build fix, no runtime or visual behavior change.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved ESM bundle compatibility by updating import paths to use
explicit `.js` extensions.
  * Added validation to catch Node ESM import issues earlier during CI.
* Updated user-facing guidance for locale imports to match the new ESM
format.

* **Chores**
  * Updated several build and test tool versions.
* Adjusted package manager settings and example project dependencies for
the latest releases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## [14.6.1](v14.6.0...v14.6.1) (2026-07-03)

### Bug Fixes

* bug bashing ChannelList + Channel ([#2474](#2474), [#2441](#2441), [#2393](#2393)) ([#3227](#3227)) ([f790520](f790520)), closes [GetStream/stream-chat-js#1788](GetStream/stream-chat-js#1788) [#2599](#2599) [#2599](#2599)
* **i18n:** use .js extensions on dayjs subpath imports for valid Node ESM ([#3231](#3231)) ([7663b18](7663b18)), closes [#3188](#3188) [#3188](#3188)
* **MessageList:** don't count thread replies toward channel unread UI state ([#3229](#3229)) ([ca5ed16](ca5ed16))
* **renderText:** recognize uppercase URL schemes ([#3226](#3226)) ([21d57e3](21d57e3))
* **renderText:** recognize uppercase URL schemes in message links ([6178513](6178513))
* return background colors to LinkPreviewCard and TypingIndicator ([a768e30](a768e30))
# Conflicts:
#	.cursor/skills/ralph-protocol/SKILL.md
#	examples/vite/src/App.tsx
#	examples/vite/src/AppSettings/tabs/Reactions/ReactionsTab.tsx
#	examples/vite/src/index.scss
#	examples/vite/src/stream-imports-layout.scss
#	src/components/Attachment/Audio.tsx
#	src/components/Attachment/Geolocation.tsx
#	src/components/Attachment/Giphy.tsx
#	src/components/Attachment/__tests__/Attachment.test.tsx
#	src/components/Attachment/__tests__/Audio.test.tsx
#	src/components/Attachment/__tests__/Card.test.tsx
#	src/components/Attachment/__tests__/VoiceRecording.test.tsx
#	src/components/Attachment/hooks/useAudioController.ts
#	src/components/Channel/Channel.tsx
#	src/components/Channel/__tests__/Channel.test.tsx
#	src/components/Channel/channelState.ts
#	src/components/Channel/hooks/useCreateChannelStateContext.ts
#	src/components/Channel/hooks/useMentionsHandlers.ts
#	src/components/Channel/utils.ts
#	src/components/ChannelHeader/ChannelHeader.tsx
#	src/components/ChannelHeader/__tests__/ChannelHeader.test.tsx
#	src/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.ts
#	src/components/ChannelList/ChannelList.tsx
#	src/components/ChannelList/__tests__/ChannelList.test.tsx
#	src/components/ChannelList/index.ts
#	src/components/ChannelListItem/ChannelListItem.tsx
#	src/components/ChannelListItem/__tests__/ChannelListItem.test.tsx
#	src/components/ChannelPreview/ChannelPreviewMessenger.tsx
#	src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js
#	src/components/ChannelSearch/hooks/useChannelSearch.ts
#	src/components/Chat/Chat.tsx
#	src/components/Chat/__tests__/Chat.test.tsx
#	src/components/Chat/hooks/useChat.ts
#	src/components/Chat/hooks/useCreateChatContext.ts
#	src/components/ChatView/ChatView.tsx
#	src/components/ChatView/__tests__/ChatView.test.tsx
#	src/components/ChatView/styling/ChatView.scss
#	src/components/Gallery/__tests__/Image.test.js
#	src/components/Icons/createIcon.tsx
#	src/components/MediaRecorder/AudioRecorder/__tests__/AudioRecorder.test.js
#	src/components/MediaRecorder/hooks/useMediaRecorder.ts
#	src/components/Message/Message.tsx
#	src/components/Message/MessageAlsoSentInChannelIndicator.tsx
#	src/components/Message/MessageStatus.tsx
#	src/components/Message/MessageUI.tsx
#	src/components/Message/QuotedMessage.tsx
#	src/components/Message/__tests__/Message.test.tsx
#	src/components/Message/__tests__/MessageDeleted.test.js
#	src/components/Message/__tests__/MessageRepliesCountButton.test.tsx
#	src/components/Message/__tests__/MessageText.test.js
#	src/components/Message/__tests__/MessageUI.test.tsx
#	src/components/Message/__tests__/QuotedMessage.test.js
#	src/components/Message/hooks/__tests__/useActionHandler.test.tsx
#	src/components/Message/hooks/__tests__/useDeleteHandler.test.tsx
#	src/components/Message/hooks/__tests__/useFlagHandler.test.js
#	src/components/Message/hooks/__tests__/useMentionsHandler.test.tsx
#	src/components/Message/hooks/__tests__/useMuteHandler.test.js
#	src/components/Message/hooks/__tests__/useOpenThreadHandler.test.tsx
#	src/components/Message/hooks/__tests__/useReactionHandler.test.tsx
#	src/components/Message/hooks/__tests__/useRetryHandler.test.tsx
#	src/components/Message/hooks/__tests__/useUserRole.test.tsx
#	src/components/Message/hooks/__tests__/userMarkUnreadHandler.test.js
#	src/components/Message/hooks/useDeleteHandler.ts
#	src/components/Message/hooks/useMarkUnreadHandler.ts
#	src/components/Message/hooks/useMuteHandler.ts
#	src/components/Message/hooks/usePinHandler.ts
#	src/components/Message/hooks/useReactionHandler.ts
#	src/components/Message/hooks/useUserRole.ts
#	src/components/Message/styling/QuotedMessage.scss
#	src/components/Message/types.ts
#	src/components/MessageActions/__tests__/MessageActions.test.js
#	src/components/MessageActions/defaults.tsx
#	src/components/MessageActions/hooks/useBaseMessageActionSetFilter.ts
#	src/components/MessageComposer/AttachmentSelector/AttachmentSelector.tsx
#	src/components/MessageComposer/EditMessageForm.tsx
#	src/components/MessageComposer/MessageComposerActions.tsx
#	src/components/MessageComposer/QuotedMessagePreview.tsx
#	src/components/MessageComposer/__tests__/AttachmentSelector.test.tsx
#	src/components/MessageComposer/__tests__/ThreadMessageInput.test.tsx
#	src/components/MessageComposer/hooks/index.ts
#	src/components/MessageComposer/hooks/useCooldownTimer.tsx
#	src/components/MessageComposer/hooks/useMessageComposerBindings.ts
#	src/components/MessageComposer/hooks/useMessageComposerController.ts
#	src/components/MessageComposer/hooks/useSendMessageFn.ts
#	src/components/MessageComposer/hooks/useSubmitHandler.ts
#	src/components/MessageComposer/hooks/useUpdateMessageFn.ts
#	src/components/MessageComposer/styling/SendToChannelCheckbox.scss
#	src/components/MessageInput/hooks/__tests__/useCooldownTimer.test.js
#	src/components/MessageList/MessageList.tsx
#	src/components/MessageList/MessageListNotifications.tsx
#	src/components/MessageList/ScrollToLatestMessageButton.tsx
#	src/components/MessageList/UnreadMessagesNotification.tsx
#	src/components/MessageList/UnreadMessagesSeparator.tsx
#	src/components/MessageList/VirtualizedMessageList.tsx
#	src/components/MessageList/VirtualizedMessageListComponents.tsx
#	src/components/MessageList/__tests__/MessageList.test.js
#	src/components/MessageList/__tests__/ScrollToLatestMessageButton.test.tsx
#	src/components/MessageList/__tests__/VirtualizedMessageListComponents.test.tsx
#	src/components/MessageList/__tests__/utils.test.ts
#	src/components/MessageList/hooks/MessageList/useFloatingDateSeparatorMessageList.ts
#	src/components/MessageList/hooks/MessageList/useMessageListScrollManager.ts
#	src/components/MessageList/hooks/MessageList/useScrollLocationLogic.tsx
#	src/components/MessageList/hooks/__tests__/useMarkRead.test.tsx
#	src/components/MessageList/hooks/useMarkRead.ts
#	src/components/Modal/CloseButtonOnModalOverlay.tsx
#	src/components/Poll/PollActions/PollResults/PollOptionWithVotes.tsx
#	src/components/Poll/PollCreationDialog/PollCreationDialogControls.tsx
#	src/components/Poll/PollOptionSelector.tsx
#	src/components/Poll/__tests__/Poll.test.tsx
#	src/components/Poll/__tests__/PollActions.test.tsx
#	src/components/Poll/__tests__/PollOptionList.test.tsx
#	src/components/Reactions/ReactionSelectorWithButton.tsx
#	src/components/Search/SearchResults/SearchResultItem.tsx
#	src/components/Search/__tests__/SearchResultItem.test.tsx
#	src/components/TextareaComposer/TextareaComposer.tsx
#	src/components/Thread/Thread.tsx
#	src/components/Thread/ThreadHead.tsx
#	src/components/Thread/ThreadHeader.tsx
#	src/components/Thread/ThreadStart.tsx
#	src/components/Thread/__tests__/Thread.test.tsx
#	src/components/Thread/__tests__/ThreadStart.test.tsx
#	src/components/Threads/ThreadContext.tsx
#	src/components/Threads/ThreadList/ThreadList.tsx
#	src/components/Threads/ThreadList/ThreadListItemUI.tsx
#	src/components/Threads/ThreadList/__tests__/ThreadList.test.tsx
#	src/components/TypingIndicator/TypingIndicator.tsx
#	src/components/TypingIndicator/__tests__/TypingIndicator.test.js
#	src/components/Window/__tests__/Window.test.tsx
#	src/context/ChannelActionContext.tsx
#	src/context/ChannelStateContext.tsx
#	src/context/ChatContext.tsx
#	src/context/MessageBounceContext.tsx
#	src/context/MessageContext.tsx
#	src/context/index.ts
#	src/i18n/de.json
#	src/i18n/en.json
#	src/i18n/es.json
#	src/i18n/fr.json
#	src/i18n/hi.json
#	src/i18n/it.json
#	src/i18n/ja.json
#	src/i18n/ko.json
#	src/i18n/nl.json
#	src/i18n/pt.json
#	src/i18n/ru.json
#	src/i18n/tr.json
#	src/stories/add-message.stories.tsx
#	src/stories/attachment-sizing.stories.tsx
#	src/stories/edit-message.stories.tsx
#	src/stories/jump-to-message.stories.tsx
#	src/stories/mark-read.stories.tsx
#	src/stories/navigate-long-message-lists.stories.tsx
#	src/stories/pin-message.stories.tsx
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

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

Run ID: 349e631c-97f0-45e5-9110-ddd559afbbc8

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/message-paginator-master-merge

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.

@MartinCupela MartinCupela marked this pull request as draft July 8, 2026 10:36
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.

8 participants