Skip to content

chore: Merge 4.73.1 into single-server#7391

Merged
diegolmello merged 41 commits into
single-serverfrom
4.73.1-single-server
Jun 9, 2026
Merged

chore: Merge 4.73.1 into single-server#7391
diegolmello merged 41 commits into
single-serverfrom
4.73.1-single-server

Conversation

@diegolmello

@diegolmello diegolmello commented Jun 9, 2026

Copy link
Copy Markdown
Member

Proposed changes

Merge the 4.73.1 patch release into single-server. Companion to the master merge (#7370); this brings the same release to the single-server product line. As a patch, 4.73.1 does not flow through develop.

Single-server's product customizations are preserved: hardcoded server in app.json, NewServerView disabled (OutsideStack.tsx), the server switcher disabled (RoomsListView/components/Header.tsx), the forced single-server flows in app/sagas/init.js / app/sagas/login.js, single-server Android config in android/gradle.properties, and no Firebase configs shipped.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1216

How to test or reproduce

  • cat package.json | grep version -> 4.73.1
  • grep versionName android/app/build.gradle -> 4.73.1
  • grep CFBundleShortVersionString -A1 ios/RocketChatRN/Info.plist ios/ShareRocketChatRN/Info.plist -> 4.73.1
  • grep MARKETING_VERSION ios/RocketChatRN.xcodeproj/project.pbxproj -> two 4.73.1 entries (RocketChatRN, NotificationService)
  • grep APPLICATION_ID android/gradle.properties -> chat.rocket.reactnative
  • ls android/app/google-services.json ios/GoogleService-Info.plist -> both absent

Screenshots

N/A

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

N/A

diegolmello and others added 30 commits May 11, 2026 14:18
* fix: emoji picker scroll does not reach the end

* feat: add test to pick the last emoji in emoji picker

* fix: e2e test

* fix: tests

* tests

* remove unused pod

* rename emoji category

* fix: new test flow

* rollback space
* feat: alt text

* action: organized translations

* fix: move alt text accessibility to button level and fix gallery height

* chore: format code and fix lint issues

* ix: stable gallery keys and forward alt text accessibility to gallery items

* fix: i18n fallbacks, alt text a11y label, stale altText on remove, and 8.4.0 boundary test

* action: organized translations

* fix: i18n

* action: organized translations

* fix: preserve caption on send, correct version gate for share extension, and translated a11y labels in gallery

* fix: alt text label

* feat: queue composer attachments inline

* feat: open attachment alt text in action sheet

* fix: remove image gallery

* action: organized translations

* feat: edit message alt text

* chore: format code and fix lint issues

* feat: backward compatibilities

* fix: edit

* remove image gallery

* fix: conflicts

* code improvements

* fix: composer

* code improvements

* fix: tests

* fix(a11y): improve image attachment accessibility labels and order

* action: organized translations

* fix: unit tests

* fix: focus

* fix: a11y labels

* chore: format code and fix lint issues

* test

* fix: merge conflicts

* feat: improve a11y experience

* fix: unlabelled on android

* fix: action sheet input keyboard

* fix: announce GIFs as interactive in ImageViewer for screen readers

* fix: ignore whitespace-only alt text in message a11y label

* refactor: extract sendAttachments helper from composer and share view

* action: organized translations

* feat: code improvements

* code improvements

* refactor: extract useImageDescriptionLabel hook and inline useAltTextSupported in Image

* fix: i18n

* action: organized translations

* refactor: stabilize FlatList renderers and harden altText checks

* fix(a11y): omit empty segments in message accessibilityLabel

* refactor: extract normalizeAttachment and preserve handler order
  in useChooseMedia

* refactor: extract normalizeAttachment and preserve handler order
  in useChooseMedia

* rollback prettier changes

* feat: attachment action sheet stories and test

* feat: attachment actionsheet stories and tests

* feat: announce images without description to screen readers

* action: organized translations

* fix: render Attachment file name in Thread Message preview when body is empty (#7323)

* fix: preference value changes causing reset to other option (#7313)

* fix: do not encrypt messages when workspace E2E is disabled (#7324)

* fix: snapshot test

* fix: snapshot test

* feat: unify thumbs

* efactor: rename handlePickedAttachments to handleSelectedAttachments

* fix: pass altText and isAnimated to ImageViewer in ShareView Preview

* refactor: make AltTextLabel altText optional with early return

* fix: stop leaking attachment.altText into caption rendering

* fix: if no alt text its rendering an empty absolute view

* test: cover useMessageAccessibilityLabel and keep suffix on
  translated

* refactor: prefer title_link/message_link over index for Reply attachment key

* refactor: unify ShareView Thumbs and ComposerAttachments under
  shared AttachmentThumbs

* feat: unify Thumbs

* remove memo of useImageDescriptionLabel

* fix: test and lint

* fix: test

* fix: i18n missing translation

* fix: fallback accessibility label when alt text is empty

* fix: remove unused code

* rollback prettier changes

* refactor: colocate useImageDescriptionLabel with message hooks

* refactor: simplify message accessibility label composition

* feat: add missing keys

* feat: use thumb as children instead of add it again

* refactor: tighten types and stable keys in message/share views

* fix: useTheme on AltTextInput

* action: organized translations

* chore: code organization

* chore: type improvement

* chore: code organization

* fix: image improvement

* code improvements

* fix: avoid undefined a11y label

* fix: altText trim and improvements

* fix: tests

* fix: use memo composer attachments

* chore: remove editAltText (not available)

* feat: standardize shareView composer

---------

Co-authored-by: OtavioStasiak <OtavioStasiak@users.noreply.github.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
Co-authored-by: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com>
* feat: announcement and focus

* action: organized translations

* fix: lint

* fix: iOS announcement

* fix: focus switch

* fix: unit tests

* fix: unit tests

* chore: migrate last focus to hooks

* fix: en translation

* fix: back focus to message

* code improvements

* fix: message actions announcement

* fix: tests

* fix: touch

* feat: a11y improvements

* action: organized translations

* fix: Touch

* i18n: translations

* useMessageAccessibilityActions and Label as hooks

* action: organized translations

* feat: control back focus on hook

* feat(a11y): message a11y label just on Touchable and add "press to view thread" hint to message accessibility label

* fix: snapshot

* fix: test snapshot

* test: fix iOS Maestro thread test by tapping message content instead of replied-on-thread indicator

* fix: encryption test ios

* code improvements

* code improvements

* fix: snapshot test

* fix: i18n

* action: organized translations

* remove duplicated label

* remove duplicated props

---------

Co-authored-by: OtavioStasiak <OtavioStasiak@users.noreply.github.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
* fix: acrionSheet height considering handle

* feat: e2e tests

* fix: e2e tests
…7336)

* chore:keyboard navigation focus on header instead of composer

* fix: focus change

* chore: code improvements

* fix: room test flow

* fix: test

* fix: e2e tests

* fix: test

* fix: tests

* fix: e2e test

* fix: e2e test

* fix: e2e test

* rollback

* improve autoFocus condition

* rollback unused change

* rollback unused change
…es (#7305)

* fix: fetch enough history when system messages are hidden

* trying to load messages

* load code improvements

* feat: improve test useMessages

* fix: dedupe concurrent room history fetches on cold open

* refactor: simplify loadMessagesForRoom and document history loader terms

* fix: tests
diegolmello and others added 10 commits June 1, 2026 18:04
# Conflicts:
#	android/app/build.gradle
#	ios/RocketChatRN.xcodeproj/project.pbxproj
#	ios/RocketChatRN/Info.plist
#	ios/ShareRocketChatRN/Info.plist
#	package.json
* chore: unify search announcement results for a11y

* chore: create useDirectorySearch hook

* feat: migrate directoryView to hooks

* code improvements

* fix: tests
Brings the 4.73.1 release into the single-server line.

Conflict resolution (mirrors the 4.73.0 single-server merge, NATIVE-1205):
- Version files (package.json, build.gradle, Info.plist x2, pbxproj) -> 4.73.1
- android/gradle.properties -> single-server values (APPLICATION_ID=chat.rocket.reactnative,
  VERSIONCODE=1, keystore block)
- Firebase configs kept deleted (google-services.json, GoogleService-Info.plist,
  debug strings.xml)
- connect.ts / connect.test.ts -> 4.73.1 (single-server merely lacked the
  unsubscribeRooms() close-listener fix #7362)
- pnpm-lock.yaml / Podfile.lock -> 4.73.1 (only delta was picker 2.11.1 -> 2.11.4)
- Single-server product customizations intact (app.json hardcoded server,
  disabled NewServerView, disabled ServersList switcher)
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Release 4.73.1 updates app/native versions, adds history UI loader lifecycle and batched message loading, introduces markdown parse caching and block rendering, refactors DirectoryView/search to hooks with accessibility announcements, optimizes deep-link connection gating, hardens SDK socket liveness/reopen, and adjusts video-call permission and settings handling.

Changes

Release 4.73.1: Multi-Feature Bundle

Layer / File(s) Summary
Version & Build Configuration
package.json, android/app/build.gradle, ios/RocketChatRN/Info.plist, ios/ShareRocketChatRN/Info.plist, ios/RocketChatRN.xcodeproj/project.pbxproj, README.md
Version updated from 4.73.04.73.1 across package and native files; @react-native-picker/picker bumped; README whitelabel link updated.
Room History UI Loader Types & Actions
app/actions/actionsTypes.ts, app/actions/room.ts
Adds ROOM.HISTORY_UI_LOADER_PUSH / ROOM.HISTORY_UI_LOADER_POP, interfaces, and action creators to mark UI loader lifecycle.
Room History UI Loader Reducer & Tests
app/reducers/room.ts, app/reducers/room.test.ts
Reducer handles push/pop of historyLoaders (de-duplicated push, remove on pop); tests verify both behaviors.
Message Loading Refactor with Batching & UI Loader
app/lib/methods/loadMessagesForRoom.ts, app/lib/methods/loadMessagesForRoom.test.ts
Async refactor with MAX_BATCHES, visibility predicate for hidden system messages, synthetic MORE markers, UI loader push/pop lifecycle; tests cover recursion, limits, partial batches, errors, settings fallback, and loaderItem suppression.
Markdown Rendering: Parse Caching & Block View
app/containers/markdown/index.tsx, app/containers/markdown/styles.ts
Bounded parse cache, token resolution helpers, memoized MarkdownBlockView, and styles.blocks (gap:2); returns null on empty tokens and logs parse errors.
Directory View: Function Component & useDirectorySearch Hook
app/views/DirectoryView/hooks/useDirectorySearch.ts, app/views/DirectoryView/index.tsx
New useDirectorySearch hook (debounced queries, pagination, workspace toggle, accessibility announcements) and DirectoryView refactored from class/Redux to hooks-based function component with header wiring.
Search Accessibility Helper & Consolidation
app/lib/methods/helpers/announceSearchResultsForAccessibility.ts, app/lib/methods/helpers/index.ts, app/views/RoomsListView/hooks/useSearch.ts
New helper announces localized "no results" or singular/plural result counts; useSearch imports and uses this helper.
Deep Linking: Connection State Optimization
app/sagas/deepLinking.js, app/sagas/__tests__/deepLinking.test.ts
Sagas detect already-connected host and conditionally wait for METEOR.SUCCESS; tests updated with setupRecordingStore, injected connectSuccess() gates, and scenarios for already-connected host and call push race conditions.
Socket Close: Room Unsubscription & Reconnect Guard
app/lib/services/connect.ts, app/lib/services/connect.test.ts
Socket 'close' handler now calls unsubscribeRooms(); regression test ensures invocation on socket close.
SDK Socket: Aggressive Reconnection & Liveness
patches/@rocket.chat+sdk+1.3.3-mobile.patch
SDK patch detaches stale handlers before swapping sockets, adds probe() (ping/pong), forceReopen() with _reopenInFlight dedup, time-bucketed checkAndReopen, and subscribes to media-signal/media-calls.
Video Conf & Camera Permission Updates
app/lib/hooks/useVideoConf/index.tsx, app/lib/hooks/useVideoConf/useVideoConfCall.ts, app/views/RoomView/components/HeaderCallButton.tsx
Switch to imperative Camera.get/requestCameraPermissionsAsync, consolidate call settings via single useAppSelector with shallowEqual, and use isInActiveCall for HeaderCallButton disable logic.
Deep Link: Localhost URL Normalization Exception
app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
Preserve http://localhost (with optional port) instead of rewriting to https://; other hosts still normalized to HTTPS.
useMessages: In-Flight History Guard & Tests
app/views/RoomView/List/hooks/useMessages.ts, app/views/RoomView/List/hooks/useMessages.test.tsx
useMessages reads store.getState().room.historyLoaders to avoid duplicate fetch dispatches for in-flight loader ids; tests extended for loader types, hideSystemMessages behaviors, unsubscription and thread reads.
Helpers & Misc
patches/expo-file-system+19.0.21.patch, CLAUDE.md, android/.../NotificationIntentHandler.kt
Add OkHttp client setter in expo-file-system patch, remove CLAUDE.md worktree section, and trivial file trailing-line change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak
  • Rohit3523
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: Merge 4.73.1 into single-server' clearly describes the main change: merging a patch release into a product branch.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • NATIVE-1216: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts (1)

10-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Anchor the localhost exception to the full host token.

These regexes match any host that merely starts with localhost, so values like localhost.example.com and http://localhost.evil.test are treated as localhost and skip the HTTPS upgrade. In a deep-link flow that can preserve cleartext transport for a remote host.

Suggested fix
-	if (!/^(http|https)/.test(host)) {
-		if (/^localhost(:\d+)?/.test(host)) {
+	if (!/^(http|https):\/\//.test(host)) {
+		if (/^localhost(?::\d+)?(?:\/|$)/.test(host)) {
 			host = `http://${host}`;
 		} else {
 			host = `https://${host}`;
 		}
-	} else if (!/^http:\/\/localhost(:\d+)?/.test(host)) {
+	} else if (!/^http:\/\/localhost(?::\d+)?(?:\/|$)/.test(host)) {
 		host = host.replace('http://', 'https://');
 	}
🤖 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 `@app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts` around lines 10 -
17, The localhost checks in normalizeDeepLinkingServerHost incorrectly match
hosts that merely start with "localhost" (e.g., "localhost.example.com"); update
the regexes used in the function so they anchor the full host token—replace
/^localhost(:\d+)?/ with /^localhost(:\d+)?$/ and /^http:\/\/localhost(:\d+)?/
with /^http:\/\/localhost(:\d+)?$/ (so only exact "localhost" with optional port
is treated as localhost) and keep the rest of the logic unchanged.
app/sagas/deepLinking.js (1)

334-344: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mirror the hostAlreadyConnected short-circuit in handleClickCallPush.

This branch still emits NewServer and waits for SERVER.SELECT_SUCCESS even when sdk.current?.client?.host === host. In that case the select-server flow short-circuits instead of dispatching SERVER.SELECT_SUCCESS, so the video-call deep-link path can hang before loginRequest and handleNavigateCallRoom.

Suggested fix
-		yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
-		yield put(serverInitAdd(server));
-		yield delay(1000);
-		EventEmitter.emit('NewServer', { server: host });
+		const hostAlreadyConnected = sdk.current?.client?.host === host;
+		if (!hostAlreadyConnected) {
+			yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
+			yield put(serverInitAdd(server));
+			yield delay(1000);
+			EventEmitter.emit('NewServer', { server: host });
+		}
 		if (params.token) {
-			yield take(types.SERVER.SELECT_SUCCESS);
-			// SERVER.SELECT_SUCCESS doesn't mean 'connected'; skip the take if it already is.
-			const connected = yield select(state => state.meteor.connected);
-			if (!connected) {
-				yield take(types.METEOR.SUCCESS);
+			if (!hostAlreadyConnected) {
+				yield take(types.SERVER.SELECT_SUCCESS);
+				// SERVER.SELECT_SUCCESS doesn't mean 'connected'; skip the take if it already is.
+				const connected = yield select(state => state.meteor.connected);
+				if (!connected) {
+					yield take(types.METEOR.SUCCESS);
+				}
 			}
 			yield put(loginRequest({ resume: params.token }, true));
🤖 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 `@app/sagas/deepLinking.js` around lines 334 - 344, The branch emitting
EventEmitter.emit('NewServer', { server: host }) and yielding
take(types.SERVER.SELECT_SUCCESS) should short-circuit exactly like
handleClickCallPush when the SDK is already pointed at the same host; detect the
same condition (e.g. sdk.current?.client?.host === host or the existing
hostAlreadyConnected check) before emitting NewServer and before yielding
take(types.SERVER.SELECT_SUCCESS), and if true skip those steps so the saga
proceeds directly to the token-handling / loginRequest and
handleNavigateCallRoom flow (still keep the connected check and possible
take(types.METEOR.SUCCESS)). Use the same unique symbols to locate code:
EventEmitter.emit('NewServer', ...), types.SERVER.SELECT_SUCCESS,
sdk.current?.client?.host (or hostAlreadyConnected), loginRequest, and
handleNavigateCallRoom.
🧹 Nitpick comments (2)
app/containers/markdown/index.tsx (1)

44-61: ⚡ Quick win

Consider upgrading cache to LRU eviction.

The current implementation uses FIFO (First-In-First-Out) eviction—when the cache reaches 200 entries, it removes the oldest inserted key. This means frequently accessed messages will still be evicted after 200 newer messages are parsed, even if they're re-rendered often (e.g., when users scroll up and down).

Upgrading to LRU (Least Recently Used) would improve cache hit rates by keeping frequently accessed messages longer.

♻️ Suggested LRU implementation
 const parseMessage = (msg: string): Root => {
 	const cached = parseCache.get(msg);
 	if (cached) {
+		// Move to end for LRU behavior
+		parseCache.delete(msg);
+		parseCache.set(msg, cached);
 		return cached;
 	}

 	const result = parse(msg);

 	if (parseCache.size >= PARSE_CACHE_MAX) {
 		const oldestKey = parseCache.keys().next().value;
 		if (oldestKey !== undefined) {
 			parseCache.delete(oldestKey);
 		}
 	}

 	parseCache.set(msg, result);
 	return result;
 };
🤖 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 `@app/containers/markdown/index.tsx` around lines 44 - 61, The cache currently
evicts in FIFO order; update parseMessage and the parseCache usage to implement
an LRU policy so frequently accessed messages stay cached: on a cache hit in
parseMessage (parseCache.get(msg)), move that entry to be most-recently-used
(delete then set the key) before returning; on cache insertion (after
parse(msg)), insert normally and, if parseCache.size > PARSE_CACHE_MAX, remove
the least-recently-used entry by deleting parseCache.keys().next().value; ensure
all mentions of parseCache, parseMessage, PARSE_CACHE_MAX and parse() are
updated accordingly so the Map behaves as an LRU cache.
app/views/DirectoryView/hooks/useDirectorySearch.ts (1)

9-9: ⚡ Quick win

Add an explicit return type for the exported hook.

This hook is exported and currently relies on inferred return shape, which weakens API contract clarity for consumers.

💡 Suggested refactor
-export const useDirectorySearch = (directoryDefaultView: string) => {
+interface IUseDirectorySearchResult {
+	data: IServerRoom[];
+	loading: boolean;
+	type: string;
+	globalUsers: boolean;
+	search: () => void;
+	loadMore: () => void;
+	onSearchChangeText: (newText: string) => void;
+	changeType: (newType: string) => void;
+	toggleWorkspace: () => void;
+}
+
+export const useDirectorySearch = (directoryDefaultView: string): IUseDirectorySearchResult => {

As per coding guidelines: Use TypeScript for type safety; add explicit type annotations to function parameters and return types.

🤖 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 `@app/views/DirectoryView/hooks/useDirectorySearch.ts` at line 9, The exported
hook useDirectorySearch currently relies on an inferred return type; add an
explicit TypeScript return annotation to its signature (e.g., export const
useDirectorySearch = (directoryDefaultView: string): UseDirectorySearchReturn =>
...) by defining and exporting a corresponding return type alias or interface
(UseDirectorySearchReturn) that declares the exact shape (states, setters,
handlers, and any derived values) the hook returns; update any callers if
necessary to import the new type.

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 `@app/lib/hooks/useVideoConf/useVideoConfCall.ts`:
- Around line 20-33: The effect that calls init() should depend on all reactive
inputs so callEnabled/disabledTooltip are recomputed when inputs change: update
the useEffect in useVideoConfCall.ts to include rid, server version check
output, the permissions result from usePermissions, user?.username, and all
settings fields (jitsiEnabled, jitsiEnableTeams, jitsiEnableChannels,
videoConfEnableDMs, videoConfEnableChannels, videoConfEnableTeams,
videoConfEnableGroups, omnichannelCallProvider) in its dependency array and call
init whenever any change; inside init (or immediately before calling it) reset
disabledTooltip back to false so it can be recalculated (disabledTooltip
currently only set true and never cleared), ensuring the effect references the
init function defined in this hook and updates callEnabled/disabledTooltip
derived state accordingly.

In `@app/views/DirectoryView/hooks/useDirectorySearch.ts`:
- Around line 20-54: The load handler can have older async responses overwrite
newer results; fix by adding a request sequencing guard: create a stable
ref/counter (e.g., latestRequestIdRef) and increment it each time load runs,
capture the current id in the async closure, then after the await from
getDirectory(...) verify the captured id === latestRequestIdRef.current before
calling setData, setTotal, announceSearchResultsForAccessibility or
setLoading(false) for successful branch; also check the id in the error/catch
branch so stale failures don't clear loading or log for newer requests. Ensure
the guard is applied around all state updates in load (including the newSearch
branch logic) so only the most recent getDirectory() result mutates state.

---

Outside diff comments:
In `@app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts`:
- Around line 10-17: The localhost checks in normalizeDeepLinkingServerHost
incorrectly match hosts that merely start with "localhost" (e.g.,
"localhost.example.com"); update the regexes used in the function so they anchor
the full host token—replace /^localhost(:\d+)?/ with /^localhost(:\d+)?$/ and
/^http:\/\/localhost(:\d+)?/ with /^http:\/\/localhost(:\d+)?$/ (so only exact
"localhost" with optional port is treated as localhost) and keep the rest of the
logic unchanged.

In `@app/sagas/deepLinking.js`:
- Around line 334-344: The branch emitting EventEmitter.emit('NewServer', {
server: host }) and yielding take(types.SERVER.SELECT_SUCCESS) should
short-circuit exactly like handleClickCallPush when the SDK is already pointed
at the same host; detect the same condition (e.g. sdk.current?.client?.host ===
host or the existing hostAlreadyConnected check) before emitting NewServer and
before yielding take(types.SERVER.SELECT_SUCCESS), and if true skip those steps
so the saga proceeds directly to the token-handling / loginRequest and
handleNavigateCallRoom flow (still keep the connected check and possible
take(types.METEOR.SUCCESS)). Use the same unique symbols to locate code:
EventEmitter.emit('NewServer', ...), types.SERVER.SELECT_SUCCESS,
sdk.current?.client?.host (or hostAlreadyConnected), loginRequest, and
handleNavigateCallRoom.

---

Nitpick comments:
In `@app/containers/markdown/index.tsx`:
- Around line 44-61: The cache currently evicts in FIFO order; update
parseMessage and the parseCache usage to implement an LRU policy so frequently
accessed messages stay cached: on a cache hit in parseMessage
(parseCache.get(msg)), move that entry to be most-recently-used (delete then set
the key) before returning; on cache insertion (after parse(msg)), insert
normally and, if parseCache.size > PARSE_CACHE_MAX, remove the
least-recently-used entry by deleting parseCache.keys().next().value; ensure all
mentions of parseCache, parseMessage, PARSE_CACHE_MAX and parse() are updated
accordingly so the Map behaves as an LRU cache.

In `@app/views/DirectoryView/hooks/useDirectorySearch.ts`:
- Line 9: The exported hook useDirectorySearch currently relies on an inferred
return type; add an explicit TypeScript return annotation to its signature
(e.g., export const useDirectorySearch = (directoryDefaultView: string):
UseDirectorySearchReturn => ...) by defining and exporting a corresponding
return type alias or interface (UseDirectorySearchReturn) that declares the
exact shape (states, setters, handlers, and any derived values) the hook
returns; update any callers if necessary to import the new type.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c74827f8-92e1-46c2-aca3-741809543fc3

📥 Commits

Reviewing files that changed from the base of the PR and between 87a0070 and 0140ead.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • README.md
  • android/app/build.gradle
  • app/actions/actionsTypes.ts
  • app/actions/room.ts
  • app/containers/markdown/index.tsx
  • app/containers/markdown/styles.ts
  • app/lib/hooks/useVideoConf/index.tsx
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
  • app/lib/methods/helpers/index.ts
  • app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/services/connect.test.ts
  • app/lib/services/connect.ts
  • app/reducers/room.test.ts
  • app/reducers/room.ts
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/deepLinking.js
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/views/DirectoryView/index.tsx
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/views/RoomsListView/hooks/useSearch.ts
  • ios/RocketChatRN.xcodeproj/project.pbxproj
  • ios/RocketChatRN/Info.plist
  • ios/ShareRocketChatRN/Info.plist
  • package.json
  • patches/@rocket.chat+sdk+1.3.3-mobile.patch
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/methods/helpers/index.ts
  • app/actions/actionsTypes.ts
  • app/containers/markdown/styles.ts
  • app/reducers/room.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
  • app/lib/services/connect.ts
  • app/reducers/room.ts
  • app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
  • app/actions/room.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/containers/markdown/index.tsx
  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/sagas/deepLinking.js
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/lib/hooks/useVideoConf/index.tsx
  • app/views/DirectoryView/index.tsx
  • app/lib/methods/loadMessagesForRoom.ts
  • app/views/RoomsListView/hooks/useSearch.ts
  • app/sagas/__tests__/deepLinking.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/lib/methods/helpers/index.ts
  • app/actions/actionsTypes.ts
  • app/containers/markdown/styles.ts
  • app/reducers/room.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
  • app/lib/services/connect.ts
  • app/reducers/room.ts
  • app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
  • app/actions/room.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/containers/markdown/index.tsx
  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/lib/hooks/useVideoConf/index.tsx
  • app/views/DirectoryView/index.tsx
  • app/lib/methods/loadMessagesForRoom.ts
  • app/views/RoomsListView/hooks/useSearch.ts
  • app/sagas/__tests__/deepLinking.test.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/lib/methods/helpers/index.ts
  • app/actions/actionsTypes.ts
  • app/containers/markdown/styles.ts
  • app/reducers/room.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
  • app/lib/services/connect.ts
  • app/reducers/room.ts
  • app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
  • app/actions/room.ts
  • package.json
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/containers/markdown/index.tsx
  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/sagas/deepLinking.js
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/lib/hooks/useVideoConf/index.tsx
  • app/views/DirectoryView/index.tsx
  • app/lib/methods/loadMessagesForRoom.ts
  • app/views/RoomsListView/hooks/useSearch.ts
  • app/sagas/__tests__/deepLinking.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/methods/helpers/index.ts
  • app/actions/actionsTypes.ts
  • app/containers/markdown/styles.ts
  • app/reducers/room.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
  • app/lib/services/connect.ts
  • app/reducers/room.ts
  • app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
  • app/actions/room.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/containers/markdown/index.tsx
  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/sagas/deepLinking.js
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/lib/hooks/useVideoConf/index.tsx
  • app/views/DirectoryView/index.tsx
  • app/lib/methods/loadMessagesForRoom.ts
  • app/views/RoomsListView/hooks/useSearch.ts
  • app/sagas/__tests__/deepLinking.test.ts
app/actions/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place action creators in 'app/actions/' directory

Files:

  • app/actions/actionsTypes.ts
  • app/actions/room.ts
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components in 'app/containers/' directory

Files:

  • app/containers/markdown/styles.ts
  • app/containers/markdown/index.tsx
app/reducers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reducers in 'app/reducers/' directory

Files:

  • app/reducers/room.test.ts
  • app/reducers/room.ts
app/lib/services/connect.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use 'app/lib/services/connect.ts' for server connection management

Files:

  • app/lib/services/connect.ts
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place screen components in 'app/views/' directory

Files:

  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/views/DirectoryView/index.tsx
  • app/views/RoomsListView/hooks/useSearch.ts
app/sagas/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place sagas in 'app/sagas/' directory for handling side effects

Files:

  • app/sagas/__tests__/deepLinking.test.ts
🧠 Learnings (5)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/methods/helpers/index.ts
  • app/actions/actionsTypes.ts
  • app/containers/markdown/styles.ts
  • app/reducers/room.test.ts
  • app/lib/services/connect.test.ts
  • app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
  • app/lib/services/connect.ts
  • app/reducers/room.ts
  • app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
  • app/actions/room.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/containers/markdown/index.tsx
  • app/views/RoomView/components/HeaderCallButton.tsx
  • app/views/DirectoryView/hooks/useDirectorySearch.ts
  • app/lib/hooks/useVideoConf/useVideoConfCall.ts
  • app/views/RoomView/List/hooks/useMessages.ts
  • app/views/RoomView/List/hooks/useMessages.test.tsx
  • app/lib/hooks/useVideoConf/index.tsx
  • app/views/DirectoryView/index.tsx
  • app/lib/methods/loadMessagesForRoom.ts
  • app/views/RoomsListView/hooks/useSearch.ts
  • app/sagas/__tests__/deepLinking.test.ts
📚 Learning: 2026-02-05T13:55:00.974Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6930
File: package.json:101-101
Timestamp: 2026-02-05T13:55:00.974Z
Learning: In this repository, the dependency on react-native-image-crop-picker should reference the RocketChat fork (RocketChat/react-native-image-crop-picker) with explicit commit pins, not the upstream ivpusic/react-native-image-crop-picker. Update package.json dependencies (and any lockfile) to point to the fork URL and a specific commit, ensuring edge-to-edge Android fixes are included. This pattern should apply to all package.json files in the repo that declare this dependency.

Applied to files:

  • package.json
📚 Learning: 2026-05-07T17:47:14.516Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7303
File: package.json:5-5
Timestamp: 2026-05-07T17:47:14.516Z
Learning: When reviewing pnpm `packageManager` version pins in any `package.json` (e.g., `"packageManager": "pnpm@<version>"`), don’t rely solely on web-search results to determine whether a version exists. For very recently published versions, cross-check the target version against the official pnpm release page (https://github.com/pnpm/pnpm/releases) and the npm registry page for pnpm (https://www.npmjs.com/package/pnpm) before flagging the pinned version as non-existent.

Applied to files:

  • package.json
📚 Learning: 2026-05-07T13:19:52.152Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7304
File: app/sagas/deepLinking.js:237-243
Timestamp: 2026-05-07T13:19:52.152Z
Learning: In this codebase’s Redux-Saga usage, remember that `yield put(action)` dispatches through the Redux store synchronously, and any saga(s) that synchronously react via action listeners (and synchronous `put` chains) will run to completion before the calling saga resumes at its next `yield`. As a result, within a single saga there is no scheduler interleaving between a `yield select(...)` and a subsequent `yield take(...)` at the next `yield` point, so a check-then-take pattern like `const state = yield select(...); if (state !== TARGET) { yield take(a => a.type === TARGET); }` is safe from TOCTOU races under the synchronous `put`/take model described above.

Applied to files:

  • app/sagas/deepLinking.js
📚 Learning: 2026-05-05T21:08:33.177Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7298
File: patches/@rocket.chat+sdk+1.3.3-mobile.patch:79-79
Timestamp: 2026-05-05T21:08:33.177Z
Learning: In the RocketChat/Rocket.Chat.ReactNative repo, for patches under patches/*.patch (especially those touching rocket.chat/sdk), remember that the patching sandbox's node_modules reflects the pre-patch state. Grepping node_modules for symbols (e.g., userDisconnectCloseCode = 4000 in node_modules/rocket.chat/sdk/lib/drivers/ddp.ts) can yield false positives. Review patches by inspecting the diff and applying it to a fresh copy of the SDK or diffing against the SDK source, rather than relying on node_modules. Ensure the patch actually introduces/updates symbols in the SDK source and run the test suite to validate behavior after applying the patch.

Applied to files:

  • patches/@rocket.chat+sdk+1.3.3-mobile.patch
🔇 Additional comments (31)
README.md (1)

29-29: LGTM!

android/app/build.gradle (1)

93-93: LGTM!

package.json (2)

3-3: LGTM!


44-44: LGTM!

ios/RocketChatRN/Info.plist (1)

29-29: LGTM!

ios/ShareRocketChatRN/Info.plist (1)

22-22: LGTM!

ios/RocketChatRN.xcodeproj/project.pbxproj (1)

2092-2092: LGTM!

Also applies to: 2145-2145

app/containers/markdown/index.tsx (5)

1-22: LGTM!


41-41: AI summary states incorrect cache size.

The code defines PARSE_CACHE_MAX = 200, but the AI-generated summary claims "max 10 entries". The implemented value of 200 is correct.


63-69: LGTM!


104-154: LGTM!


71-102: React Compiler is configured for 'use memo' directives
babel-plugin-react-compiler is enabled in babel.config.js (compilationMode: 'annotation') and present in package.json, so the 'use memo' directive should be processed rather than silently ignored.

app/containers/markdown/styles.ts (1)

11-13: LGTM!

app/actions/actionsTypes.ts (1)

25-27: LGTM!

app/actions/room.ts (1)

62-68: LGTM!

Also applies to: 77-79, 152-164

app/reducers/room.ts (1)

71-82: LGTM!

app/reducers/room.test.ts (1)

8-9: LGTM!

Also applies to: 75-84

app/lib/methods/loadMessagesForRoom.ts (1)

3-10: LGTM!

Also applies to: 15-150

app/lib/methods/loadMessagesForRoom.test.ts (1)

1-236: LGTM!

app/views/RoomView/List/hooks/useMessages.ts (1)

4-6: LGTM!

Also applies to: 40-40, 166-174

app/views/RoomView/List/hooks/useMessages.test.tsx (1)

12-15: LGTM!

Also applies to: 49-49, 68-70, 76-99, 376-522

app/lib/services/connect.ts (1)

135-141: LGTM!

app/lib/services/connect.test.ts (1)

485-513: LGTM!

patches/@rocket.chat+sdk+1.3.3-mobile.patch (2)

9-45: LGTM!

Also applies to: 171-179


102-167: checkAndReopen promise handling is already covered at call sites

Callers that run in the app handle the returned Promise: app/sagas/state.js uses checkAndReopen().catch(...), and app/lib/services/voip/MediaCallEvents.ts awaits checkAndReopen() inside a try/catch. Remaining hits are unit tests and the connect.ts wrapper definition.

app/lib/hooks/useVideoConf/index.tsx (1)

1-1: LGTM!

Also applies to: 68-71

app/views/RoomView/components/HeaderCallButton.tsx (1)

68-68: LGTM!

app/views/DirectoryView/index.tsx (1)

1-3: LGTM!

Also applies to: 7-7, 17-17, 19-21, 23-23, 26-28, 36-214

app/lib/methods/helpers/announceSearchResultsForAccessibility.ts (1)

1-13: LGTM!

app/lib/methods/helpers/index.ts (1)

21-21: LGTM!

app/views/RoomsListView/hooks/useSearch.ts (1)

6-6: LGTM!

Comment on lines +20 to +33
// Read all call-related settings in a single subscription instead of one useSetting per key.
const settings = useAppSelector(
state => ({
jitsiEnabled: state.settings.Jitsi_Enabled,
jitsiEnableTeams: state.settings.Jitsi_Enable_Teams,
jitsiEnableChannels: state.settings.Jitsi_Enable_Channels,
videoConfEnableDMs: state.settings.VideoConf_Enable_DMs,
videoConfEnableChannels: state.settings.VideoConf_Enable_Channels,
videoConfEnableTeams: state.settings.VideoConf_Enable_Teams,
videoConfEnableGroups: state.settings.VideoConf_Enable_Groups,
omnichannelCallProvider: state.settings.Omnichannel_call_provider
}),
shallowEqual
);

Copy link
Copy Markdown
Contributor

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="app/lib/hooks/useVideoConf/useVideoConfCall.ts"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "=== File header context ==="
sed -n '1,120p' "$FILE" | nl -ba | sed -n '1,120p'

echo "=== Lines 15-120 (for init + effect + derived state) ==="
sed -n '15,120p' "$FILE" | nl -ba

echo "=== Lines 120-220 (if more logic exists) ==="
if [ "$(wc -l < "$FILE")" -ge 220 ]; then
  sed -n '120,220p' "$FILE" | nl -ba
else
  sed -n '120,$p' "$FILE" | nl -ba
fi

echo "=== Search for init/callEnabled/disabledTooltip/rid dependencies ==="
rg -n "init\s*=|callEnabled|disabledTooltip|rid|useEffect\(|useCallback\(" "$FILE"

echo "=== Show full init function if small enough ==="
# Print any init function block if present
rg -n "const init|function init" "$FILE" || true

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/lib/hooks/useVideoConf/useVideoConfCall.ts"
echo "Checking file: $FILE"
ls -la "$FILE"

echo "=== Lines 1-140 ==="
sed -n '1,140p' "$FILE" | cat -n

echo "=== Lines 140-260 ==="
sed -n '140,260p' "$FILE" | cat -n

echo "=== Search for init/callEnabled/disabledTooltip/rid dependencies ==="
rg -n "const init|function init|callEnabled|disabledTooltip|rid|useEffect\(|useCallback\(" "$FILE" || true

echo "=== Extract init block candidates ==="
rg -n "init\s*=" "$FILE" || true
rg -n "const init\s*=\s*async|const init\s*=\s*\(" "$FILE" || true
rg -n "useEffect\s*\(\s*\(" "$FILE" || true

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 5288


Recompute call state when selector inputs change.
useEffect(() => { init(); }, []) runs init only once, but init uses reactive values (rid, server version check, usePermissions result, user?.username, and settings-derived flags like enabledDMs/enabledChannel/enabledTeams/enabledGroups/enabledLiveChat + jitsiEnabled/*). If any of those change after mount, callEnabled and disabledTooltip can stay stale (and disabledTooltip is only ever set to true, never reset).

💡 Suggested fix
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
...
-	const init = async () => {
+	const init = useCallback(async () => {
 		const room = await getSubscriptionByRoomId(rid);
 		if (room) {
 			...
 		}
 		return setCallEnabled(false);
-	};
+	}, [
+		rid,
+		isServer5OrNewer,
+		canStartCall,
+		user?.username,
+		enabledDMs,
+		enabledChannel,
+		enabledTeams,
+		enabledGroups,
+		enabledLiveChat,
+		jitsiEnabled,
+		jitsiEnableTeams,
+		jitsiEnableChannels
+	]);

 	useEffect(() => {
-		init();
-	}, []);
+		init();
+	}, [init]);

Also reset disabledTooltip (e.g., to false) when recomputing so it matches the latest inputs.

🤖 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 `@app/lib/hooks/useVideoConf/useVideoConfCall.ts` around lines 20 - 33, The
effect that calls init() should depend on all reactive inputs so
callEnabled/disabledTooltip are recomputed when inputs change: update the
useEffect in useVideoConfCall.ts to include rid, server version check output,
the permissions result from usePermissions, user?.username, and all settings
fields (jitsiEnabled, jitsiEnableTeams, jitsiEnableChannels, videoConfEnableDMs,
videoConfEnableChannels, videoConfEnableTeams, videoConfEnableGroups,
omnichannelCallProvider) in its dependency array and call init whenever any
change; inside init (or immediately before calling it) reset disabledTooltip
back to false so it can be recalculated (disabledTooltip currently only set true
and never cleared), ensuring the effect references the init function defined in
this hook and updates callEnabled/disabledTooltip derived state accordingly.

Comment on lines +20 to +54
const load = useDebounce(async ({ newSearch = false }: { newSearch?: boolean } = {}) => {
if (!newSearch && (loading || data.length === total)) {
return;
}

if (newSearch) {
setData([]);
setTotal(-1);
}
setLoading(true);

try {
const directories = await getDirectory({
text,
type,
workspace: globalUsers ? 'all' : 'local',
offset: newSearch ? 0 : data.length,
count: 50,
sort: type === 'users' ? { username: 1 } : { usersCount: -1 }
});
if (directories.success) {
setData(prev => [...(newSearch ? [] : prev), ...(directories.result as IServerRoom[])]);
setTotal(directories.total);
setLoading(false);
// Announce the full total on a fresh search; loadMore pages shouldn't re-announce
if (newSearch) {
announceSearchResultsForAccessibility(directories.total);
}
} else {
setLoading(false);
}
} catch (e) {
log(e);
setLoading(false);
}

Copy link
Copy Markdown
Contributor

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

Prevent stale responses from overwriting newer search results.

Concurrent requests are not sequenced. If an older request resolves after a newer one, it can overwrite/append stale data into the latest query results.

💡 Suggested fix
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
@@
 	const [globalUsers, setGlobalUsers] = useState(true);
 	const [type, setType] = useState(directoryDefaultView);
+	const requestIdRef = useRef(0);
@@
 	const load = useDebounce(async ({ newSearch = false }: { newSearch?: boolean } = {}) => {
 		if (!newSearch && (loading || data.length === total)) {
 			return;
 		}
+		const requestId = ++requestIdRef.current;
@@
-		try {
+		try {
 			const directories = await getDirectory({
@@
-			if (directories.success) {
-				setData(prev => [...(newSearch ? [] : prev), ...(directories.result as IServerRoom[])]);
-				setTotal(directories.total);
-				setLoading(false);
+			if (requestId !== requestIdRef.current) {
+				return;
+			}
+			if (directories.success) {
+				setData(prev => [...(newSearch ? [] : prev), ...(directories.result as IServerRoom[])]);
+				setTotal(directories.total);
 				// Announce the full total on a fresh search; loadMore pages shouldn't re-announce
 				if (newSearch) {
 					announceSearchResultsForAccessibility(directories.total);
 				}
-			} else {
-				setLoading(false);
 			}
 		} catch (e) {
+			if (requestId !== requestIdRef.current) {
+				return;
+			}
 			log(e);
+		} finally {
+			if (requestId === requestIdRef.current) {
+				setLoading(false);
+			}
-			setLoading(false);
 		}
 	}, 200);
📝 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 load = useDebounce(async ({ newSearch = false }: { newSearch?: boolean } = {}) => {
if (!newSearch && (loading || data.length === total)) {
return;
}
if (newSearch) {
setData([]);
setTotal(-1);
}
setLoading(true);
try {
const directories = await getDirectory({
text,
type,
workspace: globalUsers ? 'all' : 'local',
offset: newSearch ? 0 : data.length,
count: 50,
sort: type === 'users' ? { username: 1 } : { usersCount: -1 }
});
if (directories.success) {
setData(prev => [...(newSearch ? [] : prev), ...(directories.result as IServerRoom[])]);
setTotal(directories.total);
setLoading(false);
// Announce the full total on a fresh search; loadMore pages shouldn't re-announce
if (newSearch) {
announceSearchResultsForAccessibility(directories.total);
}
} else {
setLoading(false);
}
} catch (e) {
log(e);
setLoading(false);
}
const load = useDebounce(async ({ newSearch = false }: { newSearch?: boolean } = {}) => {
if (!newSearch && (loading || data.length === total)) {
return;
}
if (newSearch) {
setData([]);
setTotal(-1);
}
setLoading(true);
const requestId = ++requestIdRef.current;
try {
const directories = await getDirectory({
text,
type,
workspace: globalUsers ? 'all' : 'local',
offset: newSearch ? 0 : data.length,
count: 50,
sort: type === 'users' ? { username: 1 } : { usersCount: -1 }
});
if (requestId !== requestIdRef.current) {
return;
}
if (directories.success) {
setData(prev => [...(newSearch ? [] : prev), ...(directories.result as IServerRoom[])]);
setTotal(directories.total);
// Announce the full total on a fresh search; loadMore pages shouldn't re-announce
if (newSearch) {
announceSearchResultsForAccessibility(directories.total);
}
}
} catch (e) {
if (requestId !== requestIdRef.current) {
return;
}
log(e);
} finally {
if (requestId === requestIdRef.current) {
setLoading(false);
}
}
}, 200);
🤖 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 `@app/views/DirectoryView/hooks/useDirectorySearch.ts` around lines 20 - 54,
The load handler can have older async responses overwrite newer results; fix by
adding a request sequencing guard: create a stable ref/counter (e.g.,
latestRequestIdRef) and increment it each time load runs, capture the current id
in the async closure, then after the await from getDirectory(...) verify the
captured id === latestRequestIdRef.current before calling setData, setTotal,
announceSearchResultsForAccessibility or setLoading(false) for successful
branch; also check the id in the error/catch branch so stale failures don't
clear loading or log for newer requests. Ensure the guard is applied around all
state updates in load (including the newSearch branch logic) so only the most
recent getDirectory() result mutates state.

Take the released (develop) version of CLAUDE.md, NotificationIntentHandler.kt,
and the expo-file-system patch — these were stale single-server drift, not
product customizations, so the single-server release merge should adopt the
released content. Leaves the diff vs develop as whitelabel + version only.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
patches/expo-file-system+19.0.21.patch (1)

14-21: ⚠️ Potential issue | 🟠 Major

Harden injected shared client visibility to prevent stale/untuned OkHttp reads (Line 14-21).

patches/expo-file-system+19.0.21.patch makes FileSystemLegacyModule store a mutable process-wide companion object var client: OkHttpClient? = null, and SSLPinningTurboModule writes it via FileSystemLegacyModule.setOkHttpClient(client);. Without @Volatile/synchronization, there’s no JVM happens-before edge, so other threads can observe stale null/previous values—risking SSL pinning not being applied as intended.

Suggested patch update
   companion object {
     `@JvmStatic`
-    var client: OkHttpClient? = null
+    `@Volatile`
+    private var client: OkHttpClient? = null
 
     `@JvmStatic`
     fun setOkHttpClient(okHttpClient: OkHttpClient) {
       client = okHttpClient
     }
   }
🤖 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 `@patches/expo-file-system`+19.0.21.patch around lines 14 - 21, The companion
object's mutable process-wide client field in FileSystemLegacyModule is not
thread-safe; mark the var client: OkHttpClient? as `@Volatile` (or switch to an
AtomicReference<OkHttpClient?>) and ensure setOkHttpClient writes the
volatile/atomic reference so JVM happens-before guarantees apply; update
references in FileSystemLegacyModule (companion object client and
setOkHttpClient) and any writer call sites (e.g., SSLPinningTurboModule) to use
the new volatile/atomic field so readers on other threads cannot observe
stale/null values.
🤖 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.

Outside diff comments:
In `@patches/expo-file-system`+19.0.21.patch:
- Around line 14-21: The companion object's mutable process-wide client field in
FileSystemLegacyModule is not thread-safe; mark the var client: OkHttpClient? as
`@Volatile` (or switch to an AtomicReference<OkHttpClient?>) and ensure
setOkHttpClient writes the volatile/atomic reference so JVM happens-before
guarantees apply; update references in FileSystemLegacyModule (companion object
client and setOkHttpClient) and any writer call sites (e.g.,
SSLPinningTurboModule) to use the new volatile/atomic field so readers on other
threads cannot observe stale/null values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c75b976-5525-4d60-85c2-46fd823756af

📥 Commits

Reviewing files that changed from the base of the PR and between 0140ead and 9ec2f3a.

📒 Files selected for processing (3)
  • CLAUDE.md
  • android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt
  • patches/expo-file-system+19.0.21.patch
💤 Files with no reviewable changes (1)
  • CLAUDE.md
✅ Files skipped from review due to trivial changes (1)
  • android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Run `pnpm prettier-lint` and `TZ=UTC pnpm test` for modified files before committing
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Pre-commit hooks enforce Prettier and test checks
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Use local-first approach: UI reads from WatermelonDB, sagas sync with server
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Do not conflate VoIP and VideoConf features — they are entirely separate systems
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Support iOS 13.4+ and Android 6.0+
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Use React 19.1, React Native 0.81, and Expo 54
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Use pnpm (version pinned in `package.json#packageManager`, activated via corepack) and Node >= 18
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-06-09T19:32:40.059Z
Learning: Read UBIQUITOUS_LANGUAGE.md for project terminology and domain language
📚 Learning: 2026-05-05T21:08:33.177Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7298
File: patches/@rocket.chat+sdk+1.3.3-mobile.patch:79-79
Timestamp: 2026-05-05T21:08:33.177Z
Learning: In the RocketChat/Rocket.Chat.ReactNative repo, for patches under patches/*.patch (especially those touching rocket.chat/sdk), remember that the patching sandbox's node_modules reflects the pre-patch state. Grepping node_modules for symbols (e.g., userDisconnectCloseCode = 4000 in node_modules/rocket.chat/sdk/lib/drivers/ddp.ts) can yield false positives. Review patches by inspecting the diff and applying it to a fresh copy of the SDK or diffing against the SDK source, rather than relying on node_modules. Ensure the patch actually introduces/updates symbols in the SDK source and run the test suite to validate behavior after applying the patch.

Applied to files:

  • patches/expo-file-system+19.0.21.patch
🔇 Additional comments (1)
patches/expo-file-system+19.0.21.patch (1)

26-26: LGTM!

@diegolmello diegolmello merged commit 5be5c5a into single-server Jun 9, 2026
6 of 9 checks passed
@diegolmello diegolmello deleted the 4.73.1-single-server branch June 9, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants