Skip to content

feat: add ChannelDetail component#3221

Open
MartinCupela wants to merge 24 commits into
masterfrom
feat/channel-detail
Open

feat: add ChannelDetail component#3221
MartinCupela wants to merge 24 commits into
masterfrom
feat/channel-detail

Conversation

@MartinCupela

@MartinCupela MartinCupela commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🎯 Goal

Add a complete, composable Channel Detail experience to the React SDK — a single ChannelDetail surface that lets users inspect and manage a channel (info, members, pinned messages, shared media and files) and that works both as a docked panel and as a responsive modal. The feature is built on reusable layout primitives (SectionNavigator, ListItemLayout, Prompt, FileIcon) so the same building blocks can be reused across the SDK and by integrators.

Closes REACT-832

Depends on GetStream/stream-chat-js#1770

🛠 Implementation details

New top-level component: ChannelDetail

  • ChannelDetail + ChannelDetailContext orchestrate a set of views through a new SectionNavigator, which switches between an inline (single-column, hamburger-driven drawer) and tabs/docked layout based on available width (via ResizeObserver), exposing its layout state to children.
  • Views included:
    • ChannelManagementView — channel info + an edit mode (rename, avatar upload/delete), membership state, and destructive actions (leave/delete, block/unblock) gated behind confirmation dialogs. The Save button only renders when there are unsaved changes.
    • ChannelMembersView — browse / add / remove members with paginated search and pluggable header actions; guards against firing a search when member_count === 0.
    • ChannelMemberDetailView — per-member actions (send DM, mute/unmute, block/unblock, remove).
    • PinnedMessagesView — paginated pinned messages with its own timestamp formatting key; guards against searching when there are no pinned messages.
    • ChannelMediaView — images/videos grid with avatars, video durations, and a full-screen viewer.
    • ChannelFilesView — files/audio grouped by month, with per-type FileIcons (scraped attachments and images filtered out).

Search & pagination

  • Media/files/members/pinned views paginate through stream-chat search sources (MessageSearchSource, ChannelMemberSearchSource) via dedicated hooks (useChannelMediaSearch, useChannelFilesSearch, useChannelMembersSearch, usePinnedMessagesSearch).

Reusable primitives & shared changes

  • SectionNavigator + SectionNavigatorHeader (generic header with a leading hamburger slot, back button, title/description, trailing controls) — also adopted by the demo AppSettings tabs.
  • ListItemLayout — now wraps an outer spacing buffer around an inner interactive container so the focus ring (2px offset) clears adjacent rows/overflow ancestors; the inner container is the button/a.
  • Prompt (Dialog) header reworked to a robust flex/grid layout (vertically-centered first row, description on a second row, dedicated leading-content slot) and a scrollable body.
  • FileIcon mime-type mapping hardened (fallback guards + image/ mapping).
  • Avatar: new AvatarWithChannelDetail; ChannelAvatar now prefers an explicit channel image over derived member avatars.
  • Form: new Checkbox; SwitchField updates.
  • Modal/Dialog: notifications routed through generated modal dialogs; GlobalModal, DialogPortal, and DialogManagerContext updated accordingly.

Hooks & utils

  • useChannelHasMembersOnline, useChannelHeaderOnlineStatus, useIsUserMuted, isDmChannel, and a small useChannelPreviewInfo extension.

Demo (examples/vite)

  • AppSettings migrated to SectionNavigator; added a Channel Detail settings tab and made channel-detail actions configurable.
RDzu.webm

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Channel Detail plugin with management, members, pinned messages, files, and media views
    • Added channel member management capabilities (add/remove members)
    • Added section-based navigation component for improved UI organization
    • Added checkbox form component for enhanced selection controls
    • Implemented channel settings configuration in app settings
  • Improvements

    • Refactored settings UI with standardized tab layouts
    • Enhanced notification severity icon mapping
    • Improved notification routing when modals are open
    • Better avatar preference handling
    • Enhanced member online status detection
    • Updated switch field with presentation mode support
  • Bug Fixes

    • Fixed MIME type icon fallbacks for attachments
    • Improved dialog overlay pointer event handling
    • Enhanced prompt header with navigation controls
  • Internationalization

    • Added translations across multiple languages for new features and dialogs

# Conflicts:
#	src/components/Modal/GlobalModal.tsx
# Conflicts:
#	src/components/Modal/GlobalModal.tsx
@MartinCupela MartinCupela changed the title Feat/channel detail feat: add ChannelDetail component Jun 12, 2026
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a new ChannelDetail plugin with section-based navigation, member/files/media/pinned-message subviews, and example-app settings integration. It also updates shared dialog, modal, notification, icon, form, and styling primitives, plus locale strings and build exports.

Changes

Shared UI primitives and hooks

Layer / File(s) Summary
Shared UI primitives and hooks
src/components/..., src/utils/..., src/styling/_utils.scss
Shared dialog, modal, prompt, notification, list-item, form, icon, avatar, file, and channel-status helpers were added or updated for the channel detail flow.
Example settings and demo wiring
Layer / File(s) Summary
Example settings and demo wiring
examples/vite/src/AppSettings/..., examples/vite/src/ChatLayout/..., examples/vite/src/SystemNotification/..., examples/vite/src/index.scss, examples/vite/package.json, package.json, scripts/watch-styling.mjs, vite.config.ts
The example app now uses SectionNavigator-based settings tabs, channel-detail settings state, configured channel-detail rendering, and updated build/style entry points.
ChannelDetail shell and navigation
Layer / File(s) Summary
ChannelDetail shell and navigation
src/plugins/ChannelDetail/...
The ChannelDetail provider, shell, navigation buttons, exports, and shared plugin styles were added, along with the avatar entry component and prompt/layout styling.
Channel detail views: files, media, pinned messages
Layer / File(s) Summary
Files, media, and pinned-message views
src/plugins/ChannelDetail/Views/ChannelFilesView/..., src/plugins/ChannelDetail/Views/ChannelMediaView/..., src/plugins/ChannelDetail/Views/PinnedMessagesView/...
The files, media, and pinned-message views now include search hooks, grouping/utilities, empty states, rendering logic, exports, tests, and styles.
Channel member detail, browse, add, remove, and header actions
Layer / File(s) Summary
Member detail and actions
src/plugins/ChannelDetail/Views/ChannelMemberDetailView/..., src/plugins/ChannelDetail/Views/ChannelMembersView/..., src/plugins/ChannelDetail/Views/ChannelManagementView/...
The member detail, browse/add/remove flows, management actions, and the channel management view were added together with their tests, styles, and supporting search/state helpers.
Shared routing, notifications, and utility behavior
Layer / File(s) Summary
Routing and status hooks
src/components/Chat/..., src/components/ChannelHeader/hooks/..., src/components/ChannelListItem/hooks/..., src/components/Notifications/hooks/..., src/utils/...
Shared routing, notification targeting, DM detection, online-status, preview, and mute-state logic was updated and covered by tests.
Locale catalogs, styling utilities, and build exports
Layer / File(s) Summary
Locales, styling, and exports
src/i18n/*.json, src/components/index.ts, src/styling/index.scss, src/plugins/ChannelDetail/styling/index.scss, src/components/Avatar/styling/index.scss
Locale strings, top-level style imports, and public export surfaces were expanded for the new plugin and its supporting components.

Sequence Diagram(s)

sequenceDiagram
  participant AvatarWithChannelDetail
  participant GlobalModal
  participant ChannelDetail
  participant SectionNavigator
  AvatarWithChannelDetail->>GlobalModal: open modal
  GlobalModal->>ChannelDetail: render channel detail content
  ChannelDetail->>SectionNavigator: provide sections and layout
  SectionNavigator->>ChannelDetail: onLayoutChange
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • oliverlaz
  • arnautov-anton

Poem

A rabbit hopped through panels bright,
And found new paths in soft moonlight.
With files, pins, and members too,
The channel detail grew and grew.
Hop-hop! The tabs now twist and spin 🐇

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/channel-detail

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Dialog/service/DialogPortal.tsx (1)

25-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

captureOutsideClicks is cosmetic right now.

Line 26 adds a behavioral prop, but Lines 36-69 still register the global outside-click handler unconditionally. That means dialogs close on clicks outside the overlay even when captureOutsideClicks is falsy, so DialogManagerProvider.portalDestinationProps cannot actually disable that behavior.

Suggested direction
   useEffect(() => {
-    if (!destinationRoot || !dialogManager) return;
+    if (!destinationRoot || !dialogManager || !captureOutsideClicks) return;

     const handleDocumentClick = (event: MouseEvent) => {
       const target = event.target as Node;
       const clickedOverlay = (target as HTMLElement).closest?.(
         '[data-str-chat__portal-id]',
@@
-  }, [destinationRoot, dialogManager]);
+  }, [captureOutsideClicks, destinationRoot, dialogManager]);
🤖 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/Dialog/service/DialogPortal.tsx` around lines 25 - 69, The
click-to-close behavior is registered unconditionally in
DialogPortalDestination; make it respect the captureOutsideClicks prop by only
attaching the document 'click' listener when captureOutsideClicks is truthy (and
dialogManager/destinationRoot exist and openedDialogCount > 0), and remove it
otherwise; update the useEffect to include captureOutsideClicks in its
dependency array and gate the listener attachment around captureOutsideClicks,
keeping the existing handler (handleDocumentClick) and removal logic unchanged.
🟡 Minor comments (26)
src/i18n/ja.json-68-68 (1)

68-68: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Untranslated strings in Japanese translation file.

Several translation keys contain English text instead of Japanese translations:

  • Line 68: "Are you sure you want to leave this channel?"
  • Line 185: "Chat deleted"
  • Line 237: "Error adding members"
  • Line 240: "Error deleting chat"
  • Line 421: "Open members actions"
  • Line 528: "This permanently deletes your message history with {{ user }}. This can't be undone."
  • Line 530: "This user won't be able to message you anymore. You can unblock them anytime."

As per coding guidelines, all translation values must be non-empty strings in the target language, with zero-tolerance validation via yarn validate-translations.

Also applies to: 185-185, 237-237, 240-240, 421-421, 528-528, 530-530

🤖 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 68, The Japanese translation file has several
untranslated values (keys like "Are you sure you want to leave this channel?",
"Chat deleted", "Error adding members", "Error deleting chat", "Open members
actions", "This permanently deletes your message history with {{ user }}. This
can't be undone.", and "This user won't be able to message you anymore. You can
unblock them anytime.")—replace each English value with the correct Japanese
translation (ensuring placeholders like {{ user }} remain intact), save the
updated src/i18n/ja.json, and run the zero-tolerance validator (yarn
validate-translations) to confirm there are no empty or English strings
remaining before committing.

Source: Coding guidelines

src/i18n/pt.json-83-83 (1)

83-83: ⚠️ Potential issue | 🟡 Minor

Fix untranslated English strings in src/i18n/pt.json

src/i18n/pt.json still contains English values (the value matches the English key) for:

  • Line 83: "Are you sure you want to leave this channel?"
  • Line 200: "Chat deleted"
  • Line 252: "Error adding members"
  • Line 255: "Error deleting chat"
  • Line 442: "Open members actions"
  • Line 554: "This permanently deletes your message history with {{ user }}. This can't be undone."
  • Line 556: "This user won't be able to message you anymore. You can unblock them anytime."
"Are you sure you want to leave this channel?": "Are you sure you want to leave this channel?",

Replace these values with proper Portuguese translations and re-run yarn validate-translations to catch any remaining issues.

🤖 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 83, Update the untranslated English values in
src/i18n/pt.json by replacing the listed keys' values with proper Portuguese
translations: "Are you sure you want to leave this channel?", "Chat deleted",
"Error adding members", "Error deleting chat", "Open members actions", "This
permanently deletes your message history with {{ user }}. This can't be
undone.", and "This user won't be able to message you anymore. You can unblock
them anytime."; edit the corresponding entries so the keys remain identical but
the values are translated to Portuguese, save the file and re-run yarn
validate-translations to verify there are no remaining translation errors.

Source: Coding guidelines

src/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.ts-43-43 (1)

43-43: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use pluralized i18n keys for the member count label.

'{{ memberCount }} members' will render ungrammatical text for singular counts and won't pluralize correctly across locales. Switch this to the repo's _one / _other translation pattern instead of a fixed plural string. As per coding guidelines, "Use Plurals in translations with _one/_other suffixes".

🤖 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/ChannelHeader/hooks/useChannelHeaderOnlineStatus.ts` at line
43, The current return in useChannelHeaderOnlineStatus uses a fixed plural
string ("{{ memberCount }} members") which won't pluralize for singular or other
locales; replace it with the repo's pluralized i18n keys using the _one/_other
suffix and pass the count (memberCount) so the i18n system chooses the correct
form (e.g., use the members_one/members_other key pattern or
channel.members_one/channel.members_other per our conventions) while keeping the
watcherCount part unchanged; update the t(...) call in
useChannelHeaderOnlineStatus to use the count-aware plural key and the
memberCount variable so singular/plural translations work correctly.

Source: Coding guidelines

src/components/ChannelDetail/Views/ChannelFilesView/__tests__/ChannelFilesView.test.tsx-170-183 (1)

170-183: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update test expectation after fixing allowEmptySearchString.

This test expects allowEmptySearchString: true (line 174), which corresponds to the invalid property flagged in useChannelFilesSearch.ts. Once the hook is corrected to use the valid stream-chat API, update this assertion to match the corrected configuration.

🤖 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/ChannelDetail/Views/ChannelFilesView/__tests__/ChannelFilesView.test.tsx`
around lines 170 - 183, The test currently asserts an invalid option name
(allowEmptySearchString) — update the assertion in ChannelFilesView.test.tsx to
match the corrected option used in the hook (useChannelFilesSearch) /
MessageSearchSource configuration; replace the allowEmptySearchString: true
expectation with the valid property name used in your fix (e.g.,
allowEmptySearchQuery: true) or otherwise assert the actual option that
useChannelFilesSearch sets on mocks.searchSourceOptions[0], and remove the
incorrect allowEmptySearchString check so the test reflects the hook's corrected
configuration.
src/components/ChannelDetail/Views/PinnedMessagesView/__tests__/PinnedMessagesView.test.tsx-216-231 (1)

216-231: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear searchSourceActivate before asserting the whitespace-reset path.

This assertion is already satisfied by the mount-time activate() call, so the test would still pass if the empty-query branch stopped reactivating the source. Reset the relevant spies after the initial render/search step and then assert the second activation.

🤖 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/ChannelDetail/Views/PinnedMessagesView/__tests__/PinnedMessagesView.test.tsx`
around lines 216 - 231, The test currently asserts that the whitespace reset
branch calls searchSourceActivate, but that activation also happens at mount, so
clear the activation spy after the initial render/search step to ensure the
second activation is the one being asserted; specifically after
renderWithChannel(<PinnedMessagesView ...>) and after
mocks.searchSourceSearch.mockClear(), call
mocks.searchSourceActivate.mockClear() (or mockReset) before firing the
whitespace change, then assert mocks.searchSourceActivate was called once along
with searchSourceCancelScheduledQuery, searchSourceResetState, and that
searchSourceSearch was not called.
src/components/SectionNavigator/styling/SectionNavigator.scss-19-22 (1)

19-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the stylelint failure in this block.

Stylelint is already flagging Line 21 with declaration-empty-line-before, so this new stylesheet currently introduces a merge-blocking lint error.

🤖 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/SectionNavigator/styling/SectionNavigator.scss` around lines
19 - 22, The stylelint error comes from missing an empty line before the
declaration in the .str-chat__section-navigator__navigation block; update that
block (involving `@include` utils.hide-scrollbar(y), overscroll-behavior, display)
to satisfy declaration-empty-line-before by inserting a blank line before the
overscroll-behavior declaration (or alternatively add a targeted stylelint
disable comment before the overscroll-behavior line if an exception is needed).

Source: Linters/SAST tools

src/i18n/nl.json-72-72 (1)

72-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Several locale additions were copied over in English.

src/i18n/nl.json and src/i18n/ko.json both still contain English values for new Channel Detail strings, including destructive confirmations, error messages, "Open members actions", and the new pinned-message timestamp lastDay text. The shared root cause is incomplete localization for the new feature, and it will ship mixed-language UI in both Dutch and Korean.

🤖 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 72, The Dutch and Korean locale files contain
untranslated English strings for new Channel Detail keys (e.g., "Are you sure
you want to leave this channel?", "Open members actions", and the pinned-message
timestamp key "lastDay") and associated destructive confirmations and error
messages; update the corresponding JSON values for those keys in both locale
files with proper Dutch and Korean translations, ensure no English fallbacks
remain for any new Channel Detail strings, and run the i18n validation/test to
confirm translations load correctly.
src/components/ChannelDetail/styling/ChannelManagementView.scss-103-113 (1)

103-113: ⚠️ Potential issue | 🟡 Minor

Replace deprecated clip in the visually-hidden file input
The visually-hidden input still relies on the deprecated clip property (.str-chat__channel-detail__channel-management-view__file-input); switch to a clip-path-based recipe.

Suggested update
 .str-chat__channel-detail__channel-management-view__file-input {
   position: absolute;
   width: 1px;
   height: 1px;
   padding: 0;
   margin: -1px;
   overflow: hidden;
-  clip: rect(0, 0, 0, 0);
+  clip-path: inset(50%);
   white-space: nowrap;
   border: 0;
 }
🤖 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/ChannelDetail/styling/ChannelManagementView.scss` around lines
103 - 113, The .str-chat__channel-detail__channel-management-view__file-input
rule uses the deprecated clip property; replace it with a clip-path-based
visually-hidden recipe by removing the clip declaration and adding a clip-path
(e.g., clip-path: inset(50%)); keep position:absolute, width:1px, height:1px,
padding:0, margin:-1px, overflow:hidden, white-space:nowrap, border:0, and add
-webkit-clip-path: inset(50%) for broader browser support so the input remains
visually hidden but accessible (update the selector
.str-chat__channel-detail__channel-management-view__file-input).

Source: Linters/SAST tools

src/i18n/es.json-55-55 (1)

55-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Finish translating the new Spanish strings before merging.

Several new es values are still English ("Actions", "Are you sure you want to leave this channel?", "Chat deleted", "Error adding members", "Open members actions", the permanent-delete copy, and "[Yesterday]" inside timestamp/ChannelDetailPinnedMessageTimestamp). This will surface mixed-language UI throughout the new Channel Detail flows for Spanish users.

Also applies to: 83-83, 200-200, 252-255, 442-442, 554-556, 566-566

🤖 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/es.json` at line 55, Several Spanish translations in es.json are
still English; locate the keys with values "Actions", "Are you sure you want to
leave this channel?", "Chat deleted", "Error adding members", "Open members
actions", the permanent-delete confirmation copy, and the "[Yesterday]" value
inside timestamp/ChannelDetailPinnedMessageTimestamp and replace them with
proper Spanish translations (ensure plural/singular and formal/informal tone
matches the app). Update the corresponding JSON entries for those keys and any
other English strings in the same file ranges mentioned so the Channel Detail
flows are fully localized, then run a quick JSON lint and a smoke-check in the
UI to confirm no missing quotes or broken keys.
src/components/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx-95-100 (1)

95-100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the leading . from the destructive icon classes.

className should contain raw class tokens, not CSS selectors. The current values create literal class names like .str-chat__icon--destructive, so any existing .str-chat__icon--destructive styling will never match these icons.

💡 Suggested fix
 const BlockUserActionIcon = () => (
-  <IconNoSign className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
+  <IconNoSign className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
 );

 const RemoveUserActionIcon = () => (
-  <IconUserRemove className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--remove-user' />
+  <IconUserRemove className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--remove-user' />
 );

Based on learnings, icon selectors must use str-chat__icon--<name> class tokens without selector syntax.

🤖 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/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx`
around lines 95 - 100, In BlockUserActionIcon and RemoveUserActionIcon update
the IconNoSign and IconUserRemove className props to remove the leading '.' from
the destructive class tokens so they use raw class names (e.g.,
"str-chat__icon--destructive" not ".str-chat__icon--destructive"); ensure both
className strings for IconNoSign and IconUserRemove use space-separated class
tokens without selector syntax.

Source: Learnings

src/i18n/it.json-55-55 (1)

55-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the remaining English fallbacks in the Italian locale.

Several new entries are still English here ("Actions", "Are you sure you want to leave this channel?", "Chat deleted", "Error adding members", "Open members actions", the delete-history prompt, and "Yesterday" inside timestamp/ChannelDetailPinnedMessageTimestamp). Those strings will leak into the Italian UI as-is.

Also applies to: 83-83, 200-200, 252-255, 442-442, 554-566

🤖 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/it.json` at line 55, The Italian locale file (src/i18n/it.json)
contains untranslated English fallbacks (e.g., the keys "Actions", "Are you sure
you want to leave this channel?", "Chat deleted", "Error adding members", "Open
members actions", the delete-history prompt, and "Yesterday" under
timestamp/ChannelDetailPinnedMessageTimestamp); replace those English values
with their correct Italian translations, ensuring you update the exact JSON keys
mentioned and any other entries at the ranges called out (lines ~83, ~200,
~252-255, ~442, ~554-566) so no English strings remain in the Italian locale.
src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx-42-56 (1)

42-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the leading . from these icon class names.

The destructive modifier is never applied here because the rendered class becomes the literal token .str-chat__icon--destructive instead of str-chat__icon--destructive.

💡 Suggested fix
 const BlockUserActionIcon = () => (
-  <IconNoSign className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
+  <IconNoSign className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
 );
 const DeleteChatActionIcon = () => (
-  <IconDelete className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--delete-chat' />
+  <IconDelete className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--delete-chat' />
 );
 const LeaveChannelActionIcon = () => (
-  <IconLeave className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--leave-channel' />
+  <IconLeave className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--leave-channel' />
 );

Based on learnings: createIcon-generated selectors in this repo use the str-chat__icon--<name> class form, so the leading dot breaks the expected class token.

🤖 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/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx`
around lines 42 - 56, The icon classNames in BlockUserActionIcon,
DeleteChatActionIcon, LeaveChannelActionIcon (rendering IconNoSign, IconDelete,
IconLeave) incorrectly include a leading '.' which causes the literal token to
be used; remove the leading '.' from those className strings so they become e.g.
'str-chat__icon--destructive str-chat__channel-detail__action-icon ...' (also
verify MuteActionIcon and MutedActionIcon className forms remain unchanged) to
restore proper CSS selector matching.

Source: Learnings

src/components/Form/Checkbox.tsx-6-12 (1)

6-12: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve caller-provided className.

className from CheckboxProps is currently overwritten by the hard-coded classes, so consumers cannot attach styling or selectors to this new component.

♻️ Proposed fix
-export const Checkbox = ({ checked, ...props }: CheckboxProps) => (
+export const Checkbox = ({ checked, className, ...props }: CheckboxProps) => (
   <div
     {...props}
-    className={clsx('str-chat__checkmark str-chat__checkbox', {
+    className={clsx('str-chat__checkmark str-chat__checkbox', className, {
       'str-chat__checkbox--checked': checked,
       'str-chat__checkmark--checked': checked,
     })}
   />
 );
🤖 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/Form/Checkbox.tsx` around lines 6 - 12, The Checkbox component
currently overwrites any caller-provided className; update the props handling in
Checkbox (and the clsx call) to preserve and merge the incoming className from
CheckboxProps with the hard-coded classes (e.g., pass className through or
extract it from props and include it in clsx) so consumers can add custom
styling/selectors; ensure you still apply the conditional
'str-chat__checkbox--checked' and 'str-chat__checkmark--checked' classes based
on the checked prop while merging the user className.
src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.tsx-198-245 (1)

198-245: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the menu's accessibility label.

aria-label='Members actions' is the one user-facing string in this component that bypasses t(), so screen readers will always announce it in English and it will never be extracted into the locale JSON files. As per coding guidelines, use useTranslationContext() to access the t() function for translations and add all translatable strings to src/i18n/ files.

🤖 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/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.tsx`
around lines 198 - 245, The aria-label on ContextMenuComponent is hardcoded as
'Members actions' and bypasses translation; import and call
useTranslationContext() to get t() (e.g., const { t } = useTranslationContext())
and replace the literal aria-label on ContextMenuComponent with a translated
string (e.g., t('Members actions') or the agreed i18n key), and add that
key/translation to the i18n JSON; update
ChannelMembersHeaderActions.defaults.tsx where ContextMenuComponent and dialog
logic are defined to use t() for accessibility labels.

Source: Coding guidelines

src/components/ChannelDetail/styling/ChannelDetail.scss-56-59 (1)

56-59: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid capitalizing translated labels in CSS.

text-transform: capitalize rewrites whatever localized label gets rendered here, which breaks locale-specific casing rules and can mangle scripts that do not use title case. If these nav labels need capitalization, do it in the source strings instead of forcing it at paint time.

🤖 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/ChannelDetail/styling/ChannelDetail.scss` around lines 56 -
59, Remove the forced text-transform from the CSS rule for
.str-chat__channel-detail__nav-button so translated labels are not modified at
render time; either delete the text-transform: capitalize declaration or set it
to none, and if capitalization is required, perform that in the source/localized
strings that feed the nav button rather than in ChannelDetail.scss.
src/i18n/ru.json-589-589 (1)

589-589: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the pinned-message “Yesterday” calendar label.

This formatter still hard-codes [Yesterday] inside the Russian locale, so pinned-message timestamps will render an English day label even when the rest of the UI is Russian.

🤖 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 589, The calendarFormats in the
"timestamp/ChannelDetailPinnedMessageTimestamp" entry is hard-coded with
"[Yesterday]" in English; update that lastDay value to a localized Russian label
(e.g. "[Вчера]") or reference the appropriate i18n key so the pinned-message
timestamp uses the locale string rather than the English literal; edit the
"timestamp/ChannelDetailPinnedMessageTimestamp" value and replace the lastDay
entry inside calendarFormats accordingly.
src/i18n/ru.json-66-66 (1)

66-66: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate the new Russian locale entries before shipping.

Several newly added ru.json values are still English ("Actions", "Are you sure you want to leave this channel?", "Chat deleted", "Error adding members", "Open members actions", and the new unblock/delete-history copy). That will surface mixed-language UI in the Russian locale.

Also applies to: 95-95, 212-212, 264-267, 460-460, 576-578

🤖 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 66, Several new keys in ru.json are still in
English and should be translated into Russian to avoid mixed-language UI; locate
the entries for "Actions", "Are you sure you want to leave this channel?", "Chat
deleted", "Error adding members", "Open members actions", and the new
unblock/delete-history strings (also other occurrences flagged around the file)
and replace the English values with accurate Russian translations that match
tone and terminology used elsewhere in ru.json, keeping the JSON key names
unchanged and preserving punctuation and capitalization.
src/components/ChannelDetail/styling/ChannelFilesView.scss-62-63 (1)

62-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add empty line before declaration to satisfy stylelint.

Stylelint requires an empty line before the display declaration.

🎨 Proposed fix
 .str-chat__channel-detail__files-view__list {
   `@include` utils.hide-scrollbar;
+
   display: flex;
🤖 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/ChannelDetail/styling/ChannelFilesView.scss` around lines 62 -
63, Stylelint flags missing empty line before the display declaration in
ChannelFilesView.scss; open the ChannelFilesView.scss file, locate the ruleset
containing the `@include utils.hide-scrollbar;` followed immediately by
`display: flex;` and insert a single blank line between the include and the
`display` declaration so the file has an empty line before `display` (keep
existing indentation and formatting).
src/components/ChannelDetail/styling/ChannelMediaView.scss-62-63 (1)

62-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add empty line before declaration to satisfy stylelint.

Stylelint requires an empty line before the display declaration.

🎨 Proposed fix
 .str-chat__channel-detail__media-view__grid {
   `@include` utils.hide-scrollbar;
+
   display: flex;
🤖 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/ChannelDetail/styling/ChannelMediaView.scss` around lines 62 -
63, Add a blank line before the display declaration to satisfy stylelint: locate
the block containing the `@include` utils.hide-scrollbar; line and insert one
empty line before the subsequent display: flex; declaration so the file has an
empty line between `@include` utils.hide-scrollbar; and display: flex;.
src/components/Form/styling/Checkbox.scss-3-4 (1)

3-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add empty line before declaration to satisfy stylelint.

Stylelint requires an empty line before the grid-column declaration.

🎨 Proposed fix
 .str-chat__checkbox,
 .str-chat__checkmark {
   $poll-checkmark-size: var(--str-chat__size-24);
+
   grid-column: 1;
🤖 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/Form/styling/Checkbox.scss` around lines 3 - 4, Add a blank
line before the grid-column declaration to satisfy stylelint: open the block
containing $poll-checkmark-size and grid-column (look for $poll-checkmark-size:
var(--str-chat__size-24); and the grid-column: 1; declaration) and insert an
empty line between the two lines so grid-column is separated from the preceding
$poll-checkmark-size declaration.
src/components/Dialog/styling/Prompt.scss-3-9 (1)

3-9: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the Stylelint violation in .str-chat__prompt.

This block is currently failing declaration-empty-line-before on Line 5, which will break the repo's zero-warnings/lint-clean expectation.

🤖 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/Dialog/styling/Prompt.scss` around lines 3 - 9, The
.str-chat__prompt rule violates declaration-empty-line-before; fix it by adding
a single blank line immediately after the opening brace of the .str-chat__prompt
block so there is an empty line before the first declaration (`@include`
utils.modal), keeping the rest of the declarations intact; target the
.str-chat__prompt selector in Prompt.scss to make this change.

Sources: Coding guidelines, Linters/SAST tools

src/i18n/de.json-72-72 (1)

72-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Some new locale entries are still untranslated in src/i18n/de.json, src/i18n/hi.json, src/i18n/fr.json, and src/i18n/tr.json.

The same root cause shows up across all four locale files: several new Channel Detail strings were added with English values (for example the leave-channel confirmation, "Chat deleted", "Error adding members", "Open members actions", parts of the destructive-history/unblock copy, and [Yesterday] inside the pinned-message timestamp formatter). That will ship a mixed-language UI in German, Hindi, French, and Turkish for the new feature surface.

🤖 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/de.json` at line 72, Several new i18n keys were left untranslated
(English values) and must be localized: update the JSON entries for keys like
"Are you sure you want to leave this channel?", "Chat deleted", "Error adding
members", "Open members actions", the destructive-history/unblock strings, and
the pinned-message timestamp fragment "[Yesterday]" in the de/hi/fr/tr locale
files; replace the English values with the correct translated strings for
German, Hindi, French, and Turkish respectively, ensuring proper JSON
quoting/escaping and preserving the existing key names (e.g., the string keys
shown above) so the app uses the localized messages.
src/components/ChannelDetail/styling/PinnedMessagesView.scss-17-19 (1)

17-19: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add empty line before display declaration to satisfy stylelint.

Stylelint expects an empty line between the @include directive and the following property declaration.

🎨 Proposed fix
 .str-chat__channel-detail__pinned-messages-view__list {
   `@include` utils.hide-scrollbar;
+
   display: flex;
🤖 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/ChannelDetail/styling/PinnedMessagesView.scss` around lines 17
- 19, In .str-chat__channel-detail__pinned-messages-view__list, add a blank line
between the `@include` utils.hide-scrollbar directive and the subsequent display:
flex declaration to satisfy stylelint; locate the rule for
.str-chat__channel-detail__pinned-messages-view__list, ensure there is exactly
one empty line after the `@include` utils.hide-scrollbar line and before the
display property so the linter passes.
src/components/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts-30-40 (1)

30-40: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use channel.cid in dependency array, not channel.

The coding guideline requires using the stable channel.cid identifier in dependency arrays instead of the channel object to prevent re-creating the search source when only the channel reference changes.

♻️ Proposed fix
   const membersSearchSource = useMemo(() => {
     const source = new ChannelMemberSearchSource(channel, {
       allowEmptySearchString: true,
       debounceMs: MEMBERS_SEARCH_DEBOUNCE_MS,
       pageSize: CHANNEL_MEMBERS_QUERY_LIMIT,
       resetOnNewSearchQuery: false,
     });

     if (hasMembers) source.activate();
     return source;
-  }, [channel, hasMembers]);
+  }, [channel.cid, hasMembers]);

As per coding guidelines: "Do not include channel in dependency arrays; use channel.cid only (stable identifier), not channel.state".

🤖 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/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts`
around lines 30 - 40, The useMemo that creates membersSearchSource currently
depends on the unstable channel object; change the dependency to use the stable
channel.cid instead (keep hasMembers), so update the useMemo dependency array to
[channel.cid, hasMembers] to avoid recreating ChannelMemberSearchSource when
only the channel reference changes; ensure you still reference channel when
constructing new ChannelMemberSearchSource but only use channel.cid in the
dependency list.

Source: Coding guidelines

src/components/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts-22-25 (1)

22-25: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use channel.cid in dependency array, not channel.

The coding guideline requires using the stable channel.cid identifier in dependency arrays instead of the channel object to avoid unnecessary re-creation when the channel reference changes.

♻️ Proposed fix
   const fallbackMembers = useMemo(
     () => Object.values(channel.state?.members ?? {}),
-    [channel],
+    [channel.cid],
   );

As per coding guidelines: "Do not include channel in dependency arrays; use channel.cid only (stable identifier), not channel.state".

🤖 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/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts`
around lines 22 - 25, The useMemo creating fallbackMembers currently depends on
the entire channel object which can change reference; change its dependency
array to use the stable channel.cid instead: keep the factory as () =>
Object.values(channel.state?.members ?? {}) but replace [channel] with
[channel.cid] so fallbackMembers only recomputes when the channel identifier
changes; update the dependency array for useMemo referencing fallbackMembers
accordingly.

Source: Coding guidelines

src/components/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.ts-68-72 (1)

68-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against undefined message.id to prevent key collisions.

While MessageResponse and LocalMessage should typically have an id, optimistic or draft messages might have undefined or missing IDs, leading to colliding keys like "undefined-0" and "undefined-1". Consider adding a fallback or filter:

🛡️ Proposed fix
     message.attachments?.forEach((attachment, index) => {
       if (!isChannelFileAttachment(attachment)) return;
+      if (!message.id) return; // Skip messages without stable IDs

       const item: ChannelFileItem = {
         attachment,
         createdAt,
         id: `${message.id}-${index}`,
       };
🤖 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/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.ts`
around lines 68 - 72, When building ChannelFileItem in ChannelFilesView.utils.ts
you must guard against a missing or undefined message.id to avoid colliding keys
like "undefined-0"; update the code that creates the id (`id:
`${message.id}-${index}``) to either skip messages lacking a stable id or
generate a safe fallback (for example combine a deterministic fallback field
such as message.clientId/message.timestamp/createdAt or attachment filename with
the index) so the resulting id is always a unique non-"undefined" string; ensure
any filtering or fallback logic is applied where ChannelFileItem objects are
created so ChannelFileItem and its id field are never created with
`"undefined-..."`.
🧹 Nitpick comments (7)
src/context/DialogManagerContext.tsx (1)

55-64: ⚡ Quick win

Document the new portalDestinationProps public prop.

DialogManagerProvider now exposes a new SDK prop, but the props block only documents closeOnClickOutside. Please add inline docs here and update any affected public docs/examples so consumers know what portalDestinationProps does and which fields are supported.

As per coding guidelines, "Ensure public API changes include documentation updates" and "When altering public API, update inline docs and any affected guide pages in the docs site".

🤖 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/context/DialogManagerContext.tsx` around lines 55 - 64, Add inline
documentation for the new public prop portalDestinationProps on the
DialogManagerProviderProps type: describe that portalDestinationProps accepts a
DialogPortalDestinationProps object used to control where manager dialogs are
rendered (e.g., portal container element, mount id, or append behavior) and
enumerate supported fields from DialogPortalDestinationProps with short
descriptions and default behavior; update any public examples/docs that show
DialogManagerProvider usage to include an example using portalDestinationProps
and mention compatibility/defaults. Target symbols: DialogManagerProviderProps,
portalDestinationProps, and DialogPortalDestinationProps.

Source: Coding guidelines

src/components/ChannelDetail/ChannelDetailEmptyList.tsx (1)

5-5: ⚡ Quick win

Overly-specific class name for a reusable component.

The component is named ChannelDetailEmptyList (generic), but the class name str-chat__channel-detail__channel-members-view__empty-state is specific to the members view. If this component is reused across other channel detail views (files, media, pinned messages), the class name will be misleading and harder to maintain.

Consider either:

  • Renaming the class to str-chat__channel-detail__empty-state (generic)
  • Or creating view-specific empty list components if styling differs
🤖 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/ChannelDetail/ChannelDetailEmptyList.tsx` at line 5, The
component ChannelDetailEmptyList uses an overly-specific CSS class
'str-chat__channel-detail__channel-members-view__empty-state' that ties it to
the members view; update the component to use a generic class name like
'str-chat__channel-detail__empty-state' (or split into view-specific components
if styles differ), and update any related CSS/SCSS selectors and other usages
referencing the old class to the new generic name so styles continue to apply;
ensure the class string in ChannelDetailEmptyList and any tests or consumers are
changed consistently.
src/components/ChannelDetail/__tests__/ChannelManagementView.test.tsx (1)

31-155: 🏗️ Heavy lift

Use the repo's Chat/Channel test harness instead of ad-hoc context mocks.

This suite stubs most of the runtime contexts and renders only ChannelDetailProvider, so it bypasses the Chat/Channel wiring the SDK relies on in production. That makes the coverage less representative for mute/presence/update flows than the repository's standard test setup. As per coding guidelines, "Use mock-builders pattern for tests: getTestClientWithUser(), useMockedApis(), getOrCreateChannelApi(), set connectionId and wsPromise" and "Wrap component tests with <Chat>, <Channel>, and relevant sub-components; use channel.watch() after setup".

🤖 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/ChannelDetail/__tests__/ChannelManagementView.test.tsx` around
lines 31 - 155, The test currently bypasses SDK wiring by stubbing contexts and
rendering ChannelManagementView inside ChannelDetailProvider (see
ChannelManagementView and ChannelDetailProvider); replace the ad-hoc mocks with
the repo test harness: create a test client using getTestClientWithUser(), call
useMockedApis() and getOrCreateChannelApi() to set up the channel, set
connectionId and wsPromise as needed, wrap the rendered component with <Chat>
and <Channel> (instead of directly using ChannelDetailProvider), and call
channel.watch() after setup so mute/presence/update flows exercise the real
wiring used in production.

Source: Coding guidelines

src/components/ChannelDetail/ChannelDetailListLoadingIndicator.tsx (2)

10-12: ⚡ Quick win

Rename type to reflect general channel detail usage.

The type ChannelMembersViewListFooterProps suggests it's specific to the members view, but this component is reused across files, media, and pinned messages views. Consider renaming to ChannelDetailListLoadingIndicatorProps to match the component name and reflect its broader usage.

♻️ Suggested rename
-export type ChannelMembersViewListFooterProps<T> = {
+export type ChannelDetailListLoadingIndicatorProps<T> = {
   searchSource: SearchSource<T>;
 };

 export const ChannelDetailListLoadingIndicator = <T,>({
   searchSource,
-}: ChannelMembersViewListFooterProps<T>) => {
+}: ChannelDetailListLoadingIndicatorProps<T>) => {
🤖 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/ChannelDetail/ChannelDetailListLoadingIndicator.tsx` around
lines 10 - 12, Rename the exported type ChannelMembersViewListFooterProps<T> to
ChannelDetailListLoadingIndicatorProps<T> to reflect the component's broader
usage (component: ChannelDetailListLoadingIndicator); update the type
declaration name and all references/usages (imports, props annotations,
generics) across files that consume this component so the exported generic
SearchSource<T> prop remains named searchSource and the API stays identical.

24-28: ⚡ Quick win

Remove redundant isLoading check.

Line 26 checks {isLoading && <LoadingIndicator />}, but this is redundant because line 22 already returns null when !isLoading. The conditional on line 26 will always evaluate to true at this point.

♻️ Simplify the render
   return (
     <div className='str-chat__loading-indicator-placeholder'>
-      {isLoading && <LoadingIndicator />}
+      <LoadingIndicator />
     </div>
   );
🤖 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/ChannelDetail/ChannelDetailListLoadingIndicator.tsx` around
lines 24 - 28, The render contains a redundant conditional:
ChannelDetailListLoadingIndicator already returns null when !isLoading, so
remove the inner {isLoading && <LoadingIndicator />} and render
<LoadingIndicator /> directly inside the div; update the JSX in
ChannelDetailListLoadingIndicator to replace the conditional expression with a
plain <LoadingIndicator /> (keeping the surrounding div and className intact).
src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx (1)

54-63: ⚡ Quick win

Use one shared Channel-detail component-test harness across src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx and src/components/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx.

The shared root cause is the same in both files: each suite bypasses the repo’s required mock-builder + <Chat>/<Channel> setup and mounts only a lighter provider tree. Reusing the standard Channel-detail test harness in both files will keep these tests aligned with the real provider contract and reduce false confidence from provider-light setups.

As per coding guidelines, "Use mock-builders pattern for tests: getTestClientWithUser(), useMockedApis(), getOrCreateChannelApi(), set connectionId and wsPromise" and "Wrap component tests with , , and relevant sub-components; use channel.watch() after setup".

🤖 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/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx`
around lines 54 - 63, The tests currently use createChannel and
renderWithChannel with a lightweight ChannelDetailProvider; replace these with
the repo's standard channel test harness: use getTestClientWithUser(),
useMockedApis(), getOrCreateChannelApi() to build a mocked client and channel,
set connectionId and wsPromise, call channel.watch() after setup, and render the
component wrapped in <Chat> and <Channel> (instead of only
ChannelDetailProvider) so the provider contract matches real usage; update both
ChannelMembersHeaderActions.defaults.test.tsx and ChannelMemberDetail.test.tsx
to call the shared harness rather than the local createChannel/renderWithChannel
helpers.

Source: Coding guidelines

src/components/ChannelDetail/styling/ChannelMembersView.scss (1)

23-31: 💤 Low value

Add empty line before declaration (stylelint).

Stylelint flagged line 25 for missing an empty line before the display declaration.

✨ Suggested fix
 .str-chat__channel-detail__channel-members-view__list {
   `@include` utils.hide-scrollbar;
+
   display: flex;
   flex-direction: column;
🤖 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/ChannelDetail/styling/ChannelMembersView.scss` around lines 23
- 31, Stylelint reports a missing empty line before the display declaration
inside the .str-chat__channel-detail__channel-members-view__list rule; open the
SCSS rule for .str-chat__channel-detail__channel-members-view__list and insert a
blank line before the "display: flex;" declaration so there is an empty line
separating the preceding mixin/statement (`@include` utils.hide-scrollbar;) and
the display property.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ab854d9-cb41-4fcd-840f-0e75c9329bf3

📥 Commits

Reviewing files that changed from the base of the PR and between a7e9006 and c7352f4.

📒 Files selected for processing (143)
  • examples/vite/src/AppSettings/ActionsMenu/NotificationPromptDialog.tsx
  • examples/vite/src/AppSettings/AppSettings.scss
  • examples/vite/src/AppSettings/AppSettings.tsx
  • examples/vite/src/AppSettings/state.ts
  • examples/vite/src/AppSettings/tabs/ChannelDetail/ChannelDetailTab.tsx
  • examples/vite/src/AppSettings/tabs/ChannelDetail/channelDetailSettings.ts
  • examples/vite/src/AppSettings/tabs/ChannelDetail/index.ts
  • examples/vite/src/AppSettings/tabs/General/GeneralTab.tsx
  • examples/vite/src/AppSettings/tabs/MessageActions/MessageActionsTab.tsx
  • examples/vite/src/AppSettings/tabs/Notifications/NotificationsTab.tsx
  • examples/vite/src/AppSettings/tabs/Reactions/ReactionsTab.tsx
  • examples/vite/src/AppSettings/tabs/SettingsTabLayoutComponents.tsx
  • examples/vite/src/AppSettings/tabs/Sidebar/SidebarTab.tsx
  • examples/vite/src/ChatLayout/ConfiguredChannelDetail.tsx
  • examples/vite/src/ChatLayout/Panels.tsx
  • examples/vite/src/SystemNotification/SystemNotification.tsx
  • examples/vite/src/icons.tsx
  • src/components/AudioPlayback/components/index.ts
  • src/components/Avatar/AvatarWithChannelDetail.tsx
  • src/components/Avatar/ChannelAvatar.tsx
  • src/components/Avatar/__tests__/GroupAvatar.test.tsx
  • src/components/Avatar/index.ts
  • src/components/Avatar/styling/AvatarWithChannelDetail.scss
  • src/components/Avatar/styling/index.scss
  • src/components/ChannelDetail/ChannelDetail.tsx
  • src/components/ChannelDetail/ChannelDetailContext.tsx
  • src/components/ChannelDetail/ChannelDetailEmptyList.tsx
  • src/components/ChannelDetail/ChannelDetailListLoadingIndicator.tsx
  • src/components/ChannelDetail/ChannelDetailNavButton.tsx
  • src/components/ChannelDetail/ChannelDetailSearchInput.tsx
  • src/components/ChannelDetail/Views/ChannelFilesView/ChannelFilesEmptyList.tsx
  • src/components/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.tsx
  • src/components/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.ts
  • src/components/ChannelDetail/Views/ChannelFilesView/__tests__/ChannelFilesView.test.tsx
  • src/components/ChannelDetail/Views/ChannelFilesView/index.ts
  • src/components/ChannelDetail/Views/ChannelFilesView/useChannelFilesSearch.ts
  • src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx
  • src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx
  • src/components/ChannelDetail/Views/ChannelManagementView/index.ts
  • src/components/ChannelDetail/Views/ChannelMediaView/ChannelMediaEmptyList.tsx
  • src/components/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx
  • src/components/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.utils.ts
  • src/components/ChannelDetail/Views/ChannelMediaView/__tests__/ChannelMediaView.test.tsx
  • src/components/ChannelDetail/Views/ChannelMediaView/index.ts
  • src/components/ChannelDetail/Views/ChannelMediaView/useChannelMediaSearch.ts
  • src/components/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx
  • src/components/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberDetail.tsx
  • src/components/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx
  • src/components/ChannelDetail/Views/ChannelMemberDetailView/index.ts
  • src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersRemoveView.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.ts
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersAddView.test.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersBrowseView.test.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersRemoveView.test.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersView.test.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersView.utils.test.ts
  • src/components/ChannelDetail/Views/ChannelMembersView/__tests__/testUtils.tsx
  • src/components/ChannelDetail/Views/ChannelMembersView/index.ts
  • src/components/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts
  • src/components/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesEmptyList.tsx
  • src/components/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesView.tsx
  • src/components/ChannelDetail/Views/PinnedMessagesView/__tests__/PinnedMessagesView.test.tsx
  • src/components/ChannelDetail/Views/PinnedMessagesView/index.ts
  • src/components/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.ts
  • src/components/ChannelDetail/__tests__/ChannelDetail.test.tsx
  • src/components/ChannelDetail/__tests__/ChannelManagementActions.defaults.test.tsx
  • src/components/ChannelDetail/__tests__/ChannelManagementView.test.tsx
  • src/components/ChannelDetail/index.ts
  • src/components/ChannelDetail/styling/ChannelDetail.scss
  • src/components/ChannelDetail/styling/ChannelFilesView.scss
  • src/components/ChannelDetail/styling/ChannelManagementView.scss
  • src/components/ChannelDetail/styling/ChannelMediaView.scss
  • src/components/ChannelDetail/styling/ChannelMemberDetailView.scss
  • src/components/ChannelDetail/styling/ChannelMembersView.scss
  • src/components/ChannelDetail/styling/ChannelMembersViewListFooter.scss
  • src/components/ChannelDetail/styling/PinnedMessagesView.scss
  • src/components/ChannelDetail/styling/index.scss
  • src/components/ChannelHeader/hooks/useChannelHasMembersOnline.ts
  • src/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.ts
  • src/components/ChannelListItem/__tests__/ChannelListItemActionButtons.defaults.test.tsx
  • src/components/ChannelListItem/hooks/index.ts
  • src/components/ChannelListItem/hooks/useChannelPreviewInfo.ts
  • src/components/ChannelListItem/hooks/useIsUserMuted.ts
  • src/components/Chat/Chat.tsx
  • src/components/Chat/__tests__/Chat.test.tsx
  • src/components/Dialog/components/Prompt.tsx
  • src/components/Dialog/service/DialogPortal.tsx
  • src/components/Dialog/styling/Dialog.scss
  • src/components/Dialog/styling/Prompt.scss
  • src/components/FileIcon/FileIcon.tsx
  • src/components/FileIcon/iconMap.ts
  • src/components/Form/Checkbox.tsx
  • src/components/Form/SwitchField.tsx
  • src/components/Form/index.ts
  • src/components/Form/styling/Checkbox.scss
  • src/components/Form/styling/index.scss
  • src/components/Icons/icons.tsx
  • src/components/ListItemLayout/ListItemLayout.tsx
  • src/components/ListItemLayout/index.ts
  • src/components/ListItemLayout/styling/ListItemLayout.scss
  • src/components/ListItemLayout/styling/index.scss
  • src/components/MessageActions/MessageActions.defaults.tsx
  • src/components/MessageComposer/__tests__/MessageInput.test.tsx
  • src/components/Modal/GlobalModal.tsx
  • src/components/Modal/__tests__/GlobalModal.test.tsx
  • src/components/Modal/styling/Modal.scss
  • src/components/Notifications/hooks/__tests__/useNotificationApi.test.tsx
  • src/components/Notifications/hooks/useNotificationApi.ts
  • src/components/Poll/PollCreationDialog/MultipleAnswersField.tsx
  • src/components/Poll/PollOptionSelector.tsx
  • src/components/Poll/styling/PollCreationDialog.scss
  • src/components/Poll/styling/PollOptionList.scss
  • src/components/SectionNavigator/SectionNavigator.tsx
  • src/components/SectionNavigator/SectionNavigatorHeader.tsx
  • src/components/SectionNavigator/__tests__/SectionNavigator.test.tsx
  • src/components/SectionNavigator/__tests__/SectionNavigatorHeader.test.tsx
  • src/components/SectionNavigator/index.ts
  • src/components/SectionNavigator/styling/SectionNavigator.scss
  • src/components/SectionNavigator/styling/index.scss
  • src/components/index.ts
  • src/context/DialogManagerContext.tsx
  • 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/styling/_utils.scss
  • src/styling/index.scss
  • src/utils/__tests__/isDmChannel.test.ts
  • src/utils/index.ts
  • src/utils/isDmChannel.ts
💤 Files with no reviewable changes (2)
  • src/components/Poll/styling/PollOptionList.scss
  • examples/vite/src/icons.tsx

Comment on lines +18 to +20
useEffect(() => {
setSearchInput('');
}, [resetKey]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the upstream query when resetKey changes.

This effect only clears the local input state, so consumers can be left searching with the previous query while the field looks empty. Call onSearchChange('') here as well to keep the visible input and the active search source in sync.

🤖 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/ChannelDetail/ChannelDetailSearchInput.tsx` around lines 18 -
20, The useEffect that currently only calls setSearchInput('') when resetKey
changes must also reset the upstream query by calling onSearchChange('') so the
visible input and the active search source stay in sync; update the effect in
ChannelDetailSearchInput (the useEffect referencing resetKey and setSearchInput)
to invoke onSearchChange('') as well (guarding if onSearchChange is provided)
whenever resetKey changes.

Comment on lines +122 to +129
return useMemo(() => {
const stateMembers = Object.values(channel.state?.members ?? {});
const members = stateMembers.length ? stateMembers : (channel.data?.members ?? []);

return members.find(
(member) => member.user?.id && member.user.id !== client.user?.id,
);
}, [channel, client.user?.id]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stop using the mutable channel object in hook dependency arrays—use channel.cid

src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx memoizes useOtherMember with useMemo(..., [channel, client.user?.id]). Replace the channel dependency with stable channel.cid (keep using the live channel inside the callback body, but don’t include it in the deps) to avoid handler churn and align with the repo rule. Also audit the other useMemo/useCallback dependencies in this file for channel.

🤖 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/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx`
around lines 122 - 129, The useMemo for computing the other member currently
depends on the mutable channel object which causes unnecessary churn; change the
dependency array to use channel.cid and client.user?.id instead of channel,
keeping the live channel reference inside the callback (i.e., modify the useMemo
call that returns members.find(...) to use [channel.cid, client.user?.id]). Also
scan this file for other useMemo/useCallback hooks (e.g., any other hooks
referencing channel in their deps) and replace mutable channel dependencies with
channel.cid to follow the repo rule while retaining access to channel inside
callbacks.

Comment on lines +145 to +174
return {
canBlockUser:
isDmChannelWithOtherUser && ownCapabilities?.includes('ban-channel-members'),
canLeaveChannel: isGroupChannel && ownCapabilities?.includes('leave-channel'),
canMuteChannel: ownCapabilities?.includes('mute-channel'),
canMuteUser: isDmChannelWithOtherUser,
};
};

export const useBaseChannelManagementActionSetFilter = (
channelManagementActionSet: ChannelManagementActionItem[],
) => {
const { canBlockUser, canLeaveChannel, canMuteChannel, canMuteUser } =
useChannelManagementActionFilterState();

return useMemo(
() =>
channelManagementActionSet.filter((action) => {
switch (action.type) {
case 'blockUser':
return canBlockUser;
case 'muteChannel':
return canMuteChannel;
case 'muteUser':
return canMuteUser;
case 'leaveChannel':
return canLeaveChannel;
default:
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx"

echo "=== File exists? ==="
ls -l "$FILE"

echo
echo "=== useChannelManagementActionFilterState (capability computations) ==="
# Print around the hook/function definition; fallback to searching for its name
rg -n "useChannelManagementActionFilterState" "$FILE"
rg -n "ban-channel-members|leave-channel|mute-channel|mute-user|block|deleteChat|delete.*channel|channel.delete" "$FILE" || true

# Show around the capability hook
line="$(rg -n "useChannelManagementActionFilterState" "$FILE" | head -n1 | cut -d: -f1)"
start=$((line-60))
end=$((line+80))
sed -n "${start},${end}p" "$FILE" | cat -n

echo
echo "=== useBaseChannelManagementActionSetFilter switch cases ==="
# Show around the filter function provided in the snippet
line2="$(rg -n "useBaseChannelManagementActionSetFilter" "$FILE" | head -n1 | cut -d: -f1)"
start2=$((line2-40))
end2=$((line2+120))
sed -n "${start2},${end2}p" "$FILE" | cat -n

echo
echo "=== Find deleteChat action item definition and its conditions ==="
rg -n "deleteChat" "$FILE"
# Show a wider window around each occurrence, but cap output
for l in $(rg -n "deleteChat" "$FILE" | cut -d: -f1 | sort -n | head -n 10); do
  start=$((l-70))
  end=$((l+120))
  echo "--- Context around deleteChat at line $l (showing $start-$end) ---"
  sed -n "${start},${end}p" "$FILE" | cat -n
done

echo
echo "=== Find blockUser/unBlockUser handler gating or capability checks ==="
rg -n "blockUser|unBlockUser" "$FILE"
for l in $(rg -n "blockUser|unBlockUser" "$FILE" | cut -d: -f1 | sort -n | head -n 10); do
  start=$((l-60))
  end=$((l+120))
  echo "--- Context around block/unblock at line $l (showing $start-$end) ---"
  sed -n "${start},${end}p" "$FILE" | cat -n
done

echo
echo "=== Search for tests asserting UI visibility gating ==="
# If tests exist, look for the test file name and relevant strings
TESTFILE="$(fd -t f "ChannelManagementActions.*test.*" src | head -n 20 | head -n 1 || true)"
echo "Possible test file: $TESTFILE"
if [ -n "${TESTFILE:-}" ]; then
  rg -n "deleteChat|blockUser|ban-channel-members|leave-channel|mute-channel" "$TESTFILE" || true
  # show some surrounding test blocks
  rg -n "deleteChat|blockUser" "$TESTFILE" || true
fi

echo
echo "=== Done ==="

Repository: GetStream/stream-chat-react

Length of output: 16417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx"

echo "=== Key line numbers ==="
rg -n "const useChannelManagementActionFilterState|own_capabilities|ban-channel-members|deleteChat|type: 'deleteChat'|blockUser|unBlockUser" "$FILE" | head -n 80

echo
echo "=== Capability gating state (around useChannelManagementActionFilterState) ==="
start=120; end=190
sed -n "${start},${end}p" "$FILE" | cat -n

echo
echo "=== Filter switch (useBaseChannelManagementActionSetFilter) ==="
start=140; end=220
sed -n "${start},${end}p" "$FILE" | cat -n

echo
echo "=== deleteChat handler (around const deleteChat) ==="
# Based on prior output: deleteChat starts ~644
start=620; end=710
sed -n "${start},${end}p" "$FILE" | cat -n

echo
echo "=== deleteChat action item (around type: 'deleteChat') ==="
# Based on prior output: type: 'deleteChat' around ~736
start=690; end=780
sed -n "${start},${end}p" "$FILE" | cat -n

echo
echo "=== blockUser handler/action item (around blockUser) ==="
# Based on prior output: blockUser around ~474 and action item around ~700? We'll print broader window.
start=430; end=610
sed -n "${start},${end}p" "$FILE" | cat -n

echo
echo "=== Find where default action set items are built and how deleteChat/blockUser are included ==="
# Look for functions/constants that build the action set; limit to nearby names
rg -n "default action|action set|ChannelManagementActionItem|channelManagementActionSet|useBaseChannelManagementActionSetFilter" "$FILE" | head -n 80

echo
echo "=== Search for tests ==="
# Find any test files relevant to ChannelManagementActions.defaults
fd -t f "*ChannelManagementActions*defaults*test*" src | head -n 20
fd -t f "*ChannelManagementActions*test*" src | head -n 50

Repository: GetStream/stream-chat-react

Length of output: 19741


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RG_OPTS="--no-heading --line-number"
FILE="src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx"

echo "=== Find usages of defaultChannelManagementActionSet and useBaseChannelManagementActionSetFilter ==="
rg ${RG_OPTS} "defaultChannelManagementActionSet|useBaseChannelManagementActionSetFilter" src/components/ChannelDetail/Views/ChannelManagementView -S
rg ${RG_OPTS} "defaultChannelManagementActionSet|useBaseChannelManagementActionSetFilter" src -S

echo
echo "=== Search for capability gating related to delete/chat across repo (delete-channel, delete-messages, etc.) ==="
rg ${RG_OPTS} "delete-channel|deleteChat|channel:delete|own_capabilities.*delete|own_capabilities.*remove|delete.*capabilit|channel.delete" src -S

echo
echo "=== Locate test files mentioning ChannelManagementActions.defaults ==="
# Use literal-glob patterns instead of regex
fd -t f "*ChannelManagementActions.defaults*" src | head -n 50
fd -t f "*ChannelManagementActions*" src | head -n 50
fd -t f "*ChannelManagementActions*test*" src | head -n 50
fd -t f "*ChannelManagement*test*" src | head -n 50

echo
echo "=== Search for tests that mention deleteChat/blockUser visibility ==="
for tf in $(fd -t f "*test*" src | head -n 200); do
  rg ${RG_OPTS} "deleteChat|blockUser|ban-channel-members|leave-channel|mute-channel|delete chat" "$tf" -S >/dev/null 2>&1 && echo "MATCH: $tf"
done

echo
echo "=== Search for capability strings used for own_capabilities in this area ==="
rg ${RG_OPTS} "own_capabilities?.includes\\('([^']+)'\\)" src/components/ChannelDetail/Views/ChannelManagementView -S

Repository: GetStream/stream-chat-react

Length of output: 6182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

TESTFILE="src/components/ChannelDetail/__tests__/ChannelManagementActions.defaults.test.tsx"
if [ -f "$TESTFILE" ]; then
  echo "=== Test file line count ==="
  wc -l "$TESTFILE"
  echo

  echo "=== Test file around matches (400-520) ==="
  sed -n '380,520p' "$TESTFILE" | cat -n
else
  echo "Missing $TESTFILE"
fi

echo
echo "=== ChannelManagementView usage (around where filtering is applied) ==="
FILE2="src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx"
if [ -f "$FILE2" ]; then
  wc -l "$FILE2"
  rg -n "channelManagementActionSet|useBaseChannelManagementActionSetFilter|defaultChannelManagementActionSet" "$FILE2" | head -n 50
  echo
  sed -n '360,480p' "$FILE2" | cat -n
else
  echo "Missing $FILE2"
fi

Repository: GetStream/stream-chat-react

Length of output: 7851


Gate deleteChat by permissions; blockUser capability check matches its API call

  • useBaseChannelManagementActionSetFilter only capability-gates blockUser/muteChannel/muteUser/leaveChannel; for anything else (including deleteChat) it falls through to default: return true, so deleteChat remains visible even when channel.data.own_capabilities is empty (see the related unit test expecting only ['deleteChat']).
  • DeleteChatAction unconditionally calls channel.delete(), so its visibility should also be tied to the appropriate own_capabilities (instead of pass-through).
  • The blockUser gating (ban-channel-members in canBlockUser) aligns with the action invoking client.blockUser()/unBlockUser(), so that specific mismatch isn’t supported by the code.
🤖 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/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx`
around lines 145 - 174, Add a permission gate for the 'deleteChat' action: in
useChannelManagementActionFilterState expose a new flag (e.g., canDeleteChat)
set from ownCapabilities?.includes('delete-channel') (or the actual capability
name DeleteChatAction expects), then update
useBaseChannelManagementActionSetFilter to handle the 'deleteChat' action type
(case 'deleteChat': return canDeleteChat). Also ensure DeleteChatAction itself
checks the same capability flag before calling channel.delete(); keep the
existing blockUser check (ban-channel-members) as-is since it aligns with the
client.blockUser/unBlockUser calls.

Comment on lines +171 to +201
const initialName = channel.data?.name ?? '';
const [name, setName] = useState(initialName);
// null = keep current avatar, File = replace it, 'removed' = clear it
const [imageEdit, setImageEdit] = useState<File | 'removed' | null>(null);
const [isSaving, setIsSaving] = useState(false);

const fileInputRef = useRef<HTMLInputElement>(null);

const pickedFile = imageEdit instanceof File ? imageEdit : null;

// Preview the locally picked file, releasing the object URL when it changes or unmounts.
const objectUrl = useMemo(
() => (pickedFile ? URL.createObjectURL(pickedFile) : null),
[pickedFile],
);

useEffect(
() => () => {
if (objectUrl) URL.revokeObjectURL(objectUrl);
},
[objectUrl],
);

const previewImageUrl =
objectUrl ?? (imageEdit === 'removed' ? undefined : displayImage);

const trimmedName = name.trim();
const nameChanged = trimmedName !== initialName.trim();
const imageChanged = imageEdit !== null;
const hasChanges = (trimmedName.length > 0 && nameChanged) || imageChanged;
const canSubmit = trimmedName.length > 0 && !isSaving && hasChanges;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the dirty baseline after a successful save.

initialName is captured only once, but success only clears imageEdit. After updatePartial() resolves, a renamed channel still satisfies nameChanged, so the Save button stays visible/enabled even though there are no unsaved changes left. Update the baseline to the saved values or exit edit mode on success so repeated identical writes are not possible.

Also applies to: 243-256

🤖 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/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx`
around lines 171 - 201, The save path leaves the original baseline (initialName)
unchanged so nameChanged stays true after updatePartial; change the component to
track a mutable baseline (e.g., replace const initialName with a state or ref
like baselineName via useState/useRef and compute nameChanged/hasChanges against
baselineName), and after a successful updatePartial() call update that baseline
to the newly saved trimmedName and clear imageEdit (setImageEdit(null)) and
isSaving as before (or alternatively exit edit mode). Update references to
initialName in comparisons to use baselineName so the Save button disables when
there are no real unsaved changes.

Comment on lines +148 to +161
<Modal
className={clsx(
'str-chat__gallery-modal',
'str-chat__channel-detail__media-view__viewer',
)}
onClose={closeViewer}
open={viewerOpen}
>
<Gallery
GalleryUI={GalleryUI}
initialIndex={selectedIndex}
items={galleryItems}
onRequestClose={closeViewer}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an accessible name to the media viewer modal.

This dialog opens without aria-label/aria-labelledby, and the rendered Gallery here does not provide a visible dialog title. Screen readers will announce an unnamed dialog, which makes the viewer hard to identify and navigate.

♿ Suggested fix
       <Modal
+        aria-label={t('Media viewer')}
         className={clsx(
           'str-chat__gallery-modal',
           'str-chat__channel-detail__media-view__viewer',
         )}
         onClose={closeViewer}
         open={viewerOpen}
       >
📝 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.

Suggested change
<Modal
className={clsx(
'str-chat__gallery-modal',
'str-chat__channel-detail__media-view__viewer',
)}
onClose={closeViewer}
open={viewerOpen}
>
<Gallery
GalleryUI={GalleryUI}
initialIndex={selectedIndex}
items={galleryItems}
onRequestClose={closeViewer}
/>
<Modal
aria-label={t('Media viewer')}
className={clsx(
'str-chat__gallery-modal',
'str-chat__channel-detail__media-view__viewer',
)}
onClose={closeViewer}
open={viewerOpen}
>
<Gallery
GalleryUI={GalleryUI}
initialIndex={selectedIndex}
items={galleryItems}
onRequestClose={closeViewer}
/>
🤖 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/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx`
around lines 148 - 161, The Modal used in ChannelMediaView opens the media
viewer without an accessible name; update the Modal invocation in
ChannelMediaView (the Modal wrapping Gallery/GalleryUI) to include an accessible
name by adding an aria-label or aria-labelledby prop, and if you use
aria-labelledby add a visually-hidden title element (e.g., "Media viewer" or
localized string) with that id inside the Modal/GalleryUI so screen readers
announce the dialog; ensure the id is unique and referenced from Modal (and keep
existing props like open={viewerOpen} and onClose={closeViewer} intact).

Comment on lines +132 to +133
const renderWithChannel = (ui: React.ReactElement, channel: Channel = createChannel()) =>
render(<ChannelDetailProvider channel={channel}>{ui}</ChannelDetailProvider>);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use the standard Chat/Channel wrapper for this component test.

Rendering through ChannelDetailProvider alone bypasses the provider stack this repo expects for component tests, so this suite can miss context-wiring regressions that only surface under <Chat>/<Channel>. As per coding guidelines, "**/__tests__/**/*.{ts,tsx}: Wrap component tests with , , and relevant sub-components; use channel.watch() after setup`."

🤖 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/ChannelDetail/Views/PinnedMessagesView/__tests__/PinnedMessagesView.test.tsx`
around lines 132 - 133, The test helper renderWithChannel currently mounts the
UI inside ChannelDetailProvider only; update it to wrap the component with the
standard Chat/Channel test stack instead: render the UI inside <Chat
client={...}> and <Channel channel={channel}> (preserving ChannelDetailProvider
if still needed), and after creating the channel via createChannel() call
channel.watch() (or mock a watched channel) before rendering so the component
sees the same context wiring as real usage; adjust the helper name if desired
and update tests that use renderWithChannel to rely on the new wrapper.

