feat: add enhanced mentions#3189
Conversation
# Conflicts: # src/components/Message/__tests__/__snapshots__/MessageText.test.tsx.snap # src/components/Message/renderText/rehypePlugins/mentionsMarkdownPlugin.ts # src/components/Message/renderText/renderText.tsx
b154ab1 to
8bc5ceb
Compare
f59965d to
70241a5
Compare
# Conflicts: # src/components/Message/__tests__/__snapshots__/MessageText.test.tsx.snap
📝 WalkthroughWalkthroughThis PR introduces a reusable ListItemLayout component for polymorphic list rendering, expands the mention system to support channels, roles, here notifications, and user groups alongside users, and refactors suggestion list mention rendering with dedicated components for each mention type. Message rendering now passes enriched mention metadata to support more expressive mention display and matching. ChangesListItemLayout Component and Icon Infrastructure
Mention System Refactor and Message Integration
Suggestion List Component Refactoring
Internationalization Updates
🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/Message/MessageText.tsx (1)
81-83:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGate mention keyboard interaction on all rendered mention entities, not just users.
renderTextMentionEntitiesnow includes channel/here/role/user-group mentions, butisMentionsInteractionEnabledstill keys offmessage.mentioned_usersonly. A message that contains only@channel,@here, a role, or a user group will render mention spans, yet the inner wrapper stays unfocusable and Enter/Space never reachesonMentionsClickMessage.Suggested change
- const hasMentionedUsers = Boolean(message.mentioned_users?.length); + const hasMentions = renderTextMentionEntities.length > 0; const isMentionsInteractionEnabled = - hasMentionedUsers && typeof onMentionsClickMessage === 'function'; + hasMentions && typeof onMentionsClickMessage === 'function';🤖 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 `@src/components/Message/MessageText.tsx` around lines 81 - 83, The keyboard-interaction gating currently uses hasMentionedUsers and thus misses non-user mentions; replace that check with a broader detection (e.g., hasMentionEntities) that returns true when any mention entity arrays exist or when renderTextMentionEntities would produce mention spans — check fields like message.mentioned_users, message.mentioned_roles, message.mentioned_groups, message.mentioned_channels, and any "everyone"/"here"/"channel" indicator (or fallback to examining the rendered entities output) and use that in isMentionsInteractionEnabled alongside typeof onMentionsClickMessage to ensure Enter/Space are focusable and routed to onMentionsClickMessage for all mention types referenced by renderTextMentionEntities.
🧹 Nitpick comments (5)
src/components/TextareaComposer/SuggestionList/MentionItem/UserGroupItem.tsx (1)
17-17: ⚡ Quick winRemove unnecessary void statement.
The
void focused;statement appears to be dead code sincefocusedis actually used on line 34 (selected={focused}). This statement serves no purpose and should be removed for clarity.♻️ Proposed fix
...buttonProps }: UserGroupItemProps) => { - void focused; - return (🤖 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 `@src/components/TextareaComposer/SuggestionList/MentionItem/UserGroupItem.tsx` at line 17, Remove the stray "void focused;" dead statement in UserGroupItem.tsx; the variable focused is actually used later (e.g., passed to selected={focused}) so simply delete that line to avoid the no-op and keep the code clear while leaving the focused variable usage intact.src/components/TextareaComposer/SuggestionList/TokenizedSuggestionParts.tsx (1)
13-31: ⚡ Quick winConsider memoizing this component for better autocomplete performance.
TokenizedSuggestionPartswill re-render whenever its parent suggestion item re-renders during autocomplete typing. Since this is in the hot path of the suggestion dropdown, wrapping it withReact.memowould prevent unnecessary re-renders whentokenizedDisplayNamehasn't changed.♻️ Suggested memoization
-export const TokenizedSuggestionParts = ({ +export const TokenizedSuggestionParts = React.memo(({ tokenizedDisplayName, -}: TokenizedSuggestionPartsProps) => +}: TokenizedSuggestionPartsProps) => { + return ( + <> + {tokenizedDisplayName.parts.map((part, i) => { - tokenizedDisplayName.parts.map((part, i) => { const matches = part.toLowerCase() === tokenizedDisplayName.token; const partWithHTMLSpacesAround = part.replace(/^\s+|\s+$/g, '\u00A0'); return ( <span className={clsx({ 'str-chat__emoji-item-part': !matches, 'str-chat__suggestion-item-part--match': matches, })} key={`part-${i}`} > {partWithHTMLSpacesAround} </span> ); - }); + })} + </> + ); +});🤖 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 `@src/components/TextareaComposer/SuggestionList/TokenizedSuggestionParts.tsx` around lines 13 - 31, Wrap the TokenizedSuggestionParts functional component with React.memo so it only re-renders when its props change; specifically memoize the exported TokenizedSuggestionParts (which takes tokenizedDisplayName: TokenizedSuggestionPartsProps) to avoid re-rendering on parent updates during autocomplete and ensure the memo key uses the default shallow prop comparison (or provide a custom comparison that deeply compares tokenizedDisplayName if necessary).src/components/MessageComposer/__tests__/MessageInput.test.tsx (1)
1311-1379: ⚡ Quick winCover the actual wraparound branch.
This test sets
hasNext: true, but it never moves past the last loaded suggestion, so it does not exercise the newnextIndex >= loadedItems.lengthpath insrc/components/TextareaComposer/TextareaComposer.tsxLine 209. Add one moreArrowDownfrom@hereand assert that selection wraps back to@channel; otherwise the changed boundary behavior can regress unnoticed.🤖 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 `@src/components/MessageComposer/__tests__/MessageInput.test.tsx` around lines 1311 - 1379, Test does not exercise the wraparound branch (nextIndex >= loadedItems.length) in TextareaComposer.tsx; update the MessageInput.test.tsx test so after selecting '`@here`' you fire one more ArrowDown key event to move past the last loaded suggestion, then assert the selection wraps to '`@channel`' (e.g., fire another fireEvent.keyDown(formElement, { key: 'ArrowDown' }) before confirming with Enter and expect formElement toHaveValue('`@channel` ')). Reference the suggestions/searchSource usage in this test and the nextIndex >= loadedItems.length behavior in TextareaComposer.tsx when adding the extra ArrowDown + assertion.src/components/MessageComposer/QuotedMessagePreview.tsx (1)
409-411: 💤 Low valueConsider removing redundant
mentioned_usersparameter.The
renderTextcall passes bothquotedMessage?.mentioned_users(deprecated parameter) andmessageMentionEntities: quotedMessageMentionEntities(new option), wherequotedMessageMentionEntitiesalready includes the mentioned users. SincerenderTextderives entities frommessageMentionEntitieswhen present (line 149-151 in renderText.tsx), the deprecated parameter is unused here.♻️ Simplify by removing redundant parameter
- renderedText = renderText(quotedMessageText, quotedMessage?.mentioned_users, { + renderedText = renderText(quotedMessageText, undefined, { messageMentionEntities: quotedMessageMentionEntities, });🤖 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 `@src/components/MessageComposer/QuotedMessagePreview.tsx` around lines 409 - 411, The renderText call is passing a deprecated second argument quotedMessage?.mentioned_users while also supplying messageMentionEntities: quotedMessageMentionEntities in the options; because renderText prefers messageMentionEntities (see renderText), remove the redundant quotedMessage?.mentioned_users argument from the renderedText invocation and rely solely on the options object (messageMentionEntities: quotedMessageMentionEntities) so entities are derived from quotedMessageMentionEntities; update the call site in QuotedMessagePreview.tsx where renderedText = renderText(quotedMessageText, quotedMessage?.mentioned_users, { messageMentionEntities: quotedMessageMentionEntities }) to the single-argument form using the options object.src/components/Message/types.ts (1)
73-74: ⚡ Quick winUpdate the
renderTextprop docs to match the new contract.This prop now exposes
RenderTextFunction, but the JSDoc still points to the old utils location and doesn't mention the newoptions.messageMentionEntitiespath or the deprecatedmentionedUsersargument. Please refresh the inline docs here, and any matching guide page if this prop is documented externally. As per coding guidelines,src/**/*.{ts,tsx}: Ensure public API changes include documentation updates.🤖 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 `@src/components/Message/types.ts` around lines 73 - 74, Update the JSDoc for the renderText?: RenderTextFunction prop to reflect the new contract: remove the old utils link, describe that the function receives (text, options) where mention data is now available at options.messageMentionEntities (and note the mentionedUsers argument is deprecated), and briefly document expected return values/behavior; update any matching external docs or guide pages that reference the old signature to use RenderTextFunction and the new options.messageMentionEntities path.
🤖 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.
Inline comments:
In `@src/components/Avatar/__tests__/Avatar.test.tsx`:
- Around line 69-74: The test in Avatar.test.tsx expects the wrong CSS class for
custom fallback icons; update the assertion in the 'should render a custom
fallback icon when provided and no initials are available' case to look for the
class produced by createIcon (use 'str-chaticon--megaphone' for IconMegaphone)
instead of '.str-chat__icon--megaphone' so the check on the rendered Avatar (use
AVATAR_ROOT_TEST_ID and the FallbackIcon=IconMegaphone usage) matches the
createIcon naming convention.
In `@src/components/ListItemLayout/styling/ListItemLayout.scss`:
- Around line 3-10: The stylesheet is failing stylelint's
declaration-empty-line-before rule for the custom property and the later
declarations; add a blank line immediately before the flagged declarations to
satisfy the rule. Specifically, insert an empty line before the
--list-item-padding declaration in the .str-chat__list-item-layout selector and
likewise add an empty line before the other flagged declaration(s) (e.g., the
padding declaration around the same selector or at the later block near lines
38–40) so the rule no longer reports errors.
In `@src/components/Message/__tests__/MessageText.test.tsx`:
- Around line 377-405: Add assertions to the existing test ("renders built-in,
role, and user-group mentions with mention styling") to verify keyboard
accessibility: for each mention found via getByText('`@channel`'),
getByText('`@here`'), getByText('`@admin`'), and getByText('`@Backend` Team') locate
the surrounding mention wrapper (e.g.,
element.closest('.str-chat__message-mention') or the element returned by
renderMessageText) and assert it is focusable by checking it has tabindex="0"
(or element.tabIndex === 0); ensure these extra expect checks are added after
the current attribute assertions and before the axe accessibility check so the
regression for focus/Enter on non-user mentions is covered.
In `@src/components/Message/MessageText.tsx`:
- Around line 55-57: In MessageText.tsx, replace the incorrect use of
message.mentioned_groups with message.mentioned_group_ids when calling
getRenderTextMentionEntities and update the helper/types used by
getRenderTextMentionEntities to accept mentioned_group_ids (or map
mentioned_group_ids -> expected shape) so TypeScript no longer reports TS2551;
also ensure isMentionsInteractionEnabled’s gating logic is updated so mention
interaction is enabled for group mentions (not just mentioned_users) when those
entities are rendered.
In `@src/components/TextareaComposer/styling/SuggestionList.scss`:
- Around line 42-60: The SCSS violates stylelint's declaration-empty-line-before
rule: in .str-chat__suggestion-list__item-title and
.str-chat__suggestion-list__item-details, add a blank line before any property
declaration that immediately follows an `@include` (i.e., ensure there is an empty
line between the `@include` utils.ellipsis-text; and the next declaration such as
text-align: start; or color: ...), so update those selectors (and similarly
.str-chat__list-item-layout__title.str-chat__suggestion-list__mention-item-title
if needed) to insert the required empty lines after mixins to satisfy linting.
In `@src/components/TextareaComposer/SuggestionList/SuggestionList.tsx`:
- Around line 223-235: The current compatibility check uses the translated
string value via legacyUserSuggestionsLabel which relies on locale bundles;
instead update the mentions branch in the suggestionMenuLabel computation to
resolve the new key with the old key as a fallback by calling t('aria/Mention
Suggestions', { defaultValue: t('aria/User Suggestions') }), remove the runtime
comparison to legacyUserSuggestionsLabel, and keep the other branches that use
t('aria/Command Suggestions') and t('aria/Emoji Suggestions') unchanged so
suggestions.searchSource.type still drives selection.
In `@src/i18n/ja.json`:
- Line 144: Replace the English value for the JSON key "aria/User Suggestions"
with the correct Japanese translation (e.g., "ユーザー候補") so the value is in
Japanese (not "aria/User Suggestions"); update the entry for "aria/User
Suggestions" in the ja.json translations and then run yarn validate-translations
to ensure the file passes validation.
In `@src/i18n/ko.json`:
- Line 144: The translation for the JSON key "aria/User Suggestions" currently
uses the English key as its value; update the value to the correct Korean
translation (e.g., "사용자 제안") for the key "aria/User Suggestions" in
src/i18n/ko.json, ensure the value is a non-empty Korean string consistent with
other aria entries (see "aria/Mention Suggestions"), and run yarn
validate-translations after the change.
In `@src/i18n/nl.json`:
- Line 145: The JSON entry for the translation key "aria/User Suggestions"
currently uses the English key as its value; replace that value with the correct
Dutch translation (e.g., "Gebruikerssuggesties") for the "aria/User Suggestions"
key in the nl.json translations so Dutch users see localized text, then run yarn
validate-translations to ensure all translations are valid.
In `@src/i18n/pt.json`:
- Line 153: Update the translation value for the JSON key "aria/User
Suggestions" so it is a Portuguese string (e.g., "Sugestões de usuários")
instead of the English key; locate the "aria/User Suggestions" entry in the
pt.json translations and replace the value with the correct Portuguese
translation, ensure the string is non-empty and matches style of other entries
like "aria/Mention Suggestions", then run `yarn validate-translations` to
confirm no validation errors.
In `@src/i18n/ru.json`:
- Line 162: The JSON key "aria/User Suggestions" has an untranslated value;
update its value to the correct Russian string (e.g., the localized aria label
for "User Suggestions") in the ru.json entry for "aria/User Suggestions", then
search other src/i18n/*.json files for any identical untranslated key/value
pairs and correct them as needed; finally run yarn validate-translations to
ensure all locale files pass validation.
---
Outside diff comments:
In `@src/components/Message/MessageText.tsx`:
- Around line 81-83: The keyboard-interaction gating currently uses
hasMentionedUsers and thus misses non-user mentions; replace that check with a
broader detection (e.g., hasMentionEntities) that returns true when any mention
entity arrays exist or when renderTextMentionEntities would produce mention
spans — check fields like message.mentioned_users, message.mentioned_roles,
message.mentioned_groups, message.mentioned_channels, and any
"everyone"/"here"/"channel" indicator (or fallback to examining the rendered
entities output) and use that in isMentionsInteractionEnabled alongside typeof
onMentionsClickMessage to ensure Enter/Space are focusable and routed to
onMentionsClickMessage for all mention types referenced by
renderTextMentionEntities.
---
Nitpick comments:
In `@src/components/Message/types.ts`:
- Around line 73-74: Update the JSDoc for the renderText?: RenderTextFunction
prop to reflect the new contract: remove the old utils link, describe that the
function receives (text, options) where mention data is now available at
options.messageMentionEntities (and note the mentionedUsers argument is
deprecated), and briefly document expected return values/behavior; update any
matching external docs or guide pages that reference the old signature to use
RenderTextFunction and the new options.messageMentionEntities path.
In `@src/components/MessageComposer/__tests__/MessageInput.test.tsx`:
- Around line 1311-1379: Test does not exercise the wraparound branch (nextIndex
>= loadedItems.length) in TextareaComposer.tsx; update the MessageInput.test.tsx
test so after selecting '`@here`' you fire one more ArrowDown key event to move
past the last loaded suggestion, then assert the selection wraps to '`@channel`'
(e.g., fire another fireEvent.keyDown(formElement, { key: 'ArrowDown' }) before
confirming with Enter and expect formElement toHaveValue('`@channel` ')).
Reference the suggestions/searchSource usage in this test and the nextIndex >=
loadedItems.length behavior in TextareaComposer.tsx when adding the extra
ArrowDown + assertion.
In `@src/components/MessageComposer/QuotedMessagePreview.tsx`:
- Around line 409-411: The renderText call is passing a deprecated second
argument quotedMessage?.mentioned_users while also supplying
messageMentionEntities: quotedMessageMentionEntities in the options; because
renderText prefers messageMentionEntities (see renderText), remove the redundant
quotedMessage?.mentioned_users argument from the renderedText invocation and
rely solely on the options object (messageMentionEntities:
quotedMessageMentionEntities) so entities are derived from
quotedMessageMentionEntities; update the call site in QuotedMessagePreview.tsx
where renderedText = renderText(quotedMessageText,
quotedMessage?.mentioned_users, { messageMentionEntities:
quotedMessageMentionEntities }) to the single-argument form using the options
object.
In
`@src/components/TextareaComposer/SuggestionList/MentionItem/UserGroupItem.tsx`:
- Line 17: Remove the stray "void focused;" dead statement in UserGroupItem.tsx;
the variable focused is actually used later (e.g., passed to selected={focused})
so simply delete that line to avoid the no-op and keep the code clear while
leaving the focused variable usage intact.
In `@src/components/TextareaComposer/SuggestionList/TokenizedSuggestionParts.tsx`:
- Around line 13-31: Wrap the TokenizedSuggestionParts functional component with
React.memo so it only re-renders when its props change; specifically memoize the
exported TokenizedSuggestionParts (which takes tokenizedDisplayName:
TokenizedSuggestionPartsProps) to avoid re-rendering on parent updates during
autocomplete and ensure the memo key uses the default shallow prop comparison
(or provide a custom comparison that deeply compares tokenizedDisplayName if
necessary).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: aea911be-dcf9-4f80-a8da-7398c0d0a4b0
⛔ Files ignored due to path filters (1)
src/components/Message/renderText/__tests__/__snapshots__/renderText.test.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (51)
src/components/Avatar/Avatar.tsxsrc/components/Avatar/__tests__/Avatar.test.tsxsrc/components/Button/styling/Button.scsssrc/components/Icons/icons.tsxsrc/components/ListItemLayout/ListItemLayout.tsxsrc/components/ListItemLayout/__tests__/ListItemLayout.test.tsxsrc/components/ListItemLayout/index.tssrc/components/ListItemLayout/styling/ListItemLayout.scsssrc/components/ListItemLayout/styling/index.scsssrc/components/Message/MessageText.tsxsrc/components/Message/__tests__/MessageText.test.tsxsrc/components/Message/__tests__/QuotedMessage.test.tsxsrc/components/Message/renderText/__tests__/renderText.test.tsxsrc/components/Message/renderText/componentRenderers/Mention.tsxsrc/components/Message/renderText/rehypePlugins/mentionsMarkdownPlugin.tssrc/components/Message/renderText/renderText.tsxsrc/components/Message/types.tssrc/components/MessageComposer/QuotedMessagePreview.tsxsrc/components/MessageComposer/__tests__/MessageInput.test.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/BroadcastMentionItem.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/MentionItem.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/MentionSuggestionTitle.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/RoleItem.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/SpecialMentionItem.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/UserGroupItem.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/UserItem.tsxsrc/components/TextareaComposer/SuggestionList/MentionItem/index.tssrc/components/TextareaComposer/SuggestionList/MentionItem/types.tssrc/components/TextareaComposer/SuggestionList/SuggestionList.tsxsrc/components/TextareaComposer/SuggestionList/SuggestionListItem.tsxsrc/components/TextareaComposer/SuggestionList/TokenizedSuggestionParts.tsxsrc/components/TextareaComposer/SuggestionList/UserItem.tsxsrc/components/TextareaComposer/SuggestionList/index.tssrc/components/TextareaComposer/TextareaComposer.tsxsrc/components/TextareaComposer/__tests__/MentionItem.test.tsxsrc/components/TextareaComposer/styling/SuggestionList.scsssrc/components/index.tssrc/context/MessageContext.tsxsrc/i18n/de.jsonsrc/i18n/en.jsonsrc/i18n/es.jsonsrc/i18n/fr.jsonsrc/i18n/hi.jsonsrc/i18n/it.jsonsrc/i18n/ja.jsonsrc/i18n/ko.jsonsrc/i18n/nl.jsonsrc/i18n/pt.jsonsrc/i18n/ru.jsonsrc/i18n/tr.jsonsrc/styling/index.scss
💤 Files with no reviewable changes (1)
- src/components/TextareaComposer/SuggestionList/UserItem.tsx
| it('should render a custom fallback icon when provided and no initials are available', () => { | ||
| const { getByTestId } = render(<Avatar FallbackIcon={IconMegaphone} size='md' />); | ||
| const root = getByTestId(AVATAR_ROOT_TEST_ID); | ||
|
|
||
| expect(root.querySelector('.str-chat__icon--megaphone')).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the createIcon implementation to confirm class name generation
cat src/components/Icons/createIcon.tsxRepository: GetStream/stream-chat-react
Length of output: 1923
🌐 Web query:
GetStream stream-chat-react createIcon.tsx str-chat__icon-- class name generation
💡 Result:
In the stream-chat-react library, the str-chaticon-- class name prefix is generated dynamically based on the icon name provided to the createIcon utility function [1][2]. The createIcon function utilizes a helper function named toIconClass(name: string) to transform the icon name into the appropriate CSS class [1]. For instance, an icon named IconArrowLeft will be processed to generate a class similar to str-chaticon--arrow-left [1][2]. Key points regarding this generation process: 1. Dynamic Generation: The toIconClass function performs string manipulation on the icon's identifier (often removing the "Icon" prefix and converting camelCase to kebab-case) to ensure the resulting CSS class adheres to the project's BEM-like naming convention [1][2]. 2. CSS Mapping: These auto-derived class names correspond to selectors defined in the project's SCSS files (e.g., Icons.scss), which handle styling such as sizing, coloring, and RTL (right-to-left) transformations [1][3]. 3. History: Recent updates to the library (such as the migration to the Phosphor icon set) involved updating these constants, which in turn updated the auto-derived CSS class names across the application [2]. Developers customizing these icons must ensure their CSS selectors match these generated class names [4]. While stream-chat-css historically relied on Fontello to generate icon fonts with this specific prefix [5][6], the modern stream-chat-react implementation uses the createIcon utility to manage icon components and their associated classes directly within the React package [1][2].
Citations:
- 1: a4b1c26
- 2: 30c1bee
- 3: fix(Icons, RTL): update icon catalog, RTL layout fixes, dark mode & thread voice recording #3090
- 4: https://getstream.io/chat/docs/sdk/angular/v5/theming/themingv2/
- 5: https://github.com/GetStream/stream-chat-css/blob/main/README.md
- 6: https://github.com/GetStream/stream-chat-css
Update Avatar fallback icon test to match createIcon CSS class naming
createIcon derives icon classes using str-chaticon--<kebab-cased-icon-name> (e.g., IconArrowLeft → str-chaticon--arrow-left), so the expectation should not use .str-chat__icon--megaphone; update the test to the str-chaticon--megaphone (or whatever the generated prefix is) class.
🤖 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 `@src/components/Avatar/__tests__/Avatar.test.tsx` around lines 69 - 74, The
test in Avatar.test.tsx expects the wrong CSS class for custom fallback icons;
update the assertion in the 'should render a custom fallback icon when provided
and no initials are available' case to look for the class produced by createIcon
(use 'str-chaticon--megaphone' for IconMegaphone) instead of
'.str-chat__icon--megaphone' so the check on the rendered Avatar (use
AVATAR_ROOT_TEST_ID and the FallbackIcon=IconMegaphone usage) matches the
createIcon naming convention.
| .str-chat__list-item-layout { | ||
| --list-item-padding: var(--str-chat__spacing-xs) var(--str-chat__spacing-sm); | ||
| display: flex; | ||
| align-items: center; | ||
| gap: var(--str-chat__spacing-sm); | ||
| text-align: start; | ||
| padding: var(--list-item-padding); | ||
| width: 100%; |
There was a problem hiding this comment.
Fix the current Stylelint failures in this file.
Lines 5 and 40 are already flagged by declaration-empty-line-before, so this stylesheet will not pass lint as written.
Suggested change
.str-chat__list-item-layout {
--list-item-padding: var(--str-chat__spacing-xs) var(--str-chat__spacing-sm);
+
display: flex;
align-items: center;
gap: var(--str-chat__spacing-sm);
@@
&:is(button) {
`@include` utils.button-reset;
+
padding: var(--list-item-padding);
cursor: pointer;Also applies to: 38-40
🧰 Tools
🪛 Stylelint (17.12.0)
[error] 5-5: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 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 `@src/components/ListItemLayout/styling/ListItemLayout.scss` around lines 3 -
10, The stylesheet is failing stylelint's declaration-empty-line-before rule for
the custom property and the later declarations; add a blank line immediately
before the flagged declarations to satisfy the rule. Specifically, insert an
empty line before the --list-item-padding declaration in the
.str-chat__list-item-layout selector and likewise add an empty line before the
other flagged declaration(s) (e.g., the padding declaration around the same
selector or at the later block near lines 38–40) so the rule no longer reports
errors.
| it('renders built-in, role, and user-group mentions with mention styling', async () => { | ||
| const text = 'Hello @channel @here @admin @Backend Team'; | ||
| const message = generateAliceMessage({ | ||
| mentioned_channel: true, | ||
| mentioned_groups: [ | ||
| fromPartial({ | ||
| created_at: '2026-05-28T00:00:00.000Z', | ||
| id: 'backend-team', | ||
| name: 'Backend Team', | ||
| updated_at: '2026-05-28T00:00:00.000Z', | ||
| }), | ||
| ], | ||
| mentioned_here: true, | ||
| mentioned_roles: ['admin'], | ||
| text, | ||
| }); | ||
| const { container, getByText } = await renderMessageText({ | ||
| customProps: { message }, | ||
| }); | ||
|
|
||
| expect(getByText('@channel')).toHaveAttribute('data-mention-type', 'channel'); | ||
| expect(getByText('@here')).toHaveAttribute('data-mention-type', 'here'); | ||
| expect(getByText('@admin')).toHaveAttribute('data-mention-type', 'role'); | ||
| expect(getByText('@Backend Team')).toHaveAttribute('data-mention-type', 'user_group'); | ||
| expect(container.querySelectorAll('.str-chat__message-mention')).toHaveLength(4); | ||
|
|
||
| const results = await axe(container); | ||
| expect(results).toHaveNoViolations(); | ||
| }); |
There was a problem hiding this comment.
Extend this regression to cover keyboard accessibility for non-user mentions.
This test proves the spans render, but it won't catch the current focus/Enter regression for messages that only contain @channel/role/group mentions. Please assert the inner wrapper becomes focusable for this payload so the accessibility path stays covered.
🤖 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 `@src/components/Message/__tests__/MessageText.test.tsx` around lines 377 -
405, Add assertions to the existing test ("renders built-in, role, and
user-group mentions with mention styling") to verify keyboard accessibility: for
each mention found via getByText('`@channel`'), getByText('`@here`'),
getByText('`@admin`'), and getByText('`@Backend` Team') locate the surrounding
mention wrapper (e.g., element.closest('.str-chat__message-mention') or the
element returned by renderMessageText) and assert it is focusable by checking it
has tabindex="0" (or element.tabIndex === 0); ensure these extra expect checks
are added after the current attribute assertions and before the axe
accessibility check so the regression for focus/Enter on non-user mentions is
covered.
| getRenderTextMentionEntities({ | ||
| mentioned_channel: message.mentioned_channel, | ||
| mentioned_groups: message.mentioned_groups, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Enhanced mention fields referenced in message surfaces:"
rg -n --type=ts --type=tsx '\bmentioned_(channel|groups|here|roles|users)\b' src/components/Message src/context
echo
echo "Current LocalMessage usages on message surfaces:"
rg -n --type=ts --type=tsx '\bLocalMessage\b' src/components/Message src/contextRepository: GetStream/stream-chat-react
Length of output: 1923
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Check for enhanced mention fields in MessageText.tsx:"
rg -n --type=ts --type=tsx '\bmentioned_(channel|groups|here|roles|users)\b' src/components/Message/MessageText.tsx || true
echo
echo "Check for LocalMessage typing in MessageText.tsx:"
rg -n --type=ts --type=tsx '\bLocalMessage\b' src/components/Message/MessageText.tsx || trueRepository: GetStream/stream-chat-react
Length of output: 1942
🌐 Web query:
stream-chat LocalMessage mentioned_groups field TypeScript
💡 Result:
In the GetStream Chat SDK for JavaScript and TypeScript, the field used to handle group mentions is named mentioned_group_ids, not mentioned_groups [1][2][3]. This field is used when sending a message to notify members of specific user groups who are also members of the channel [1][2]. It accepts an array of strings representing the unique IDs of the user groups (with a maximum of 10 groups) [1][2]. While the term mentioned_groups is commonly used in conversation to describe this feature, the technical implementation within the Message payload and the SDK's type definitions strictly uses mentioned_group_ids [1][2][3]. If you are looking at LocalMessage or other message-related types in the TypeScript SDK, you should map or reference this field as mentioned_group_ids [4][5]. Example usage: const groupMessage = await channel.sendMessage({ text: "Design team, please review!", mentioned_group_ids: ["design-team-id"], });
Citations:
- 1: https://getstream.io/chat/docs/react/send-message/
- 2: https://getstream.io/chat/docs/javascript/send_message/
- 3: https://getstream.io/chat/docs/react/send_message/
- 4: https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/dist/types/types.d.ts
- 5: https://cdn.jsdelivr.net/npm/stream-chat@9.38.0/src/utils.ts
Fix mention group field typing: use mentioned_group_ids instead of mentioned_groups.
src/components/Message/MessageText.tsxpassesmessage.mentioned_groupsintogetRenderTextMentionEntities, but Stream Chat’s message/types usementioned_group_idsfor group mentions; rename/map this consistently (and align the mention-entity helper/types) to resolve the TS2551 merge blocker.- Re-check keyboard/focus gating (
isMentionsInteractionEnabled) so interaction is enabled for mention entities beyondmentioned_userswhen those entities are rendered.
🧰 Tools
🪛 GitHub Actions: Size / 0_Compressed Size.txt
[error] 57-57: TypeScript (TS2551) error: Property 'mentioned_groups' does not exist on type 'LocalMessage'. Did you mean 'mentioned_roles'?
🪛 GitHub Actions: Size / Compressed Size
[error] 57-57: TypeScript (TS2551): Property 'mentioned_groups' does not exist on type 'LocalMessage'. Did you mean 'mentioned_roles'?
🤖 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 `@src/components/Message/MessageText.tsx` around lines 55 - 57, In
MessageText.tsx, replace the incorrect use of message.mentioned_groups with
message.mentioned_group_ids when calling getRenderTextMentionEntities and update
the helper/types used by getRenderTextMentionEntities to accept
mentioned_group_ids (or map mentioned_group_ids -> expected shape) so TypeScript
no longer reports TS2551; also ensure isMentionsInteractionEnabled’s gating
logic is updated so mention interaction is enabled for group mentions (not just
mentioned_users) when those entities are rendered.
| .str-chat__suggestion-list__item-title { | ||
| @include fonts.text-caption-default; | ||
| @include utils.ellipsis-text; | ||
| text-align: start; | ||
| min-width: 0; | ||
| } | ||
|
|
||
| .str-chat__list-item-layout__title.str-chat__suggestion-list__mention-item-title { | ||
| font: var(--str-chat__font-body-default); | ||
| } | ||
|
|
||
| .str-chat__suggestion-list__item-details { | ||
| @include fonts.text-metadata-default; | ||
| @include utils.ellipsis-text; | ||
| color: var(--str-chat__text-tertiary); | ||
| text-align: start; | ||
| min-width: 0; | ||
| } | ||
| } |
There was a problem hiding this comment.
Fix stylelint violations: add empty lines before declarations following mixins.
The declaration-empty-line-before rule requires an empty line before declarations that follow @include statements. Lines 45 and 56 violate this rule.
🎨 Proposed fix
.str-chat__suggestion-list__item-title {
`@include` fonts.text-caption-default;
`@include` utils.ellipsis-text;
+
text-align: start;
min-width: 0;
}
.str-chat__list-item-layout__title.str-chat__suggestion-list__mention-item-title {
font: var(--str-chat__font-body-default);
}
.str-chat__suggestion-list__item-details {
`@include` fonts.text-metadata-default;
`@include` utils.ellipsis-text;
+
color: var(--str-chat__text-tertiary);
text-align: start;
min-width: 0;
}As per coding guidelines, the project follows a 'zero warnings' policy—fix new warnings and avoid introducing any.
🧰 Tools
🪛 Stylelint (17.12.0)
[error] 45-45: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 56-56: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 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 `@src/components/TextareaComposer/styling/SuggestionList.scss` around lines 42
- 60, The SCSS violates stylelint's declaration-empty-line-before rule: in
.str-chat__suggestion-list__item-title and
.str-chat__suggestion-list__item-details, add a blank line before any property
declaration that immediately follows an `@include` (i.e., ensure there is an empty
line between the `@include` utils.ellipsis-text; and the next declaration such as
text-align: start; or color: ...), so update those selectors (and similarly
.str-chat__list-item-layout__title.str-chat__suggestion-list__mention-item-title
if needed) to insert the required empty lines after mixins to satisfy linting.
| "aria/Unmute User": "無音を解除する", | ||
| "aria/Unpin Message": "ピンを解除", | ||
| "aria/User Suggestions": "ユーザー候補", | ||
| "aria/User Suggestions": "aria/User Suggestions", |
There was a problem hiding this comment.
Translation value must be in Japanese, not the English key.
The value for aria/User Suggestions should be the Japanese translation (likely ユーザー候補), not the literal string aria/User Suggestions. This will cause English text to be displayed to Japanese users, breaking the i18n contract. Compare with line 97 where aria/Mention Suggestions is properly translated to メンション候補.
🌍 Proposed fix
- "aria/User Suggestions": "aria/User Suggestions",
+ "aria/User Suggestions": "ユーザー候補",As per coding guidelines, ensure all translation values are non-empty strings in the target language and run yarn validate-translations after fixing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "aria/User Suggestions": "aria/User Suggestions", | |
| "aria/User Suggestions": "ユーザー候補", |
🤖 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 `@src/i18n/ja.json` at line 144, Replace the English value for the JSON key
"aria/User Suggestions" with the correct Japanese translation (e.g., "ユーザー候補")
so the value is in Japanese (not "aria/User Suggestions"); update the entry for
"aria/User Suggestions" in the ja.json translations and then run yarn
validate-translations to ensure the file passes validation.
| "aria/Unmute User": "음소거 해제", | ||
| "aria/Unpin Message": "핀 해제", | ||
| "aria/User Suggestions": "사용자 제안", | ||
| "aria/User Suggestions": "aria/User Suggestions", |
There was a problem hiding this comment.
Translation value must be in Korean, not the English key.
The value for aria/User Suggestions should be the Korean translation (likely 사용자 제안), not the literal string aria/User Suggestions. This will cause English text to be displayed to Korean users. Compare with line 97 where aria/Mention Suggestions is properly translated to 멘션 제안.
🌍 Proposed fix
- "aria/User Suggestions": "aria/User Suggestions",
+ "aria/User Suggestions": "사용자 제안",As per coding guidelines, ensure all translation values are non-empty strings in the target language and run yarn validate-translations after fixing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "aria/User Suggestions": "aria/User Suggestions", | |
| "aria/User Suggestions": "사용자 제안", |
🤖 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 `@src/i18n/ko.json` at line 144, The translation for the JSON key "aria/User
Suggestions" currently uses the English key as its value; update the value to
the correct Korean translation (e.g., "사용자 제안") for the key "aria/User
Suggestions" in src/i18n/ko.json, ensure the value is a non-empty Korean string
consistent with other aria entries (see "aria/Mention Suggestions"), and run
yarn validate-translations after the change.
| "aria/Unmute User": "Dempen opheffen", | ||
| "aria/Unpin Message": "Losmaken", | ||
| "aria/User Suggestions": "Gebruikerssuggesties", | ||
| "aria/User Suggestions": "aria/User Suggestions", |
There was a problem hiding this comment.
Translation value must be in Dutch, not the English key.
The value for aria/User Suggestions should be the Dutch translation (likely Gebruikerssuggesties), not the literal string aria/User Suggestions. This will cause English text to be displayed to Dutch users. Compare with line 98 where aria/Mention Suggestions is properly translated to Vermeldingssuggesties.
🌍 Proposed fix
- "aria/User Suggestions": "aria/User Suggestions",
+ "aria/User Suggestions": "Gebruikerssuggesties",As per coding guidelines, ensure all translation values are non-empty strings in the target language and run yarn validate-translations after fixing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "aria/User Suggestions": "aria/User Suggestions", | |
| "aria/User Suggestions": "Gebruikerssuggesties", |
🤖 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 `@src/i18n/nl.json` at line 145, The JSON entry for the translation key
"aria/User Suggestions" currently uses the English key as its value; replace
that value with the correct Dutch translation (e.g., "Gebruikerssuggesties") for
the "aria/User Suggestions" key in the nl.json translations so Dutch users see
localized text, then run yarn validate-translations to ensure all translations
are valid.
| "aria/Unmute User": "Ativar som", | ||
| "aria/Unpin Message": "Desfixar mensagem", | ||
| "aria/User Suggestions": "Sugestões de usuários", | ||
| "aria/User Suggestions": "aria/User Suggestions", |
There was a problem hiding this comment.
Translation value must be in Portuguese, not the English key.
The value for aria/User Suggestions should be the Portuguese translation (likely Sugestões de usuários), not the literal string aria/User Suggestions. This will cause English text to be displayed to Portuguese users. Compare with line 106 where aria/Mention Suggestions is properly translated to Sugestões de menções.
🌍 Proposed fix
- "aria/User Suggestions": "aria/User Suggestions",
+ "aria/User Suggestions": "Sugestões de usuários",As per coding guidelines, ensure all translation values are non-empty strings in the target language and run yarn validate-translations after fixing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "aria/User Suggestions": "aria/User Suggestions", | |
| "aria/User Suggestions": "Sugestões de usuários", |
🤖 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 `@src/i18n/pt.json` at line 153, Update the translation value for the JSON key
"aria/User Suggestions" so it is a Portuguese string (e.g., "Sugestões de
usuários") instead of the English key; locate the "aria/User Suggestions" entry
in the pt.json translations and replace the value with the correct Portuguese
translation, ensure the string is non-empty and matches style of other entries
like "aria/Mention Suggestions", then run `yarn validate-translations` to
confirm no validation errors.
| "aria/Unmute User": "Включить уведомления", | ||
| "aria/Unpin Message": "Открепить сообщение", | ||
| "aria/User Suggestions": "Подсказки пользователей", | ||
| "aria/User Suggestions": "aria/User Suggestions", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check all locale files for aria/User Suggestions values
rg -n '"aria/User Suggestions":\s*"aria/User Suggestions"' 'src/i18n/*.json'Repository: GetStream/stream-chat-react
Length of output: 1923
🏁 Script executed:
rg -n '"aria/User Suggestions":\s*"aria/User Suggestions"' src/i18n/*.json
yarn validate-translationsRepository: GetStream/stream-chat-react
Length of output: 1942
🏁 Script executed:
rg -n '"aria/User Suggestions":\s*"aria/User Suggestions"' src/i18n/*.json
yarn validate-translationsRepository: GetStream/stream-chat-react
Length of output: 1942
🏁 Script executed:
rg -n '"aria/User Suggestions":\s*"aria/User Suggestions"' src/i18n/*.json
yarn validate-translationsRepository: GetStream/stream-chat-react
Length of output: 1942
Restore proper Russian translation for "aria/User Suggestions" (src/i18n/ru.json:162)
"aria/User Suggestions" maps to the literal string "aria/User Suggestions", so Russian users will hear an untranslated aria label instead of localized text.
Replace the value with the intended Russian string, then check other src/i18n/*.json locales for the same pattern and run yarn validate-translations.
🤖 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 `@src/i18n/ru.json` at line 162, The JSON key "aria/User Suggestions" has an
untranslated value; update its value to the correct Russian string (e.g., the
localized aria label for "User Suggestions") in the ru.json entry for "aria/User
Suggestions", then search other src/i18n/*.json files for any identical
untranslated key/value pairs and correct them as needed; finally run yarn
validate-translations to ensure all locale files pass validation.
🎯 Goal
Depends on: GetStream/stream-chat-js#1743
Closes REACT-979
This PR brings enhanced mentions support to
stream-chat-reactacross both the composer UI and rendered message text.It updates the
@suggestion flow to render a mixed mention set fromstream-chat-js, including direct users, built-in mentions like@channeland@here, roles, and user groups. The suggestion list now uses dedicated row components for each mention type, keeps keyboard navigation working across non-user items, and preserves accessibility and i18n behavior for the mixed result set.It also extends the message text rendering pipeline so enhanced mentions are highlighted the same way direct user mentions are. The
renderTextpath now accepts additive mention metadata, routes all mention kinds through a unifiedmentionrenderer contract vianode.mentionedEntity, and keeps backward compatibility for older user-only mention consumers through the deprecatednode.mentionedUseralias and the existingrenderText(text, mentioned_users, options)signature.Finally, the PR adds focused regression coverage for render-text behavior, quoted-message rendering, backward-compatible plugin usage, email-like mention edge cases, and multi-word mention names.
Highlights
@suggestion results fromstream-chat-js:@channel@hereMentionItemSpecialMentionItemRoleItemUserGroupItemUserItembackward compatibility for existing consumersrenderTextto support additive mention metadata throughoptions.messageMentionEntitiesmention+node.mentionedEntitynode.mentionedUsermentionsMarkdownPlugin(UserResponse[])renderText(text, mentioned_users, options)Summary by CodeRabbit
Release Notes
New Features
@here), roles, and user groups alongside user mentionsListItemLayoutcomponent for flexible, reusable list item rendering with customizable icons and contentLocalization