Skip to content

feat(a11y): Message actions announcement and back focus#7229

Open
OtavioStasiak wants to merge 31 commits into
developfrom
feat.a11y-action-sheet-focus-and-hint
Open

feat(a11y): Message actions announcement and back focus#7229
OtavioStasiak wants to merge 31 commits into
developfrom
feat.a11y-action-sheet-focus-and-hint

Conversation

@OtavioStasiak
Copy link
Copy Markdown
Contributor

@OtavioStasiak OtavioStasiak commented Apr 23, 2026

Proposed changes

Improve the actionSheet behavior on a11y.

  • Action Sheet handle is accessible;
  • open message actions is available on a11y actions;
  • When we close MessageActions the focus went back to the message, and not from top of the View;

Issue(s)

https://rocketchat.atlassian.net/browse/MA-263

How to test or reproduce

  • Open the app;
  • Go to Room;
  • Turn Screen reader on;
  • Focus on a message;
  • Open ActionSheet;
  • Navigate through the action sheet;
  • Close ActionSheet focusing on handle;

Screenshots

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

Summary by CodeRabbit

  • Accessibility Improvements

    • Improved focus management for action sheets and message actions; restores screen-reader focus after sheets open/close on iOS and Android.
    • Localized labels and hints for dismissing action sheets and showing message actions.
  • New Features

    • Hook to track the last-focused message to return focus reliably.
    • New message accessibility label and action generation for richer screen‑reader descriptions.
  • Tests

    • Added tests for message accessibility label and actions behavior.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • app/containers/message/__snapshots__/Message.test.tsx.snap is excluded by !**/*.snap

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4b6abbc-2f16-420b-8dc5-1e04b4b86ac3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds accessibility focus tracking and restoration for message action sheets: shared last-focused message ref, message accessibility label/action utilities and tests, Touch/Handle ref forwarding, action-sheet header focus on present, and focus restoration on dismiss.

Changes

Accessibility Focus Restoration Workflow

Layer / File(s) Summary
Accessibility Ref Tracker
app/lib/a11y/useLastFocusedMessageRef.ts
New utility that maintains a module-scoped RefObject<View | null> with markAsLastFocused(), get(), and clear() for tracking the last focused message.
Message Accessibility Helpers & Tests
app/lib/a11y/useMessageAccessibilityLabel.ts, app/lib/a11y/useMessageAccessibilityActions.ts, app/lib/a11y/*test.ts
Adds buildMessageAccessibilityLabel / useMessageAccessibilityLabel and useMessageAccessibilityActions, plus comprehensive tests covering label composition, translation/read-receipt/thread hints, and action availability.
Message & Touch integration
app/containers/message/Message.tsx, app/containers/message/Touch.tsx
Integrates accessibility label/actions into MessageTouchable, tracks last-focused message on long-press, forwards componentRef through Touch, and wires onAccessibilityAction to trigger message actions.
ActionSheet Handle ForwardRef
app/containers/ActionSheet/Handle.tsx
Converts Handle to forwardRef, forwards the RectButton ref, and sources accessibility label/hint from i18n keys A11y_close_action_sheet / A11y_close_action_sheet_hint.
ActionSheet Focus on Present
app/containers/ActionSheet/ActionSheet.tsx
Adds handleRef, focusHandle() (uses findNodeHandle + AccessibilityInfo.setAccessibilityFocus) and onDidPresent wired into TrueSheet; on Android the focus is deferred ~300ms.
MessageActions Focus Restoration
app/containers/MessageActions/index.tsx
Captures last-focused message ref via useLastFocusedMessageRef, provides onClose to restore accessibility focus to the saved native node after dismiss animation, clears the stored ref, and passes onClose to showActionSheet.
Localization entries
app/i18n/locales/*
Adds keys: A11y_close_action_sheet, A11y_close_action_sheet_hint, and Show_message_actions (English) and A11y_close_action_sheet across many locale files.

Sequence Diagram

sequenceDiagram
  participant User
  participant MessageTouchable
  participant lastFocusedRef as lastFocusedMessageRef
  participant MessageActions
  participant ActionSheet
  participant Handle
  participant AccessibilityInfo

  User->>MessageTouchable: Long press / accessibility action
  MessageTouchable->>lastFocusedRef: markAsLastFocused()
  MessageTouchable->>MessageActions: open message actions
  MessageActions->>ActionSheet: showActionSheet(onClose)
  ActionSheet->>ActionSheet: onDidPresent
  ActionSheet->>Handle: focusHandle -> findNodeHandle(handleRef)
  Handle->>AccessibilityInfo: setAccessibilityFocus(header) (defer 300ms on Android)

  User->>ActionSheet: Dismiss
  ActionSheet->>lastFocusedRef: get() -> messageRef
  ActionSheet->>AccessibilityInfo: setAccessibilityFocus(messageRef) (after animation)
  ActionSheet->>lastFocusedRef: clear()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: feature

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: improving accessibility for message actions through focus announcement and returning focus to the message after dismissal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ 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)
  • MA-263: Request failed with status code 401

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@OtavioStasiak OtavioStasiak changed the title feat: announcement and focus feat(a11y): Message actions announcement and back focus Apr 23, 2026
@OtavioStasiak OtavioStasiak temporarily deployed to experimental_ios_build April 23, 2026 16:42 — with GitHub Actions Inactive
@OtavioStasiak OtavioStasiak temporarily deployed to experimental_android_build April 23, 2026 16:42 — with GitHub Actions Inactive
@OtavioStasiak OtavioStasiak had a problem deploying to upload_experimental_android April 23, 2026 17:18 — with GitHub Actions Error
@github-actions
Copy link
Copy Markdown

Android Build Available

Rocket.Chat Experimental 4.72.0.108617

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNR-q4kPySm10tUnMpVhAS_0xW-fB4K8AaSET3MuDoXjn9hXgrmFsio_VEihbYHT191fB6aC8vRNjhGD5Ibp

@github-actions
Copy link
Copy Markdown

iOS Build Available

Rocket.Chat Experimental 4.72.0.108618

@OtavioStasiak OtavioStasiak had a problem deploying to experimental_ios_build May 11, 2026 20:11 — with GitHub Actions Error
@OtavioStasiak OtavioStasiak had a problem deploying to official_android_build May 11, 2026 20:11 — with GitHub Actions Error
Comment thread app/containers/message/Message.tsx Outdated
Comment thread app/i18n/locales/en.json
Comment thread app/containers/message/Message.tsx Outdated
Comment thread app/containers/message/Message.tsx Outdated
Comment thread app/lib/a11y/useLastFocusedMessageRef.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/a11y/useMessageAccessibilityLabel.test.ts`:
- Around line 33-41: The test suite is missing a case for isThreadReply when
tmid is falsy which exposes a bug in useMessageAccessibilityLabel where the
implementation mistakenly references props instead of props.msg (causing
“[object Object]” in the label); add the proposed test case (it('handles
isThreadReply without tmid', ...) asserting the label contains 'replying to' and
'hello world' and not '[object Object]') and fix the implementation in
useMessageAccessibilityLabel by replacing any erroneous uses of props with
props.msg (ensure all message field accesses come from props.msg and handle tmid
undefined correctly so the accessibility label builds from the message text).

In `@app/lib/a11y/useMessageAccessibilityLabel.ts`:
- Line 28: The accessibility label construction in useMessageAccessibilityLabel
sets label to `replying to ${props.tmid ? \`thread message ${props.msg}\` :
props}` which falls back to the entire props object when tmid is falsy; update
the fallback to use props.msg (or a safe string) instead of props so the label
becomes `replying to ${props.tmid ? \`thread message ${props.msg}\` :
props.msg}` (ensure you reference the label assignment in
useMessageAccessibilityLabel and handle missing/undefined props.msg with a
sensible default).
- Around line 33-46: In useMessageAccessibilityLabel, you're duplicating the
"Encrypted_message" string by setting label = i18n.t('Encrypted_message') when
props.isEncrypted and also appending encryptedMessageLabel = props.isEncrypted ?
i18n.t('Encrypted_message') : ''; remove the redundant encryptedMessageLabel
assignment (or its inclusion in the final concatenation) so the encrypted
message text appears only once, keeping the initial label setting when
props.isEncrypted and leaving readReceipt/other suffixes intact.
- Line 37: The code is suppressing a real TypeScript error with // `@ts-ignore`
when calling getInfoMessage from useMessageAccessibilityLabel; instead extract
and pass only the required properties (type, role, msg, author: { username:
string }, and optional comment) from the incoming IMessageTouchable & IMessage
object rather than spreading ...props. Update the call to getInfoMessage to
build a new plain object using props.type, props.role, props.msg, props.comment
(if present) and author: { username: props.author.username } (converting from
IUserMessage), remove the // `@ts-ignore`, and ensure the argument matches the
expected shape so the TS error is resolved.
🪄 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: fb62eaa2-72bd-4202-b41e-69536d992978

📥 Commits

Reviewing files that changed from the base of the PR and between 8bc9b9c and b08698c.

📒 Files selected for processing (29)
  • app/containers/message/Message.tsx
  • app/i18n/locales/ar.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/de.json
  • app/i18n/locales/es.json
  • app/i18n/locales/fi.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/it.json
  • app/i18n/locales/ja.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/nn.json
  • app/i18n/locales/no.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/zh-TW.json
  • app/lib/a11y/useMessageAccessibilityActions.test.ts
  • app/lib/a11y/useMessageAccessibilityActions.ts
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
✅ Files skipped from review due to trivial changes (20)
  • app/i18n/locales/es.json
  • app/lib/a11y/useMessageAccessibilityActions.test.ts
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/ar.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/no.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/it.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/ja.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/nn.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/fi.json
  • app/i18n/locales/hu.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/containers/message/Message.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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/a11y/useMessageAccessibilityActions.ts
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.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

Files:

  • app/lib/a11y/useMessageAccessibilityActions.ts
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character width, no trailing commas, avoid arrow parens, and bracket same line

Files:

  • app/lib/a11y/useMessageAccessibilityActions.ts
  • app/i18n/locales/cs.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/de.json
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/i18n/locales/zh-TW.json
  • app/i18n/locales/sv.json
  • app/lib/a11y/useMessageAccessibilityLabel.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base including React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/a11y/useMessageAccessibilityActions.ts
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
app/i18n/**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Place i18n configuration and locale files in 'app/i18n/' directory with support for 40+ locales and RTL

Files:

  • app/i18n/locales/cs.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/de.json
  • app/i18n/locales/zh-TW.json
  • app/i18n/locales/sv.json
🧠 Learnings (1)
📚 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/a11y/useMessageAccessibilityActions.ts
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
🔇 Additional comments (6)
app/i18n/locales/cs.json (1)

12-12: LGTM!

app/i18n/locales/de.json (1)

12-12: LGTM!

app/i18n/locales/hi-IN.json (1)

12-12: LGTM!

app/i18n/locales/sv.json (1)

12-12: LGTM!

app/i18n/locales/zh-TW.json (1)

8-8: LGTM!

app/lib/a11y/useMessageAccessibilityActions.ts (1)

5-12: LGTM!

Comment thread app/lib/a11y/useMessageAccessibilityLabel.test.ts
Comment thread app/lib/a11y/useMessageAccessibilityLabel.ts
Comment thread app/lib/a11y/useMessageAccessibilityLabel.ts
Comment thread app/lib/a11y/useMessageAccessibilityLabel.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
app/lib/a11y/useMessageAccessibilityLabel.ts (3)

28-28: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix object-to-string coercion bug.

When isThreadReply is true and tmid is falsy, the fallback uses props (the entire object) instead of props.msg, which will produce [object Object] in the accessibility label.

🐛 Proposed fix
 	if (props.isThreadReply) {
-		label = `replying to ${props.tmid ? `thread message ${props.msg}` : props}`;
+		label = `replying to ${props.tmid ? `thread message ${props.msg}` : props.msg}`;
 	}
🤖 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/a11y/useMessageAccessibilityLabel.ts` at line 28, The accessibility
label construction in useMessageAccessibilityLabel is coercing the whole props
object to string when tmid is falsy; update the label assignment so the fallback
uses props.msg (not props) when building the "replying to" string (i.e., change
the expression that currently does `props.tmid ? \`thread message ${props.msg}\`
: props` to use `: props.msg`), ensuring you reference the label variable and
props.tmid/props.msg in the fix.

36-39: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass only required properties to getInfoMessage instead of using @ts-ignore.

The @ts-ignore suppresses a real type mismatch: getInfoMessage expects specific properties including author: { username: string }, but spreading ...props passes the entire IMessageTouchable & IMessage object with author as IUserMessage (which has additional fields beyond username).

🔧 Suggested fix
 	if (props.isInfo) {
-		// `@ts-ignore`
-		label = getInfoMessage({ ...props });
+		label = getInfoMessage({
+			type: props.type,
+			role: props.role,
+			msg: props.msg,
+			author: { username: props.author?.username || '' },
+			comment: props.comment
+		});
 	}

As per coding guidelines: "Use explicit error handling" and "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/lib/a11y/useMessageAccessibilityLabel.ts` around lines 36 - 39, The code
currently ignores TS errors when calling getInfoMessage by spreading ...props;
instead remove the `@ts-ignore` and call getInfoMessage with an explicit object
containing only the properties that getInfoMessage expects (for example pass
author: { username: props.author.username } and any other required fields rather
than the full IMessage/IMessageTouchable), e.g. build a small payload from props
when props.isInfo is true and pass that to getInfoMessage; this preserves type
safety and avoids passing an IUserMessage with extra fields.

33-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove duplicate "Encrypted message" in final label.

When isEncrypted is true, label is set to i18n.t('Encrypted_message') at line 34, and encryptedMessageLabel is also set to the same string at line 46. Both are concatenated in the return statement at line 53, causing "Encrypted message" to appear twice.

♻️ Proposed fix: remove the duplicate assignment
 	const readOrUnreadLabel = !props.unread && props.unread !== null ? i18n.t('Message_was_read') : i18n.t('Message_was_not_read');
 	const readReceipt = props.isReadReceiptEnabled && !props.isInfo ? readOrUnreadLabel : '';
-	const encryptedMessageLabel = props.isEncrypted ? i18n.t('Encrypted_message') : '';
 	const translatedLanguage = translationLanguages[props?.autoTranslateLanguage || 'en'];
 	const translated = props.isTranslated ? i18n.t('Message_translated_into_idiom', { idiom: translatedLanguage }) : '';
 	const isThread = !props.isInfo && !!(props.tmid || props.tcount || props.isThreadReply || props.isThreadSequential);
 	const threadHint = isThread ? `. ${i18n.t('A11y_press_to_view_thread')}` : '';
 	return props.isTranslated
 		? `${user} ${hour} ${translated}${threadHint}`
-		: `${user} ${hour} ${translated} ${label}. ${encryptedMessageLabel} ${readReceipt}${threadHint}`;
+		: `${user} ${hour} ${translated} ${label}. ${readReceipt}${threadHint}`;
🤖 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/a11y/useMessageAccessibilityLabel.ts` around lines 33 - 53, The
return value duplicates the "Encrypted_message" because label is set to
i18n.t('Encrypted_message') when props.isEncrypted and encryptedMessageLabel is
also appended; in useMessageAccessibilityLabel remove the redundant assignment
by not overriding label for encrypted messages (delete or skip the if
(props.isEncrypted) { label = i18n.t('Encrypted_message'); } block) so
encryptedMessageLabel remains the single source for that phrase (keep references
to label, encryptedMessageLabel, and the final return unchanged).
app/lib/a11y/useMessageAccessibilityLabel.test.ts (1)

100-103: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Enhance test to catch the object-to-string coercion bug.

This test sets isThreadReply: true without a tmid (since baseProps doesn't include it), which triggers the critical bug at line 28 of the implementation where props is used instead of props.msg. However, the test only checks for the thread hint and doesn't verify the actual label content, so it misses the [object Object] bug.

✅ Enhanced test case
 	it('appends "Press to view thread" hint when message is a thread reply', () => {
 		const { result } = renderHook(() => useMessageAccessibilityLabel({ ...baseProps, isThreadReply: true }));
+		expect(result.current).toContain('replying to hello world');
+		expect(result.current).not.toContain('[object Object]');
 		expect(result.current).toContain('Press to view thread');
 	});
🤖 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/a11y/useMessageAccessibilityLabel.test.ts` around lines 100 - 103,
The test for useMessageAccessibilityLabel misses the object-to-string coercion
bug because it sets isThreadReply: true without asserting the full label or
checking for "[object Object]"; update the test that uses baseProps and
isThreadReply to include a realistic msg object (ensure baseProps.msg is
present), call useMessageAccessibilityLabel with isThreadReply: true and no
tmid, then assert the returned label contains the expected readable text (e.g.,
the message text and "Press to view thread") and explicitly assert it does NOT
contain the substring "[object Object]" so the implementation bug where props is
used instead of props.msg is caught; reference useMessageAccessibilityLabel,
baseProps, isThreadReply, tmid and props.msg when making the assertion.
🧹 Nitpick comments (1)
app/lib/a11y/useMessageAccessibilityLabel.test.ts (1)

5-18: ⚡ Quick win

Replace any type with proper interface.

Using any for baseProps defeats TypeScript's type checking. Define an explicit type matching IMessageTouchable & IMessage or import the Params type from the implementation.

♻️ Suggested fix
+import { type IMessage, type IMessageTouchable } from '../../containers/message/interfaces';
+
 import { renderHook } from '@testing-library/react-native';

 import { useMessageAccessibilityLabel } from './useMessageAccessibilityLabel';

-const baseProps: any = {
+const baseProps: IMessageTouchable & IMessage = {
 	msg: 'hello world',
 	author: { username: 'john', name: 'John Doe' },

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/lib/a11y/useMessageAccessibilityLabel.test.ts` around lines 5 - 18, The
test uses baseProps: any which disables type checking; change baseProps to use
the proper interface by importing and applying the correct type (e.g.,
IMessageTouchable & IMessage or the Params type exported by
useMessageAccessibilityLabel) and annotate the constant accordingly; ensure all
fields in baseProps match the selected interface (msg, author, ts, isInfo,
isThreadReply, isThreadSequential, isEncrypted, isTranslated,
isReadReceiptEnabled, useRealName, unread, type) and update any
missing/incorrect property names to satisfy the type.
🤖 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/a11y/useMessageAccessibilityLabel.ts`:
- Line 47: The lookup for translatedLanguage using
translationLanguages[props?.autoTranslateLanguage || 'en'] can yield undefined
if props.autoTranslateLanguage is not a key; update the logic in
useMessageAccessibilityLabel so you coalesce missing keys to a safe default
(e.g., 'en' or translationLanguages['en']) before interpolation: check
translationLanguages[props?.autoTranslateLanguage] and fall back to
translationLanguages['en'] (or a hardcoded string) when undefined, then use that
safe translatedLanguage value for the i18n string to avoid "translated into
undefined".

---

Duplicate comments:
In `@app/lib/a11y/useMessageAccessibilityLabel.test.ts`:
- Around line 100-103: The test for useMessageAccessibilityLabel misses the
object-to-string coercion bug because it sets isThreadReply: true without
asserting the full label or checking for "[object Object]"; update the test that
uses baseProps and isThreadReply to include a realistic msg object (ensure
baseProps.msg is present), call useMessageAccessibilityLabel with isThreadReply:
true and no tmid, then assert the returned label contains the expected readable
text (e.g., the message text and "Press to view thread") and explicitly assert
it does NOT contain the substring "[object Object]" so the implementation bug
where props is used instead of props.msg is caught; reference
useMessageAccessibilityLabel, baseProps, isThreadReply, tmid and props.msg when
making the assertion.

In `@app/lib/a11y/useMessageAccessibilityLabel.ts`:
- Line 28: The accessibility label construction in useMessageAccessibilityLabel
is coercing the whole props object to string when tmid is falsy; update the
label assignment so the fallback uses props.msg (not props) when building the
"replying to" string (i.e., change the expression that currently does
`props.tmid ? \`thread message ${props.msg}\` : props` to use `: props.msg`),
ensuring you reference the label variable and props.tmid/props.msg in the fix.
- Around line 36-39: The code currently ignores TS errors when calling
getInfoMessage by spreading ...props; instead remove the `@ts-ignore` and call
getInfoMessage with an explicit object containing only the properties that
getInfoMessage expects (for example pass author: { username:
props.author.username } and any other required fields rather than the full
IMessage/IMessageTouchable), e.g. build a small payload from props when
props.isInfo is true and pass that to getInfoMessage; this preserves type safety
and avoids passing an IUserMessage with extra fields.
- Around line 33-53: The return value duplicates the "Encrypted_message" because
label is set to i18n.t('Encrypted_message') when props.isEncrypted and
encryptedMessageLabel is also appended; in useMessageAccessibilityLabel remove
the redundant assignment by not overriding label for encrypted messages (delete
or skip the if (props.isEncrypted) { label = i18n.t('Encrypted_message'); }
block) so encryptedMessageLabel remains the single source for that phrase (keep
references to label, encryptedMessageLabel, and the final return unchanged).

---

Nitpick comments:
In `@app/lib/a11y/useMessageAccessibilityLabel.test.ts`:
- Around line 5-18: The test uses baseProps: any which disables type checking;
change baseProps to use the proper interface by importing and applying the
correct type (e.g., IMessageTouchable & IMessage or the Params type exported by
useMessageAccessibilityLabel) and annotate the constant accordingly; ensure all
fields in baseProps match the selected interface (msg, author, ts, isInfo,
isThreadReply, isThreadSequential, isEncrypted, isTranslated,
isReadReceiptEnabled, useRealName, unread, type) and update any
missing/incorrect property names to satisfy the 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: efe12536-326d-4e0d-8d78-8cd19ca5bb62

📥 Commits

Reviewing files that changed from the base of the PR and between b08698c and b97c8b2.

📒 Files selected for processing (30)
  • app/containers/MessageActions/index.tsx
  • app/containers/message/Message.tsx
  • app/i18n/locales/ar.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/de.json
  • app/i18n/locales/en.json
  • app/i18n/locales/es.json
  • app/i18n/locales/fi.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/it.json
  • app/i18n/locales/ja.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/nn.json
  • app/i18n/locales/no.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/zh-TW.json
  • app/lib/a11y/useLastFocusedMessageRef.ts
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
✅ Files skipped from review due to trivial changes (10)
  • app/i18n/locales/fi.json
  • app/i18n/locales/es.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/ar.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/it.json
  • app/i18n/locales/de.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/ta-IN.json
🚧 Files skipped from review as they are similar to previous changes (15)
  • app/i18n/locales/nn.json
  • app/lib/a11y/useLastFocusedMessageRef.ts
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/zh-TW.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/en.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/ja.json
  • app/containers/MessageActions/index.tsx
  • app/i18n/locales/ru.json
  • app/i18n/locales/no.json
📜 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 (6)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character width, no trailing commas, avoid arrow parens, and bracket same line

Files:

  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/tr.json
  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
  • app/containers/message/Message.tsx
app/i18n/**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Place i18n configuration and locale files in 'app/i18n/' directory with support for 40+ locales and RTL

Files:

  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/tr.json
**/*.{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/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
  • app/containers/message/Message.tsx
**/*.{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

Files:

  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
  • app/containers/message/Message.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base including React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
  • app/containers/message/Message.tsx
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • app/containers/message/Message.tsx
🧠 Learnings (1)
📚 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/a11y/useMessageAccessibilityLabel.test.ts
  • app/lib/a11y/useMessageAccessibilityLabel.ts
  • app/containers/message/Message.tsx
🔇 Additional comments (4)
app/i18n/locales/tr.json (1)

8-8: LGTM!

Also applies to: 12-12

app/i18n/locales/zh-CN.json (1)

8-8: LGTM!

Also applies to: 12-12

app/containers/message/Message.tsx (1)

176-218: LGTM!

app/lib/a11y/useMessageAccessibilityLabel.ts (1)

57-57: No action needed. The 'use memo' directive is a valid React Compiler directive, properly documented at react.dev/reference/react-compiler/directives, and correctly placed at the beginning of the function for compiler optimization. It is not a React 19 feature like 'use client' or 'use server', but rather a build-time compiler instruction that enables the React Compiler to memoize this function.

Comment thread app/lib/a11y/useMessageAccessibilityLabel.ts
@OtavioStasiak OtavioStasiak temporarily deployed to approve_e2e_testing May 14, 2026 21:11 — with GitHub Actions Inactive
@OtavioStasiak OtavioStasiak had a problem deploying to experimental_android_build May 14, 2026 21:15 — with GitHub Actions Error
@OtavioStasiak OtavioStasiak had a problem deploying to official_android_build May 14, 2026 21:15 — with GitHub Actions Error
@OtavioStasiak OtavioStasiak had a problem deploying to experimental_ios_build May 14, 2026 21:15 — with GitHub Actions Error
@OtavioStasiak OtavioStasiak requested a deployment to experimental_ios_build May 15, 2026 15:12 — with GitHub Actions Waiting
@OtavioStasiak OtavioStasiak requested a deployment to experimental_android_build May 15, 2026 15:12 — with GitHub Actions Waiting
@OtavioStasiak OtavioStasiak requested a deployment to official_android_build May 15, 2026 15:12 — with GitHub Actions Waiting
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.

2 participants