Source: Coding guidelines

Comment on lines +25 to +30
const fallbackPinnedMessages = useMemo(
// sort descending by creation date
() =>
channel.state?.pinnedMessages?.sort(
(a, b) => b.created_at.getTime() - a.created_at.getTime(),
) ?? [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t sort channel.state.pinnedMessages in place.

sort() mutates the SDK-owned array, so this hook is reordering shared channel state just to derive a fallback view. That can leak a local presentation concern into every other consumer reading channel.state.pinnedMessages. As per coding guidelines, "**/*.{ts,tsx}: Do not change message sort order; the SDK maintains order, and local changes will conflict`."

Suggested update
   const fallbackPinnedMessages = useMemo(
     // sort descending by creation date
     () =>
-      channel.state?.pinnedMessages?.sort(
-        (a, b) => b.created_at.getTime() - a.created_at.getTime(),
-      ) ?? [],
+      [...(channel.state?.pinnedMessages ?? [])].sort(
+        (a, b) => b.created_at.getTime() - a.created_at.getTime(),
+      ),
     [channel],
   );
📝 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.

Suggested change
const fallbackPinnedMessages = useMemo(
// sort descending by creation date
() =>
channel.state?.pinnedMessages?.sort(
(a, b) => b.created_at.getTime() - a.created_at.getTime(),
) ?? [],
const fallbackPinnedMessages = useMemo(
// sort descending by creation date
() =>
[...(channel.state?.pinnedMessages ?? [])].sort(
(a, b) => b.created_at.getTime() - a.created_at.getTime(),
),
[channel],
);
🤖 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/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.ts`
around lines 25 - 30, The fallbackPinnedMessages computation in
usePinnedMessagesSearch currently calls sort() directly on
channel.state?.pinnedMessages which mutates the SDK-owned array; instead create
a shallow copy of channel.state?.pinnedMessages (e.g., via slice() or spread)
and sort that copy so you don't mutate the original array; update the
fallbackPinnedMessages expression to copy then sort while keeping the same
descending-by-created_at comparator, referencing fallbackPinnedMessages and
channel.state?.pinnedMessages in your change.

Source: Coding guidelines

Comment on lines +17 to +53
const [watchers, setWatchers] = useState<ChannelState['watchers']>(() =>
Object.assign({}, channel?.state?.watchers ?? {}),
);

useEffect(() => {
setWatchers(Object.assign({}, channel?.state?.watchers ?? {}));
}, [channel]);

useEffect(() => {
if (!enabled || !channel) return;

const startSubscription = channel.on('user.watching.start', (event) => {
setWatchers((prev) => {
if (!event.user?.id) return prev;
if (prev[event.user.id]) return prev;
return Object.assign({ [event.user.id]: event.user }, prev);
});
});
const stopSubscription = channel.on('user.watching.stop', (event) => {
setWatchers((prev) => {
if (!event.user?.id || !prev[event.user.id]) return prev;

const next = Object.assign({}, prev);
delete next[event.user.id];
return next;
});
});

return () => {
startSubscription.unsubscribe();
stopSubscription.unsubscribe();
};
}, [channel, enabled]);

if (!enabled) return false;

return Object.keys(watchers).length > 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Exclude the current user from the watcher set.

This hook returns true when any watcher exists, including the local user. In a DM that means the header/avatar can show the other participant as online as soon as I am watching the channel, even if they are offline. Filter out client.user.id here or make the hook target the non-self member explicitly.

🤖 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/ChannelHeader/hooks/useChannelHasMembersOnline.ts` around
lines 17 - 53, The hook currently counts the local user as a watcher, so update
the initialization and update logic to exclude the client user id: when
constructing initial watchers state and in the effect that runs
setWatchers(Object.assign({}, channel?.state?.watchers ?? {})), filter out
client.user?.id from the object; likewise, in the 'user.watching.start' and
'user.watching.stop' handlers (startSubscription/stopSubscription) ignore events
where event.user?.id === client.user?.id and when adding/removing entries ensure
you never add the client id into watchers; finally, when computing the return
value (Object.keys(watchers).length > 0) this will then reflect only other users
watching the channel.

Comment on lines +13 to +22
.str-chat__list-item-layout__container {
--list-item-padding: var(--str-chat__spacing-xs) var(--str-chat__spacing-sm);
display: flex;
flex: 1;
align-items: center;
gap: var(--str-chat__spacing-sm);
min-width: 0;
text-align: start;
padding: var(--list-item-padding);
border-radius: var(--str-chat__radius-md);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the new Stylelint failures.

Lines 15, 31, and 133 violate declaration-empty-line-before, so this stylesheet will fail lint as written.

🧹 Proposed fix
   .str-chat__list-item-layout__container {
     --list-item-padding: var(--str-chat__spacing-xs) var(--str-chat__spacing-sm);
+
     display: flex;
     flex: 1;
     align-items: center;
     gap: var(--str-chat__spacing-sm);
     min-width: 0;
@@
   .str-chat__list-item-layout__container:where(button) {
     `@include` utils.button-reset;
+
     padding: var(--list-item-padding);
     cursor: pointer;
@@
   .str-chat__list-item-layout__subtitle,
   .str-chat__list-item-layout__description,
   .str-chat__list-item-layout__title {
     `@include` utils.ellipsis-text;
+
     max-width: 100%;
   }

Based on static analysis, Stylelint 17.12.0 reports declaration-empty-line-before at Lines 15, 31, and 133.

Also applies to: 29-32, 129-134

🧰 Tools
🪛 Stylelint (17.12.0)

[error] 15-15: 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 13 -
22, Stylelint flags are due to missing empty lines before certain declarations
in this stylesheet (e.g., inside .str-chat__list-item-layout__container and the
blocks around lines 29-32 and 129-134); fix by inserting a single blank line
before the offending declaration(s) so they comply with
declaration-empty-line-before (or collapse/merge those declarations so the rule
no longer applies), targeting the selector
.str-chat__list-item-layout__container and the other nearby rule blocks reported
by the linter; re-run Stylelint to confirm no more declaration-empty-line-before
errors.

Source: Linters/SAST tools

Comment thread src/utils/isDmChannel.ts
Comment on lines +10 to +15
const memberCount = channel.data?.member_count ?? 0;
return (
memberCount === 1 ||
(memberCount === 2 &&
!!ownUserId &&
Object.values(channel.state?.members).some(({ user }) => user?.id === ownUserId))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List repo files for context
ls -la

# Inspect the target file with line numbers
echo "---- src/utils/isDmChannel.ts ----"
if [ -f "src/utils/isDmChannel.ts" ]; then
  nl -ba src/utils/isDmChannel.ts | sed -n '1,200p'
else
  echo "Missing src/utils/isDmChannel.ts"
  exit 1
fi

# Find usages
echo "---- rg 'isDmChannel(' ----"
rg -n "isDmChannel\\s*\\(" -S src || true

# Inspect any related consumer file mentioned in the review comment
echo "---- Consumer: ChannelManagementActions.defaults.tsx ----"
if [ -f "src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx" ]; then
  nl -ba src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx | sed -n '1,240p'
else
  echo "Missing consumer file at expected path"
fi

# Check TypeScript types for Channel/Member shape (best-effort)
echo "---- Search for 'member_count' and 'members' types ----"
rg -n "member_count" -S src || true
rg -n "state\\.members" -S src || true
rg -n "members\\??\\:" -S src || true

Repository: GetStream/stream-chat-react

Length of output: 3450


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/utils/isDmChannel.ts (first 200 lines, with line numbers) ----"
if [ -f "src/utils/isDmChannel.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/utils/isDmChannel.ts | sed -n '1,200p'
else
  echo "Missing src/utils/isDmChannel.ts"
  exit 1
fi

echo "---- rg 'isDmChannel(' in src ----"
rg -n "isDmChannel\\s*\\(" src || true

echo "---- Inspect file mentioned in review comment ----"
if [ -f "src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx | sed -n '1,260p'
else
  echo "Missing consumer file path"
fi

echo "---- Search for channel.state?.members / members usage ----"
rg -n "state\\?\\.members|state\\.members|members\\?" src/utils src/components src || true

echo "---- Search for member_count usage ----"
rg -n "member_count" src || true

Repository: GetStream/stream-chat-react

Length of output: 28751


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- src/utils/__tests__/isDmChannel.test.ts ----"
if [ -f "src/utils/__tests__/isDmChannel.test.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/utils/__tests__/isDmChannel.test.ts | sed -n '1,220p'
else
  echo "Missing test file"
fi

echo "---- Inspect call site: useChannelHeaderOnlineStatus ----"
if [ -f "src/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.ts | sed -n '1,140p'
fi

echo "---- Inspect call site: ChannelManagementView.tsx (DM checks) ----"
if [ -f "src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx" ]; then
  rg -n "resolvedIsDmChannel\\s*=\\s*isDmChannel\\(" src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx | sed -n '40,120p'
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx | sed -n '150,220p'
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx | sed -n '380,460p'
fi

echo "---- Inspect call site: ChannelManagementActions.defaults.tsx (DM check line ~136) ----"
if [ -f "src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx | sed -n '110,175p'
fi

Repository: GetStream/stream-chat-react

Length of output: 16701


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Inspect src/components/Channel/channelState.ts around member initialization ----"
if [ -f "src/components/Channel/channelState.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/Channel/channelState.ts | sed -n '1,220p'
else
  echo "Missing src/components/Channel/channelState.ts"
fi

echo "---- Search for 'members: { ...channel.state.members }' ----"
rg -n "members: \\{ \\.+channel\\.state\\.members" -S src/components/Channel/channelState.ts src || true

echo "---- Inspect ChannelStateContext type for members ----"
if [ -f "src/context/ChannelStateContext.tsx" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/context/ChannelStateContext.tsx | sed -n '1,140p'
else
  echo "Missing src/context/ChannelStateContext.tsx"
fi

echo "---- Inspect any code that may set state.members to undefined ----"
rg -n "state\\.members\\s*=\\s*undefined|members\\s*:\\s*undefined" -S src || true

Repository: GetStream/stream-chat-react

Length of output: 11222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Search for user_id fallbacks when iterating members ----"
rg -n "user_id" src/components src/utils -S || true

echo "---- Search for Object.values(channel.state?.members) usage patterns ----"
rg -n "Object\\.values\\(channel\\.state\\?\\.members\\)" src/components src/utils -S || true

echo "---- Search for Object.values(channel.state.members) usage patterns ----"
rg -n "Object\\.values\\(channel\\.state\\.members\\)" src/components src/utils -S || true

echo "---- Search for direct checks on member.user?.id within members iterations ----"
rg -n "member\\.user\\?\\.id|\\{ user \\}\\) =>" src/components src/utils -S || true

Repository: GetStream/stream-chat-react

Length of output: 8957


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Search for ChannelStateProvider usage (where members initial state is set) ----"
rg -n "ChannelStateProvider\\b" src/components src/context -S

echo "---- Inspect src/components/Channel/Channel.tsx for initial channel state context value ----"
if [ -f "src/components/Channel/Channel.tsx" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/Channel/Channel.tsx | sed -n '380,620p'
else
  echo "Missing src/components/Channel/Channel.tsx"
fi

echo "---- Search within Channel.tsx for 'members:' or 'state:' initialization ----"
rg -n "members\\s*:" src/components/Channel/Channel.tsx -S || true
rg -n "state\\.members|channel\\.state\\.members" src/components/Channel/Channel.tsx -S || true

echo "---- Inspect src/context/ChannelStateContext.tsx provider setup if any helper initializes members ----"
if [ -f "src/context/ChannelStateContext.tsx" ]; then
  rg -n "members\\b|member" src/context/ChannelStateContext.tsx -S || true
fi

Repository: GetStream/stream-chat-react

Length of output: 24564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Check if node_modules/stream-chat exists (to inspect types) ----"
ls -la node_modules/stream-chat 2>/dev/null || echo "node_modules/stream-chat not present"

echo "---- Inspect src/components/ChannelList/hooks/useChannelListShape.ts around members normalization ----"
if [ -f "src/components/ChannelList/hooks/useChannelListShape.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelList/hooks/useChannelListShape.ts | sed -n '180,280p'
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelList/hooks/useChannelListShape.ts | sed -n '400,470p'
else
  echo "Missing file src/components/ChannelList/hooks/useChannelListShape.ts"
fi

echo "---- Inspect src/components/Channel/Channel.tsx around member mapping at ~450-480 ----"
if [ -f "src/components/Channel/Channel.tsx" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/Channel/Channel.tsx | sed -n '430,520p'
else
  echo "Missing file src/components/Channel/Channel.tsx"
fi

Repository: GetStream/stream-chat-react

Length of output: 10112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Inspect src/utils/getChannel.ts ----"
if [ -f "src/utils/getChannel.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/utils/getChannel.ts | sed -n '1,240p'
else
  echo "Missing src/utils/getChannel.ts"
fi

echo "---- Search for mockChannelStateContext implementation ----"
rg -n "function\\s+mockChannelStateContext|mockChannelStateContext\\s*=" src -S

echo "---- Inspect mockChannelStateContext definition (if found) ----"
# try to open first match(s)
matches=$(rg -n "mockChannelStateContext" src -S | head -n 20)
echo "$matches"
# open any file that appears to define it
first_file=$(echo "$matches" | head -n 1 | awk -F: '{print $1}')
if [ -n "${first_file:-}" ] && [ -f "$first_file" ]; then
  echo "---- Open: $first_file ----"
  awk '{printf "%6d\t%s\n", NR, $0}' "$first_file" | sed -n '1,220p'
fi

echo "---- Inspect any tests that call isDmChannel with missing state.members ----"
rg -n "isDmChannel\\(.*members" src/utils/__tests__ -S || true

Repository: GetStream/stream-chat-react

Length of output: 8447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Inspect ChannelMembersView.utils.ts ----"
if [ -f "src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.ts | sed -n '1,120p'
fi

echo "---- Inspect ChannelMembersView.utils.test.ts ----"
if [ -f "src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersView.utils.test.ts" ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' src/components/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersView.utils.test.ts | sed -n '1,140p'
fi

Repository: GetStream/stream-chat-react

Length of output: 4703


Guard isDmChannel against missing members + use user_id fallback

Object.values(channel.state?.members) will throw if channel.state.members is ever undefined (needs ?? {} fallback). Also, the two-member check only compares member.user?.id and ignores member.user_id, unlike other member utilities that fall back to user_id, so partially hydrated members can be misclassified.

🤖 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/utils/isDmChannel.ts` around lines 10 - 15, The isDmChannel check should
guard against missing members and consider partially hydrated members: when
iterating members use Object.values(channel.state?.members ?? {}) instead of
Object.values(channel.state?.members) to avoid throwing when members is
undefined, and in the two-member branch check both member.user?.id and
member.user_id (e.g., compare ownUserId to (member.user?.id ?? member.user_id))
so fallback IDs are handled; keep the existing memberCount logic (memberCount
=== 1 || (memberCount === 2 && !!ownUserId && ...)) and update only the members
access and ID comparison.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx (1)

95-100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the leading . from these icon class names.

className takes raw class tokens, not CSS selectors. With the leading dot, the rendered token is literally .str-chat__icon--destructive, so the destructive icon styles never match.

Proposed fix
 const BlockUserActionIcon = () => (
-  <IconNoSign className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
+  <IconNoSign className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
 );

 const RemoveUserActionIcon = () => (
-  <IconUserRemove className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--remove-user' />
+  <IconUserRemove className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--remove-user' />
 );
Based on learnings, icon classes in this repo use plain `str-chat__icon--` tokens, so the selector-style `.` prefix is invalid here.
🤖 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/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx`
around lines 95 - 100, The icon className strings in BlockUserActionIcon and
RemoveUserActionIcon incorrectly include leading dots (treating them like CSS
selectors), causing classes like ".str-chat__icon--destructive" to be rendered
literally and styles to not apply; remove the leading '.' from each class token
in the className props for IconNoSign and IconUserRemove so they use plain
tokens (e.g., "str-chat__icon--destructive str-chat__channel-detail__action-icon
...") to restore styling.

Source: Learnings

src/plugins/ChannelDetail/ChannelDetailNavButton.tsx (1)

27-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set the nav button type explicitly.

Because this renders a real <button>, omitting type makes it submit any surrounding form by default. That creates accidental saves if ChannelDetailNavButton is embedded inside a form host.

Proposed fix
   const rootProps = useMemo(
     () => ({
       ...props,
       'aria-current': selected ? ('page' as const) : undefined,
       className: clsx('str-chat__channel-detail__nav-button', className),
       onClick: select,
+      type: 'button' as const,
     }),
     [className, props, select, selected],
   );
🤖 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/plugins/ChannelDetail/ChannelDetailNavButton.tsx` around lines 27 - 33,
The rootProps object in ChannelDetailNavButton (created via useMemo) is missing
an explicit button type, which causes the rendered <button> to default to
type="submit" when nested in a form; update the rootProps (the object returned
in the useMemo where 'aria-current', className, and onClick are set) to include
type: 'button' so the button will not submit surrounding forms (ensure you add
this alongside the existing className and onClick entries that reference
select).
src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx (1)

48-62: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the leading . from these icon class names.

Lines 49, 52, and 61 pass class names like .str-chat__icon--destructive. React will render that literal dot into the class attribute, so the destructive icon styles never match.

Suggested fix
 const BlockUserActionIcon = () => (
-  <IconNoSign className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
+  <IconNoSign className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user' />
 );
 const DeleteChatActionIcon = () => (
-  <IconDelete className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--delete-chat' />
+  <IconDelete className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--delete-chat' />
 );
 const LeaveChannelActionIcon = () => (
-  <IconLeave className='.str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--leave-channel' />
+  <IconLeave className='str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--leave-channel' />
 );
🤖 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/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx`
around lines 48 - 62, The icon components BlockUserActionIcon,
DeleteChatActionIcon, and LeaveChannelActionIcon include className strings that
start with a literal '.' which will render into the DOM and break CSS selectors;
edit these components to remove the leading '.' from their className values
(e.g., change '.str-chat__icon--destructive ...' to 'str-chat__icon--destructive
...') so the correct CSS classes are applied; leave MuteActionIcon and
MutedActionIcon as-is since they already use correct class names.
src/plugins/ChannelDetail/ChannelDetail.tsx (1)

49-87: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Route the new nav labels through t() instead of hard-coded English.

Channel info, Members, Pinned messages, Photos & videos, and Files are new visible SDK strings, but they bypass the translation pipeline here.

As per coding guidelines, use useTranslationContext() for translations and add all translatable strings to src/i18n/ files.

🤖 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/plugins/ChannelDetail/ChannelDetail.tsx` around lines 49 - 87, The nav
button labels are hard-coded English strings in ChannelManagementNavButton,
ChannelMembersNavButton, PinnedMessagesNavButton, ChannelMediaNavButton, and
ChannelFilesNavButton; replace those title props with translated strings by
calling useTranslationContext() (or the project's translation hook) to get t and
pass t('...') for each label, and add the new keys ("Channel info", "Members",
"Pinned messages", "Photos & videos", "Files") to the src/i18n/ resource files
so translations are available. Ensure you import/use useTranslationContext() in
ChannelDetail.tsx and use the same keys in the i18n JSON/TS files.

Source: Coding guidelines

src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.tsx (1)

93-110: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t render member rows as buttons when no selection handler is provided.

onMemberSelect is optional, but these rows always announce “View member details…” and stay keyboard-clickable even when the callback is missing. That leaves a no-op button in the accessibility tree.

🛠️ Suggested fix
+            const isInteractive = !!onMemberSelect;
+
             return (
               <ListItemLayout
                 key={memberUserId}
                 LeadingSlot={() => (
                   <Avatar
@@
                 )}
-                RootElement='button'
-                rootProps={{
-                  'aria-label': t('View member details for {{ member }}', {
-                    member: displayName,
-                  }),
-                  className: 'str-chat__channel-detail__channel-members-view__list-item',
-                  onClick: () => onMemberSelect?.(member),
-                }}
+                RootElement={isInteractive ? 'button' : 'div'}
+                rootProps={{
+                  ...(isInteractive
+                    ? {
+                        'aria-label': t('View member details for {{ member }}', {
+                          member: displayName,
+                        }),
+                        onClick: () => onMemberSelect(member),
+                      }
+                    : {}),
+                  className: 'str-chat__channel-detail__channel-members-view__list-item',
+                }}
                 subtitle={getPresenceStatusText(user, t)}
                 title={displayName}
🤖 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/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.tsx`
around lines 93 - 110, The member rows are rendered as interactive buttons even
when onMemberSelect is undefined; update the ListItemLayout usage so that when
onMemberSelect is falsy you do not render a button: choose a non-interactive
element (e.g., set RootElement='div' or omit RootElement) and remove
rootProps.onClick, aria-label, and any keyboard/tab props in that branch; keep
the existing button/aria-label/onClick only when onMemberSelect is provided
(references: ListItemLayout, RootElement, rootProps.onClick, aria-label,
onMemberSelect).
src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx (2)

68-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recompute the DM counterpart from current member state.

otherMemberUserId depends on channel.state.members, but this memo only keys on the stable channel object. If members hydrate after mount, DM mute state can stay stuck as undefined and the info view never reflects the other member correctly.

♻️ Suggested fix
-  const otherMemberUserId = useMemo(() => {
-    if (!resolvedIsDmChannel) return;
-
-    return Object.values(channel.state?.members ?? {}).find(
-      (member) => member.user?.id && member.user.id !== client.user?.id,
-    )?.user?.id;
-  }, [channel, client.user?.id, resolvedIsDmChannel]);
+  const otherMemberUserId = useMemo(() => {
+    if (!resolvedIsDmChannel) return;
+
+    return Object.values(channel.state?.members ?? {}).find(
+      (member) => member.user?.id && member.user.id !== client.user?.id,
+    )?.user?.id;
+  }, [channel.state?.members, client.user?.id, resolvedIsDmChannel]);
🤖 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/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx`
around lines 68 - 74, The memoized otherMemberUserId (useMemo in
ChannelManagementView) only depends on channel and thus won't recompute when
channel.state.members hydrates; update the dependency array to include the
members collection so the find runs when members change (e.g. add
channel.state?.members or Object.values(channel.state?.members) to the
dependency array alongside client.user?.id and resolvedIsDmChannel) so
otherMemberUserId updates when DM members hydrate.

200-204: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Allow avatar-only saves for unnamed channels.

canSubmit currently requires a non-empty trimmed name even when the only pending change is the avatar. That blocks image updates entirely for DMs or any channel whose channel.data?.name starts empty.

♻️ Suggested fix
   const trimmedName = name.trim();
   const nameChanged = trimmedName !== initialName.trim();
   const imageChanged = imageEdit !== null;
-  const hasChanges = (trimmedName.length > 0 && nameChanged) || imageChanged;
-  const canSubmit = trimmedName.length > 0 && !isSaving && hasChanges;
+  const hasChanges = nameChanged || imageChanged;
+  const hasValidName = !nameChanged || trimmedName.length > 0;
+  const canSubmit = hasValidName && !isSaving && hasChanges;
🤖 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/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx`
around lines 200 - 204, The submit gating currently requires a non-empty
trimmedName even when only the avatar changed, blocking avatar-only saves;
update the logic around
trimmedName/nameChanged/imageChanged/hasChanges/canSubmit so avatar edits are
allowed for unnamed channels: make hasChanges reflect any name change OR image
change (use nameChanged || imageChanged) and compute canSubmit as hasChanges &&
!isSaving && (!nameChanged || trimmedName.length > 0) so a name change still
requires a non-empty name but an image-only change can submit even if
trimmedName is empty (variables: trimmedName, nameChanged, imageChanged,
hasChanges, canSubmit, initialName, imageEdit, isSaving, channel.data?.name).
♻️ Duplicate comments (4)
src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx (1)

171-175: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t capability-gate personal block/unblock.

canBlockUser is still tied to 'ban-channel-members', which hides a personal block/unblock action whenever the viewer lacks channel-moderation rights. That breaks the user-level blocking contract in ordinary channels.

Proposed fix
   return {
-    canBlockUser:
-      !isCurrentUser &&
-      !!targetUserId &&
-      ownCapabilities?.includes('ban-channel-members'),
+    canBlockUser: !isCurrentUser && !!targetUserId,
     canMuteUser: !isCurrentUser && !!targetUserId,
     canRemoveUser:
       !isCurrentUser &&
Based on prior review context, blocking is a per-user action and should not depend on channel moderation capabilities.
🤖 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/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx`
around lines 171 - 175, The canBlockUser flag currently gates personal
block/unblock on the 'ban-channel-members' capability; remove that capability
check so blocking is available to non-current users regardless of channel
moderation rights. Update the expression that computes canBlockUser (referenced
as canBlockUser, isCurrentUser, targetUserId, ownCapabilities) to only require
!isCurrentUser and !!targetUserId (drop
ownCapabilities?.includes('ban-channel-members')), ensuring personal
block/unblock is not tied to channel moderation capabilities.
src/plugins/ChannelDetail/ChannelDetailSearchInput.tsx (1)

18-20: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset the upstream query when resetKey changes.

The effect only clears the local input state, so consumers can be left searching with the previous query while the field looks empty. Call onSearchChange('') here as well to keep the visible input and the active search source in sync.

🔄 Proposed fix
 useEffect(() => {
   setSearchInput('');
+  onSearchChange('');
 }, [resetKey]);
🤖 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/plugins/ChannelDetail/ChannelDetailSearchInput.tsx` around lines 18 - 20,
The effect watching resetKey currently only calls setSearchInput('') and leaves
the upstream query unchanged; update the useEffect in ChannelDetailSearchInput
(the effect that runs on resetKey) to also call onSearchChange('') so the
external search state is reset in sync with the input; ensure you reference the
existing setSearchInput and onSearchChange functions and keep resetKey as the
effect dependency.
src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx (1)

151-158: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an accessible name to the media viewer modal.

This dialog still opens without aria-label/aria-labelledby, so screen readers get an unnamed modal.

♿ Suggested fix
       <Modal
+        aria-label={t('Media viewer')}
         className={clsx(
           'str-chat__gallery-modal',
           'str-chat__channel-detail__media-view__viewer',
         )}
         onClose={closeViewer}
         open={viewerOpen}
       >
🤖 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/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx` around
lines 151 - 158, The Modal used in ChannelMediaView (the Modal element with
className using clsx and props onClose={closeViewer} open={viewerOpen}) lacks an
accessible name; add an accessible name by passing either an aria-label (e.g.
aria-label="Media viewer") or aria-labelledby that points to the modal title
element’s id (create a visible heading inside the modal and set its id, then set
aria-labelledby on the Modal) so screen readers announce the dialog.
src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.tsx (1)

112-137: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the saving state on the success path too.

isSaving is still only reset in catch. If this view stays mounted after addMembers() succeeds, the footer button remains disabled and the previous selection persists.

💡 Suggested fix
   const handleSave = async () => {
     if (!canManageChannelMembers || !selectedUserIds.length || isSaving) return;

+    const addedCount = selectedUserIds.length;
     setIsSaving(true);
     try {
       await channel.addMembers(selectedUserIds);
-      onMembersAdded(selectedUserIds.length);
+      setSelectedUserIds([]);
+      setIsSaving(false);
+      onMembersAdded(addedCount);
       addNotification({
         context: { channel },
         emitter: 'ChannelMembersView',
-        message: t('{{ count }} members added', { count: selectedUserIds.length }),
+        message: t('{{ count }} members added', { count: addedCount }),
         severity: 'success',
         type: 'api:channel:addMembers:success',
       });
     } catch (error) {
       setIsSaving(false);
🤖 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/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.tsx`
around lines 112 - 137, The success path in handleSave never clears the saving
flag, leaving isSaving true and the footer button disabled; after awaiting
channel.addMembers and before returning, call setIsSaving(false) (or move
setIsSaving(false) into a finally block) so the saving state is reset on
success; optionally also clear the selection (e.g., reset selectedUserIds via
its setter) after onMembersAdded if you want the previous selection to be
cleared.
🧹 Nitpick comments (2)
src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx (1)

62-63: ⚡ Quick win

Wrap this test helper with the repo’s standard Chat/Channel stack.

Lines 62-63 render through ChannelDetailProvider only, so the suite is not exercising the provider wiring the repo requires for component tests. As per coding guidelines, **/__tests__/**/*.{ts,tsx}: Use mock-builders pattern for tests: getTestClientWithUser(), useMockedApis(), getOrCreateChannelApi(), set connectionId and wsPromiseandWrap component tests with , , and relevant sub-components; use channel.watch() after setup`.

🤖 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/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx`
around lines 62 - 63, The test helper renderWithChannel should be updated to use
the repo's standard Chat/Channel stack and mock-builders: replace the direct
ChannelDetailProvider render with wiring that creates a mocked client via
getTestClientWithUser() and useMockedApis(), obtains/creates the channel via
getOrCreateChannelApi(), sets the client's connectionId and wsPromise, wraps the
UI in <Chat> and <Channel> providers (or the repo's ChannelDetail/Chat wrappers)
and ensures channel.watch() is called after setup; keep the helper named
renderWithChannel and reference ChannelDetailProvider only if still needed
inside the full Chat/Channel wrapper so tests exercise the required provider
wiring.

Source: Coding guidelines

src/plugins/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx (1)

53-145: 🏗️ Heavy lift

Use the repository’s standard Channel test harness in src/plugins/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx, src/plugins/ChannelDetail/Views/ChannelMediaView/__tests__/ChannelMediaView.test.tsx, and src/plugins/ChannelDetail/Views/ChannelFilesView/__tests__/ChannelFilesView.test.tsx.

These three new suites share the same root cause: they mock context/store hooks directly instead of using the repo’s required mock-builders setup and <Chat>/<Channel> wrappers, so they do not exercise the production provider wiring consistently.

As per coding guidelines, **/__tests__/**/*.{ts,tsx}: Use mock-builders pattern for tests: getTestClientWithUser(), useMockedApis(), getOrCreateChannelApi(), set connectionId and wsPromise and Wrap component tests with <Chat>, <Channel>, and relevant sub-components; use channel.watch() after setup.

🤖 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/plugins/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx`
around lines 53 - 145, Tests currently bypass the repo's standard test harness
by mocking context hooks and using ChannelDetailProvider directly
(renderWithChannel, createChannel, ChannelMemberDetail); replace that with the
repository mock-builders and wrappers: use getTestClientWithUser(),
useMockedApis(), getOrCreateChannelApi() and ensure connectionId and wsPromise
are set, create/watch the channel via channel.watch() after setup, then wrap the
rendered component with <Chat> and <Channel> (instead of ChannelDetailProvider)
so production provider wiring is exercised; remove the direct
vi.mocked(useChatContext|useModalContext|useComponentContext) overrides and
ensure tests still pass the same props (e.g., channelMemberActionSet, member) to
ChannelMemberDetail.

Source: Coding guidelines

🤖 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 `@examples/vite/src/index.scss`:
- Line 16: The import uses url(...) which violates the repo's quoted-import
notation; replace the `@import`
url('stream-chat-react/dist/css/channel-detail.css') layer(stream-new-plugins);
statement in examples/vite/src/index.scss with the quoted form (keep the same
module path and the layer(stream-new-plugins) suffix) so it reads as a quoted
import without the url(...) wrapper.

In `@package.json`:
- Line 194: The "build-styling" npm script currently uses a semicolon which lets
the cp step run even if the preceding sass compilation fails; update the script
(the "build-styling" entry in package.json) to chain commands so the copy only
runs on successful sass compile (e.g., replace the semicolon separator with a
conditional chain such as && or use a small shell conditional) to ensure a
failed Sass compile prevents the cp step and the script exits non‑zero.

---

Outside diff comments:
In `@src/plugins/ChannelDetail/ChannelDetail.tsx`:
- Around line 49-87: The nav button labels are hard-coded English strings in
ChannelManagementNavButton, ChannelMembersNavButton, PinnedMessagesNavButton,
ChannelMediaNavButton, and ChannelFilesNavButton; replace those title props with
translated strings by calling useTranslationContext() (or the project's
translation hook) to get t and pass t('...') for each label, and add the new
keys ("Channel info", "Members", "Pinned messages", "Photos & videos", "Files")
to the src/i18n/ resource files so translations are available. Ensure you
import/use useTranslationContext() in ChannelDetail.tsx and use the same keys in
the i18n JSON/TS files.

In `@src/plugins/ChannelDetail/ChannelDetailNavButton.tsx`:
- Around line 27-33: The rootProps object in ChannelDetailNavButton (created via
useMemo) is missing an explicit button type, which causes the rendered <button>
to default to type="submit" when nested in a form; update the rootProps (the
object returned in the useMemo where 'aria-current', className, and onClick are
set) to include type: 'button' so the button will not submit surrounding forms
(ensure you add this alongside the existing className and onClick entries that
reference select).

In
`@src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx`:
- Around line 48-62: The icon components BlockUserActionIcon,
DeleteChatActionIcon, and LeaveChannelActionIcon include className strings that
start with a literal '.' which will render into the DOM and break CSS selectors;
edit these components to remove the leading '.' from their className values
(e.g., change '.str-chat__icon--destructive ...' to 'str-chat__icon--destructive
...') so the correct CSS classes are applied; leave MuteActionIcon and
MutedActionIcon as-is since they already use correct class names.

In
`@src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx`:
- Around line 68-74: The memoized otherMemberUserId (useMemo in
ChannelManagementView) only depends on channel and thus won't recompute when
channel.state.members hydrates; update the dependency array to include the
members collection so the find runs when members change (e.g. add
channel.state?.members or Object.values(channel.state?.members) to the
dependency array alongside client.user?.id and resolvedIsDmChannel) so
otherMemberUserId updates when DM members hydrate.
- Around line 200-204: The submit gating currently requires a non-empty
trimmedName even when only the avatar changed, blocking avatar-only saves;
update the logic around
trimmedName/nameChanged/imageChanged/hasChanges/canSubmit so avatar edits are
allowed for unnamed channels: make hasChanges reflect any name change OR image
change (use nameChanged || imageChanged) and compute canSubmit as hasChanges &&
!isSaving && (!nameChanged || trimmedName.length > 0) so a name change still
requires a non-empty name but an image-only change can submit even if
trimmedName is empty (variables: trimmedName, nameChanged, imageChanged,
hasChanges, canSubmit, initialName, imageEdit, isSaving, channel.data?.name).

In
`@src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx`:
- Around line 95-100: The icon className strings in BlockUserActionIcon and
RemoveUserActionIcon incorrectly include leading dots (treating them like CSS
selectors), causing classes like ".str-chat__icon--destructive" to be rendered
literally and styles to not apply; remove the leading '.' from each class token
in the className props for IconNoSign and IconUserRemove so they use plain
tokens (e.g., "str-chat__icon--destructive str-chat__channel-detail__action-icon
...") to restore styling.

In
`@src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.tsx`:
- Around line 93-110: The member rows are rendered as interactive buttons even
when onMemberSelect is undefined; update the ListItemLayout usage so that when
onMemberSelect is falsy you do not render a button: choose a non-interactive
element (e.g., set RootElement='div' or omit RootElement) and remove
rootProps.onClick, aria-label, and any keyboard/tab props in that branch; keep
the existing button/aria-label/onClick only when onMemberSelect is provided
(references: ListItemLayout, RootElement, rootProps.onClick, aria-label,
onMemberSelect).

---

Duplicate comments:
In `@src/plugins/ChannelDetail/ChannelDetailSearchInput.tsx`:
- Around line 18-20: The effect watching resetKey currently only calls
setSearchInput('') and leaves the upstream query unchanged; update the useEffect
in ChannelDetailSearchInput (the effect that runs on resetKey) to also call
onSearchChange('') so the external search state is reset in sync with the input;
ensure you reference the existing setSearchInput and onSearchChange functions
and keep resetKey as the effect dependency.

In `@src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx`:
- Around line 151-158: The Modal used in ChannelMediaView (the Modal element
with className using clsx and props onClose={closeViewer} open={viewerOpen})
lacks an accessible name; add an accessible name by passing either an aria-label
(e.g. aria-label="Media viewer") or aria-labelledby that points to the modal
title element’s id (create a visible heading inside the modal and set its id,
then set aria-labelledby on the Modal) so screen readers announce the dialog.

In
`@src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx`:
- Around line 171-175: The canBlockUser flag currently gates personal
block/unblock on the 'ban-channel-members' capability; remove that capability
check so blocking is available to non-current users regardless of channel
moderation rights. Update the expression that computes canBlockUser (referenced
as canBlockUser, isCurrentUser, targetUserId, ownCapabilities) to only require
!isCurrentUser and !!targetUserId (drop
ownCapabilities?.includes('ban-channel-members')), ensuring personal
block/unblock is not tied to channel moderation capabilities.

In
`@src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.tsx`:
- Around line 112-137: The success path in handleSave never clears the saving
flag, leaving isSaving true and the footer button disabled; after awaiting
channel.addMembers and before returning, call setIsSaving(false) (or move
setIsSaving(false) into a finally block) so the saving state is reset on
success; optionally also clear the selection (e.g., reset selectedUserIds via
its setter) after onMembersAdded if you want the previous selection to be
cleared.

---

Nitpick comments:
In
`@src/plugins/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx`:
- Around line 53-145: Tests currently bypass the repo's standard test harness by
mocking context hooks and using ChannelDetailProvider directly
(renderWithChannel, createChannel, ChannelMemberDetail); replace that with the
repository mock-builders and wrappers: use getTestClientWithUser(),
useMockedApis(), getOrCreateChannelApi() and ensure connectionId and wsPromise
are set, create/watch the channel via channel.watch() after setup, then wrap the
rendered component with <Chat> and <Channel> (instead of ChannelDetailProvider)
so production provider wiring is exercised; remove the direct
vi.mocked(useChatContext|useModalContext|useComponentContext) overrides and
ensure tests still pass the same props (e.g., channelMemberActionSet, member) to
ChannelMemberDetail.

In
`@src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx`:
- Around line 62-63: The test helper renderWithChannel should be updated to use
the repo's standard Chat/Channel stack and mock-builders: replace the direct
ChannelDetailProvider render with wiring that creates a mocked client via
getTestClientWithUser() and useMockedApis(), obtains/creates the channel via
getOrCreateChannelApi(), sets the client's connectionId and wsPromise, wraps the
UI in <Chat> and <Channel> providers (or the repo's ChannelDetail/Chat wrappers)
and ensures channel.watch() is called after setup; keep the helper named
renderWithChannel and reference ChannelDetailProvider only if still needed
inside the full Chat/Channel wrapper so tests exercise the required provider
wiring.
🪄 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: 5d3179ed-4fc8-4e7d-a971-50ffb65595fb

📥 Commits

Reviewing files that changed from the base of the PR and between c7352f4 and 97a07bf.

📒 Files selected for processing (70)
  • examples/vite/package.json
  • examples/vite/src/AppSettings/tabs/ChannelDetail/channelDetailSettings.ts
  • examples/vite/src/ChatLayout/ConfiguredChannelDetail.tsx
  • examples/vite/src/index.scss
  • package.json
  • scripts/watch-styling.mjs
  • src/components/Avatar/styling/index.scss
  • src/components/index.ts
  • src/plugins/ChannelDetail/AvatarWithChannelDetail.tsx
  • src/plugins/ChannelDetail/ChannelDetail.tsx
  • src/plugins/ChannelDetail/ChannelDetailContext.tsx
  • src/plugins/ChannelDetail/ChannelDetailEmptyList.tsx
  • src/plugins/ChannelDetail/ChannelDetailListLoadingIndicator.tsx
  • src/plugins/ChannelDetail/ChannelDetailNavButton.tsx
  • src/plugins/ChannelDetail/ChannelDetailSearchInput.tsx
  • src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesEmptyList.tsx
  • src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.tsx
  • src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.ts
  • src/plugins/ChannelDetail/Views/ChannelFilesView/__tests__/ChannelFilesView.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelFilesView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelFilesView/useChannelFilesSearch.ts
  • src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx
  • src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx
  • src/plugins/ChannelDetail/Views/ChannelManagementView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaEmptyList.tsx
  • src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx
  • src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.utils.ts
  • src/plugins/ChannelDetail/Views/ChannelMediaView/__tests__/ChannelMediaView.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMediaView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelMediaView/useChannelMediaSearch.ts
  • src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx
  • src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberDetail.tsx
  • src/plugins/ChannelDetail/Views/ChannelMemberDetailView/__tests__/ChannelMemberDetail.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMemberDetailView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersRemoveView.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersAddView.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersBrowseView.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersHeaderActions.defaults.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersRemoveView.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersView.test.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/ChannelMembersView.utils.test.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/__tests__/testUtils.tsx
  • src/plugins/ChannelDetail/Views/ChannelMembersView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesEmptyList.tsx
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesView.tsx
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/__tests__/PinnedMessagesView.test.tsx
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/index.ts
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.ts
  • src/plugins/ChannelDetail/__tests__/ChannelDetail.test.tsx
  • src/plugins/ChannelDetail/__tests__/ChannelManagementActions.defaults.test.tsx
  • src/plugins/ChannelDetail/__tests__/ChannelManagementView.test.tsx
  • src/plugins/ChannelDetail/index.ts
  • src/plugins/ChannelDetail/styling/AvatarWithChannelDetail.scss
  • src/plugins/ChannelDetail/styling/ChannelDetail.scss
  • src/plugins/ChannelDetail/styling/ChannelFilesView.scss
  • src/plugins/ChannelDetail/styling/ChannelManagementView.scss
  • src/plugins/ChannelDetail/styling/ChannelMediaView.scss
  • src/plugins/ChannelDetail/styling/ChannelMemberDetailView.scss
  • src/plugins/ChannelDetail/styling/ChannelMembersView.scss
  • src/plugins/ChannelDetail/styling/ChannelMembersViewListFooter.scss
  • src/plugins/ChannelDetail/styling/PinnedMessagesView.scss
  • src/plugins/ChannelDetail/styling/index.scss
  • src/styling/index.scss
  • vite.config.ts
💤 Files with no reviewable changes (26)
  • src/plugins/ChannelDetail/styling/AvatarWithChannelDetail.scss
  • src/plugins/ChannelDetail/Views/ChannelManagementView/index.ts
  • src/plugins/ChannelDetail/styling/ChannelMembersViewListFooter.scss
  • src/components/Avatar/styling/index.scss
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/index.ts
  • src/components/index.ts
  • src/plugins/ChannelDetail/Views/ChannelFilesView/useChannelFilesSearch.ts
  • src/plugins/ChannelDetail/Views/ChannelMediaView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/index.ts
  • src/plugins/ChannelDetail/styling/ChannelDetail.scss
  • src/plugins/ChannelDetail/Views/ChannelMembersView/tests/ChannelMembersView.utils.test.ts
  • src/plugins/ChannelDetail/styling/ChannelFilesView.scss
  • src/plugins/ChannelDetail/ChannelDetailContext.tsx
  • src/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.ts
  • src/plugins/ChannelDetail/Views/ChannelMemberDetailView/index.ts
  • src/plugins/ChannelDetail/styling/ChannelMemberDetailView.scss
  • src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.ts
  • src/plugins/ChannelDetail/Views/ChannelFilesView/index.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts
  • src/plugins/ChannelDetail/styling/ChannelMembersView.scss
  • src/plugins/ChannelDetail/Views/ChannelMediaView/useChannelMediaSearch.ts
  • src/plugins/ChannelDetail/Views/ChannelMembersView/tests/testUtils.tsx
  • src/plugins/ChannelDetail/styling/ChannelMediaView.scss
  • src/plugins/ChannelDetail/styling/ChannelManagementView.scss
  • src/plugins/ChannelDetail/styling/PinnedMessagesView.scss
✅ Files skipped from review due to trivial changes (4)
  • examples/vite/package.json
  • src/plugins/ChannelDetail/index.ts
  • src/plugins/ChannelDetail/styling/index.scss
  • src/plugins/ChannelDetail/AvatarWithChannelDetail.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/vite/src/AppSettings/tabs/ChannelDetail/channelDetailSettings.ts
  • examples/vite/src/ChatLayout/ConfiguredChannelDetail.tsx

@import url('./AccessibilityNavigation/ReturnToSkipNavigation.scss')
layer(stream-app-overrides);
@import url('stream-chat-react/dist/css/emoji-picker.css') layer(stream-new-plugins);
@import url('stream-chat-react/dist/css/channel-detail.css') layer(stream-new-plugins);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rewrite this import to the repo’s expected notation.

Stylelint is already flagging this line: the new import uses url(...), but this file is configured to require quoted import notation. That will keep the examples stylesheet in a failing state.

Proposed fix
-@import url('stream-chat-react/dist/css/channel-detail.css') layer(stream-new-plugins);
+@import 'stream-chat-react/dist/css/channel-detail.css' layer(stream-new-plugins);
As per coding guidelines, this repo follows a zero-warnings policy, and static analysis is already reporting the `import-notation` violation on this line.
📝 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.

Suggested change
@import url('stream-chat-react/dist/css/channel-detail.css') layer(stream-new-plugins);
`@import` 'stream-chat-react/dist/css/channel-detail.css' layer(stream-new-plugins);
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 16-16: Expected "url('stream-chat-react/dist/css/channel-detail.css')" to be "'stream-chat-react/dist/css/channel-detail.css'" (import-notation)

(import-notation)

🤖 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 `@examples/vite/src/index.scss` at line 16, The import uses url(...) which
violates the repo's quoted-import notation; replace the `@import`
url('stream-chat-react/dist/css/channel-detail.css') layer(stream-new-plugins);
statement in examples/vite/src/index.scss with the quoted form (keep the same
module path and the layer(stream-new-plugins) suffix) so it reads as a quoted
import without the url(...) wrapper.

Sources: Coding guidelines, Linters/SAST tools

Comment thread package.json
"clean": "rm -rf dist",
"build": "yarn clean && concurrently 'yarn build-translations' 'vite build' 'tsc --project tsconfig.lib.json' 'yarn build-styling'",
"build-styling": "sass src/styling/index.scss:dist/css/index.css src/styling/_emoji-replacement.scss:dist/css/emoji-replacement.css src/plugins/Emojis/styling/index.scss:dist/css/emoji-picker.css; cp -r src/styling/assets dist/css/assets",
"build-styling": "sass src/styling/index.scss:dist/css/index.css src/styling/_emoji-replacement.scss:dist/css/emoji-replacement.css src/plugins/Emojis/styling/index.scss:dist/css/emoji-picker.css src/plugins/ChannelDetail/styling/index.scss:dist/css/channel-detail.css; cp -r src/styling/assets dist/css/assets",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t mask Sass failures in build-styling.

Using ; here means cp still runs after a Sass compile error, and the script can exit successfully with only the copy step’s status. That can publish a broken channel-detail.css while yarn build still passes.

Proposed fix
-    "build-styling": "sass src/styling/index.scss:dist/css/index.css src/styling/_emoji-replacement.scss:dist/css/emoji-replacement.css src/plugins/Emojis/styling/index.scss:dist/css/emoji-picker.css src/plugins/ChannelDetail/styling/index.scss:dist/css/channel-detail.css; cp -r src/styling/assets dist/css/assets",
+    "build-styling": "sass src/styling/index.scss:dist/css/index.css src/styling/_emoji-replacement.scss:dist/css/emoji-replacement.css src/plugins/Emojis/styling/index.scss:dist/css/emoji-picker.css src/plugins/ChannelDetail/styling/index.scss:dist/css/channel-detail.css && cp -r src/styling/assets dist/css/assets",
📝 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.

Suggested change
"build-styling": "sass src/styling/index.scss:dist/css/index.css src/styling/_emoji-replacement.scss:dist/css/emoji-replacement.css src/plugins/Emojis/styling/index.scss:dist/css/emoji-picker.css src/plugins/ChannelDetail/styling/index.scss:dist/css/channel-detail.css; cp -r src/styling/assets dist/css/assets",
"build-styling": "sass src/styling/index.scss:dist/css/index.css src/styling/_emoji-replacement.scss:dist/css/emoji-replacement.css src/plugins/Emojis/styling/index.scss:dist/css/emoji-picker.css src/plugins/ChannelDetail/styling/index.scss:dist/css/channel-detail.css && cp -r src/styling/assets dist/css/assets",
🤖 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 `@package.json` at line 194, The "build-styling" npm script currently uses a
semicolon which lets the cp step run even if the preceding sass compilation
fails; update the script (the "build-styling" entry in package.json) to chain
commands so the copy only runs on successful sass compile (e.g., replace the
semicolon separator with a conditional chain such as && or use a small shell
conditional) to ensure a failed Sass compile prevents the cp step and the script
exits non‑zero.

Comment on lines +5 to +12
import {
SECTION_NAVIGATOR_LAYOUT,
SectionNavigator,
type SectionNavigatorLayout,
type SectionNavigatorNavButtonProps,
type SectionNavigatorProps,
type SectionNavigatorSection,
} from '../../components/SectionNavigator';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these components be bundled together with the plugin? Do they have usage outside of ChannelDetails?

Comment thread src/components/index.ts
export * from './Poll';
export * from './Reactions';
export * from './SafeAnchor';
export * from './SectionNavigator';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one too

import React, { type ComponentType, useMemo } from 'react';

import type { SectionNavigatorNavButtonProps } from '../../components/SectionNavigator';
import { ListItemLayout } from '../../components/ListItemLayout';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe bundle this one together with the plugin too.

/>
);

export const defaultChannelDetailSections: SectionNavigatorSection[] = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By watching the demo video attached to this PR, I feel like we'll benefit from some additional "navigation header" in the mobile view. Maybe the Channel Header can sit above the navigation items?
What do you think @jurgenploeger?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants