Skip to content

refactor: migrate Message rendering to a per-message store#7455

Open
diegolmello wants to merge 67 commits into
developfrom
native-22-message-hooks
Open

refactor: migrate Message rendering to a per-message store#7455
diegolmello wants to merge 67 commits into
developfrom
native-22-message-hooks

Conversation

@diegolmello

@diegolmello diegolmello commented Jun 30, 2026

Copy link
Copy Markdown
Member

Proposed changes

Modernizes the message rendering layer and folds in the two prerequisite changes it is built on.

Message rendering

  • Replaces the single useMessage hook — which snapshotted the whole message record and prop-drilled every field down the subtree — with a per-message store. The store is created once per message and ticked by the WatermelonDB record's subscription; each leaf reads only the fields it needs through granular selector hooks. A change to one field now re-renders just the consumers of that field instead of the entire message.
  • The selector hooks bail out per field (Object.is for a single field, shallow equality for a domain group). This relies on the model's @json(..., { memo: true }) decorators, which return a stable reference when the underlying column is unchanged.
  • Migrates the message row container from a class component to a function component.
  • Moves per-message interaction state out of the container into a per-Room store, so interacting with one message no longer re-renders the rest of the list.
  • Narrows the Message component props contract: handlers and shared data now flow through MessageContext instead of being threaded down as individual props.
  • Annotates the presentational message layers with the 'use memo' directive for the React Compiler (which runs in annotation mode in this app).
  • Removes dead state from the RoomView message container (reactionsModalVisible, replyWithMention, isOnHold); on-hold re-renders are already covered by the roomUpdate diff in shouldComponentUpdate.

Prerequisites folded in

Pull requests #7450 and #7451 are closed in favor of this one, which carries their commits.

Issue(s)

How to test or reproduce

This is a behavior-preserving refactor — the goal is that nothing changes for the user.

  1. Open a busy channel and scroll up through history. Confirm every message type still renders: plain and long text, markdown (bold, headings, inline code, code blocks, links), grouped consecutive-author messages (collapsed header/avatar after the first), system messages, reactions, thread badges/replies, URL preview cards, image/quote/file attachments, edited and deleted indicators, and date separators.
  2. Long-press a message — the action sheet should open. Tap a thread badge — it should navigate into the thread.
  3. Open a DM and an encrypted room to confirm they render, and confirm an auto-translated message shows its translated body.
  4. Edit a message, add a reaction, and send a message that errors — confirm only the affected message updates, not the whole list.

Verified on an Android emulator across public channels, a DM, and E2EE rooms with no red box, crash, blank rows, or visual regression. TZ=UTC pnpm test passes and pnpm lint reports 0 errors; message-container story snapshots are unchanged, so the presentational output is byte-identical.

Screenshots

No visual change — behavior-preserving refactor.

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

The refactor is staged so each step is independently reviewable: memoize the @json model fields, introduce the per-message store and selector hooks, migrate the container to a function component, relocate interaction state to a per-Room store, narrow the props contract to a context, annotate for the React Compiler, then delete the now-dead state. Story-snapshot tests render through the real message container, so an unchanged snapshot is the behavior oracle for the whole refactor.

Summary by CodeRabbit

  • New Features
    • Added dedicated interaction state management for message editing, quoting, and reacting, improving consistency across composer, sharing, and thread experiences.
    • Expanded support for custom emoji and richer message accessibility labeling.
  • Bug Fixes
    • Reduced crashes when user/message metadata is missing, improving resilience for image, video, audio, quote, and reply rendering.
    • Safer interaction handlers and null-tolerant link/action behavior.
  • Refactor
    • Centralized message state retrieval to streamline rendering and reduce prop plumbing.
  • Tests
    • Added/updated regression coverage for message container, thread behavior, and composer interactions.
  • Documentation
    • Updated glossary/project guidance references to the latest shared context.

…state vocabulary

Rename UBIQUITOUS_LANGUAGE.md to CONTEXT.md (history preserved) and update its
three references in CLAUDE.md, the VoIP docs README, and the RoomView
architecture doc. Add the "Message Interaction & Position State" section
distinguishing Interaction state (selection plus the reply/quote/edit/react
action, owned by the per-Room interaction store) from Positional state
(highlight plus jump/scroll position, owned by the Room view scroll machinery),
since the two are migrated independently.

Ref: NATIVE-1345

Claude-Session: https://claude.ai/code/session_01Cj1jHhEbiC9mWGe8dpqQeS
Switch every @json-decorated WatermelonDB model field to the memoized form
({ memo: true }) so an unchanged nested JSON value keeps its reference identity
across reads, enabling per-field re-render bailout once consumers adopt it.

The memo cache is invalidated only when the raw column string changes, so
in-place mutation of a memoized value would silently corrupt it. Convert the
two remaining in-place mutations to replacement:
- ReactionsList sorts a copy ([...reactions].sort) instead of the field.
- encryption quote-attachment decryption reassigns message.attachments instead
  of pushing into it.

Ref: NATIVE-1346

Claude-Session: https://claude.ai/code/session_01Cj1jHhEbiC9mWGe8dpqQeS
Introduce useMessage, a reactive hook that takes a live WatermelonDB
Message model and returns a plain Message Snapshot via
useSyncExternalStore. It subscribes to model mutations, paints
synchronously on first render with no flash, and reads the memoized
@JSON getters directly so unchanged fields keep their reference for
per-field re-render bailout. All WatermelonDB specifics are confined
to the hook; it is not yet wired into the row container.
Convert MessageContainer from a class to a function component.

- Replace manual experimentalSubscribe + forceUpdate with the reactive
  useMessage snapshot hook; model mutations now re-render through
  useSyncExternalStore.
- Replace shouldComponentUpdate with React.memo and an equivalent custom
  comparator over the same nine props.
- Move isManualUnignored to useState, theming to useTheme, and
  defaultProps to destructuring parameter defaults.
- Keep the debounced onPress as a single lifetime instance via a ref,
  always invoking the latest closure.

Render output is unchanged: the existing Message snapshot suite passes
without updates. Adds a behavior test for the row container.
Move `action` and `selectedMessages` out of the RoomView class state into a
per-Room Zustand store (InteractionStore), mirroring the MessageComposer store
topology. Message rows now read their edit state via a `useIsBeingEdited`
selector instead of a prop, so starting or cancelling an edit re-renders only
the affected row instead of triggering a RoomView render and a FlatList walk.

Composer consumers read the interaction values from the store while keeping
their handlers on RoomContext, so the RoomContext value no longer changes when
interaction state changes. ShareView provides its own store for its composer
subtree. Behavior is unchanged.
Remove the umbrella IMessage/IMessageInner interfaces and the {...props}
bag spread from the message component tree. Each presentational component
now declares the narrow interface it consumes and receives explicit props.

Shared handlers (getCustomEmoji, navToRoomInfo, showAttachment, blockAction,
handleEnterCall, fetchThreadName) are read from MessageContext rather than
threaded as props, and MessageContext is now typed (IMessageContext) in
place of `any`. Behavior is unchanged.
…NATIVE-1351)

Add the `'use memo'` opt-in directive to the three presentational memo
components in Message.tsx (MessageInner, Message, MessageTouchable),
keeping their explicit React.memo gate. This mirrors the existing
precedent in message/index.tsx (MessageContainer) under the project's
React Compiler annotation mode.

Clean Message.stories.tsx to pass only the explicit data props each story
exercises: drop the per-story handler props (onReactionPress, onErrorPress,
replyBroadcast) and the dead `theme` prop, all of which are now supplied
through the MessageContext provider value. Behavior is unchanged — the
snapshot suite passes without updates.
Drop three confirmed-dead members from the RoomView container state: the
unused reaction-modal visibility flag (reactionsModalVisible), the unused
reply-with-mention flag (replyWithMention), and the redundant on-hold flag
(isOnHold).

reactionsModalVisible and replyWithMention were only ever initialized and
type-declared, never read or re-set. isOnHold was set alongside roomUpdate
and only compared in shouldComponentUpdate; that comparison is redundant
because roomUpdate already carries onHold (it is part of roomAttrsUpdate),
so on-hold transitions still trigger a re-render through the existing
roomUpdate diff. No behavior change — the full test suite passes without
snapshot updates.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR moves room interaction state into a Zustand-backed store, adds a per-message store for derived message data, refactors message rendering and attachment components to use typed context/hooks, updates WatermelonDB JSON decorators to memoize sanitization, and switches documentation references to CONTEXT.md.

Changes

InteractionStore and message rendering

Layer / File(s) Summary
InteractionStore and room wiring
app/views/RoomView/index.tsx, app/views/ShareView/index.tsx, app/views/RoomView/{context,definitions,constants}.ts, app/views/RoomView/InteractionStore.tsx
Room interaction state is moved into InteractionStore, and RoomView/ShareView stop carrying action and selectedMessages through RoomContext/state.
MessageComposer migration
app/containers/MessageComposer/**
Composer components, hooks, and tests read interaction state from InteractionStore hooks instead of RoomContext.
Typed MessageContext and MessageStore
app/containers/message/Context.ts, app/containers/message/MessageStore.tsx, app/containers/message/MessageStore.test.tsx, .rnstorybook/preview.tsx
A typed MessageContext is introduced alongside a per-message MessageStore, its derived hooks, and updated preview/test wiring.
Message components and attachments
app/containers/message/{Blocks,CallButton,RepliedThread,MessageAvatar,Broadcast,Discussion,Thread,Time,User,Reactions,Urls,Emoji,Touchable,Content,Message,interfaces,utils}.ts(x), app/containers/message/Components/**, app/lib/encryption/room.ts, app/containers/ReactionsList/index.tsx, tests/stories
Message rendering, right icons, attachments, and supporting helpers switch to store/context-driven data and explicit props, with matching test/story updates.

WatermelonDB JSON Decorator Memoization

Layer / File(s) Summary
Model decorator updates
app/lib/database/model/*.js
Multiple @json decorators now pass { memo: true } to the sanitizer.

Documentation Reference Updates

Layer / File(s) Summary
Docs and glossary updates
CLAUDE.md, CONTEXT.md, app/lib/services/voip/docs/README.md, app/views/RoomView/docs/ARCHITECTURE.md
Documentation references move to CONTEXT.md, and CONTEXT.md adds grouping and interaction/position glossary entries.

Estimated code review effort: 5 (Critical) | ~150 minutes

Possibly related PRs

Suggested labels: type: chore

Suggested reviewers: OtavioStasiak

🚥 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 captures the main change: migrating message rendering to a per-message store.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (9)
  • NATIVE-22: Request failed with status code 401
  • NATIVE-1345: Request failed with status code 401
  • NATIVE-1346: Request failed with status code 401
  • NATIVE-1347: Request failed with status code 401
  • NATIVE-1348: Request failed with status code 401
  • NATIVE-1349: Request failed with status code 401
  • NATIVE-1350: Request failed with status code 401
  • NATIVE-1351: Request failed with status code 401
  • NATIVE-1352: 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.

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
app/lib/database/model/Subscription.js (1)

40-162: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Clone memoized JSON fields before exposing them from asPlain()

@json(..., { memo: true }) returns the same object reference across reads, and asPlain() only creates a shallow snapshot. The JSON-backed fields are still shared by reference, so mutating them through the returned “plain” object mutates the model cache too. Clone or freeze those fields before returning them.

🤖 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/database/model/Subscription.js` around lines 40 - 162, The issue is
that memoized `@json` fields are being exposed by asPlain() by reference, so the
“plain” snapshot still shares mutable objects with the model cache. Update
asPlain() in Subscription to deep-clone or freeze the memoized JSON-backed
properties (those declared with `@json`(..., { memo: true })) before returning
them, so reads from the plain object cannot mutate the cached model state.
app/containers/message/Components/Attachments/Reply.tsx (1)

219-228: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reset loading on preview failures.

If fileDownloadAndPreview() rejects here, setLoading(false) never runs and this reply stays disabled behind the spinner. Wrap the awaited path in try/catch/finally so the UI always recovers and the failure is handled explicitly. As per coding guidelines, "Use explicit error handling with try/catch blocks for async operations." Based on learnings, this repo only treats async event handlers as a review concern when there is a real unhandled promise path.

Suggested fix
 		if (attachment.type === 'file' && attachment.title_link) {
 			setLoading(true);
-			url = formatAttachmentUrl(attachment.title_link, user?.id ?? '', user?.token ?? '', baseUrl ?? '');
-			await fileDownloadAndPreview(url, attachment, id ?? '');
-			setLoading(false);
+			try {
+				url = formatAttachmentUrl(attachment.title_link, user?.id ?? '', user?.token ?? '', baseUrl ?? '');
+				await fileDownloadAndPreview(url, attachment, id ?? '');
+			} catch (e) {
+				// surface/log the failure here
+			} finally {
+				setLoading(false);
+			}
 			return;
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/containers/message/Components/Attachments/Reply.tsx` around lines 219 -
228, In Reply.tsx, the onPress async handler leaves loading stuck true if
fileDownloadAndPreview() rejects, so update the file-download branch in onPress
to use explicit try/catch/finally around the awaited call and always reset
loading in the finally block. Keep the existing attachment.url formatting logic,
but make sure any preview failure is handled in the catch path so the reply UI
can recover instead of staying disabled.

Sources: Coding guidelines, Learnings

🧹 Nitpick comments (4)
app/views/RoomView/InteractionStore.tsx (2)

6-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use interfaces for the store object shapes.

TInteractionActions and InteractionState define object shapes, so they should be interfaces per the TS guideline.

Refactor sketch
-type TInteractionActions = {
+interface IInteractionActions {
 	setEditing(messageId: string): void;
 	initQuote(messageId: string): void;
 	appendQuote(messageId: string): void;
 	removeQuote(messageId: string): void;
 	setReacting(messageId: string): void;
 	setQuotes(messageIds: string[]): void;
 	reset(): void;
-};
+}

-type InteractionState = {
+interface IInteractionState {
 	action: TMessageAction;
 	selectedMessages: string[];
 	// Built once inside the store initializer — stable ref for consumers.
-	actions: TInteractionActions;
-};
+	actions: IInteractionActions;
+}

As per coding guidelines, “Prefer interfaces over type aliases for defining object shapes in TypeScript”.

🤖 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/RoomView/InteractionStore.tsx` around lines 6 - 21, The store
object shapes in TInteractionActions and InteractionState should use interfaces
instead of type aliases. Update the declarations in InteractionStore to convert
both shape definitions to interfaces while keeping the same members and
preserving the stable actions reference used by the store initializer.

Source: Coding guidelines


23-38: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Copy selectedMessages at the store boundary.

initialState.selectedMessages and setQuotes(messageIds) currently retain caller-owned array references. Copy them before storing so external mutations cannot change Zustand state without notifying subscribers.

Proposed fix
-		selectedMessages: initialState?.selectedMessages ?? [],
+		selectedMessages: initialState?.selectedMessages ? [...initialState.selectedMessages] : [],
...
-			setQuotes: messageIds => set({ action: messageIds.length ? 'quote' : null, selectedMessages: messageIds }),
+			setQuotes: messageIds => set({ action: messageIds.length ? 'quote' : null, selectedMessages: [...messageIds] }),
🤖 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/RoomView/InteractionStore.tsx` around lines 23 - 38, The
InteractionStore state is keeping external array references for
selectedMessages, which can let caller mutations bypass Zustand updates. In
createInteractionStore, copy initialState.selectedMessages when initializing
state, and in the setQuotes action copy the incoming messageIds before saving
them. Use the existing createInteractionStore and actions.setQuotes symbols to
locate the boundary where the array should be cloned.
app/containers/MessageComposer/components/CancelEdit.tsx (1)

6-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit return type to CancelEdit.

This changed TSX component still relies on inference. Please annotate the return type explicitly so the exported contract stays obvious.

As per coding guidelines, **/*.{ts,tsx}: 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/containers/MessageComposer/components/CancelEdit.tsx` around lines 6 -
10, The CancelEdit component is still relying on inferred return typing, so make
its exported contract explicit. Update the CancelEdit function declaration to
include an explicit return type, using the function name CancelEdit as the
anchor, while keeping its current use of useRoomContext and useMessageAction
unchanged.

Source: Coding guidelines

app/containers/MessageComposer/hooks/useAutoSaveDraft.ts (1)

9-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the useAutoSaveDraft signature explicit.

text and the returned shape are both inferred here. Please add explicit parameter and return types so this exported hook’s contract is clear at the call site.

As per coding guidelines, **/*.{ts,tsx}: 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/containers/MessageComposer/hooks/useAutoSaveDraft.ts` around lines 9 -
15, The exported hook useAutoSaveDraft currently relies on inference for both
its input and return value, so make the contract explicit by adding a type
annotation for the text parameter and an explicit return type on
useAutoSaveDraft. Keep the existing logic intact, but ensure the returned object
shape from the hook is declared so call sites can rely on the typed API even if
internals change.

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/containers/message/Components/Attachments/Reply.tsx`:
- Line 168: The thumbnail URL fallback in Reply should not interpolate baseUrl
when it may be missing, since that can produce invalid undefined/... paths.
Update the relative-image handling in Reply.tsx to guard the baseUrl-dependent
branch in the same place where image is normalized, so quoted attachment
thumbnails fail closed when MessageContext is partial or default. Use the
existing Reply component’s image assignment logic and preserve the http check,
but only build the relative URL when baseUrl is available.

In `@app/containers/message/index.tsx`:
- Around line 248-253: The link handling in onLinkPress incorrectly treats
external links as message links when message.attachments is undefined because
the optional chain makes the comparison to -1 evaluate true. Update the
isMessageLink check in message/containers/message/index.tsx to only return true
when attachments actually exist and a matching attachment.message_link is found,
so jumpToMessage is only called for real message links and openLink remains the
fallback.
- Around line 69-79: The memo comparator in areEqual is missing item and other
render-consumed props, so it can incorrectly skip updates and leave
MessageContainer rendering stale data. Update areEqual to compare prev.item and
every prop that affects rendering or hook inputs such as useMessage(next.item),
or remove the custom comparator entirely if exhaustive comparison is
impractical. Keep the check aligned with IMessageContainerProps and the values
actually used inside the message container render path.

In `@app/lib/encryption/room.ts`:
- Around line 731-732: The `message.attachments` update in `room.ts` is racing
across concurrent `Promise.all` branches, so later URL decryptions can overwrite
earlier quote attachments. Update the quote-handling flow in the relevant
decrypt/attach logic to collect all new quote attachments first, then assign to
`message.attachments` once after `Promise.all` completes, using the existing
`message.attachments` and quote attachment creation path as the reference
points.

In `@app/views/ShareView/index.tsx`:
- Line 243: ShareView is still using local state as the quote source in send()
and unmount, which can drift from the interactionStore updates made through
setQuotes(selectedMessages). Update ShareView to read the current quote/action
data from interactionStore everywhere it needs quote state, and keep the local
state in sync only if necessary. Use the existing ShareView methods and fields
around send(), componentWillUnmount, this.state.action, and
this.state.selectedMessages to replace stale reads with store-backed values.

---

Outside diff comments:
In `@app/containers/message/Components/Attachments/Reply.tsx`:
- Around line 219-228: In Reply.tsx, the onPress async handler leaves loading
stuck true if fileDownloadAndPreview() rejects, so update the file-download
branch in onPress to use explicit try/catch/finally around the awaited call and
always reset loading in the finally block. Keep the existing attachment.url
formatting logic, but make sure any preview failure is handled in the catch path
so the reply UI can recover instead of staying disabled.

In `@app/lib/database/model/Subscription.js`:
- Around line 40-162: The issue is that memoized `@json` fields are being exposed
by asPlain() by reference, so the “plain” snapshot still shares mutable objects
with the model cache. Update asPlain() in Subscription to deep-clone or freeze
the memoized JSON-backed properties (those declared with `@json`(..., { memo: true
})) before returning them, so reads from the plain object cannot mutate the
cached model state.

---

Nitpick comments:
In `@app/containers/MessageComposer/components/CancelEdit.tsx`:
- Around line 6-10: The CancelEdit component is still relying on inferred return
typing, so make its exported contract explicit. Update the CancelEdit function
declaration to include an explicit return type, using the function name
CancelEdit as the anchor, while keeping its current use of useRoomContext and
useMessageAction unchanged.

In `@app/containers/MessageComposer/hooks/useAutoSaveDraft.ts`:
- Around line 9-15: The exported hook useAutoSaveDraft currently relies on
inference for both its input and return value, so make the contract explicit by
adding a type annotation for the text parameter and an explicit return type on
useAutoSaveDraft. Keep the existing logic intact, but ensure the returned object
shape from the hook is declared so call sites can rely on the typed API even if
internals change.

In `@app/views/RoomView/InteractionStore.tsx`:
- Around line 6-21: The store object shapes in TInteractionActions and
InteractionState should use interfaces instead of type aliases. Update the
declarations in InteractionStore to convert both shape definitions to interfaces
while keeping the same members and preserving the stable actions reference used
by the store initializer.
- Around line 23-38: The InteractionStore state is keeping external array
references for selectedMessages, which can let caller mutations bypass Zustand
updates. In createInteractionStore, copy initialState.selectedMessages when
initializing state, and in the setQuotes action copy the incoming messageIds
before saving them. Use the existing createInteractionStore and
actions.setQuotes symbols to locate the boundary where the array should be
cloned.
🪄 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: 9bcc5d87-b924-4943-af20-abe404e704ac

📥 Commits

Reviewing files that changed from the base of the PR and between 3549de6 and 7738cc3.

⛔ Files ignored due to path filters (1)
  • app/containers/message/__snapshots__/Message.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (64)
  • .rnstorybook/preview.tsx
  • CLAUDE.md
  • CONTEXT.md
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/MessageComposer/components/Quotes/Quotes.tsx
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/containers/ReactionsList/index.tsx
  • app/containers/message/Blocks.ts
  • app/containers/message/Broadcast.tsx
  • app/containers/message/CallButton.tsx
  • app/containers/message/Components/Attachments/AttachedActions.tsx
  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/message/Components/Attachments/Attachments.tsx
  • app/containers/message/Components/Attachments/Audio.tsx
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/containers/message/Components/Attachments/Image/Container.tsx
  • app/containers/message/Components/Attachments/Quote.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/containers/message/Content.test.tsx
  • app/containers/message/Content.tsx
  • app/containers/message/Context.ts
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Message.tsx
  • app/containers/message/MessageAvatar.tsx
  • app/containers/message/Reactions.test.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/RepliedThread.tsx
  • app/containers/message/Thread.tsx
  • app/containers/message/Urls.tsx
  • app/containers/message/User.tsx
  • app/containers/message/hooks/useMediaAutoDownload.tsx
  • app/containers/message/hooks/useMessage.test.ts
  • app/containers/message/hooks/useMessage.ts
  • app/containers/message/hooks/useMessageAccessibilityLabel.test.ts
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/message/index.test.tsx
  • app/containers/message/index.tsx
  • app/containers/message/interfaces.ts
  • app/lib/database/model/CustomEmoji.js
  • app/lib/database/model/Message.js
  • app/lib/database/model/Permission.js
  • app/lib/database/model/Room.js
  • app/lib/database/model/Setting.js
  • app/lib/database/model/Subscription.js
  • app/lib/database/model/Thread.js
  • app/lib/database/model/ThreadMessage.js
  • app/lib/database/model/User.js
  • app/lib/database/model/servers/Server.js
  • app/lib/database/model/servers/User.js
  • app/lib/encryption/room.ts
  • app/lib/services/voip/docs/README.md
  • app/views/RoomView/InteractionStore.tsx
  • app/views/RoomView/constants.ts
  • app/views/RoomView/context.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/docs/ARCHITECTURE.md
  • app/views/RoomView/index.tsx
  • app/views/ShareView/index.tsx
💤 Files with no reviewable changes (2)
  • app/views/RoomView/constants.ts
  • app/views/RoomView/context.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{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/containers/message/Components/Attachments/Audio.tsx
  • app/containers/message/Components/Attachments/Image/Container.tsx
  • app/containers/MessageComposer/components/Quotes/Quotes.tsx
  • app/containers/message/Broadcast.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/lib/database/model/User.js
  • app/containers/message/Thread.tsx
  • app/containers/message/Content.tsx
  • app/containers/message/Components/Attachments/AttachedActions.tsx
  • app/lib/database/model/Permission.js
  • app/containers/message/Urls.tsx
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/lib/database/model/servers/Server.js
  • app/containers/message/Reactions.test.tsx
  • app/containers/ReactionsList/index.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/RepliedThread.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.test.ts
  • app/lib/database/model/CustomEmoji.js
  • app/lib/database/model/servers/User.js
  • app/containers/message/Components/Attachments/Quote.tsx
  • app/containers/message/hooks/useMessage.ts
  • app/containers/message/hooks/useMessage.test.ts
  • app/lib/encryption/room.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/lib/database/model/Setting.js
  • app/containers/message/index.test.tsx
  • app/containers/message/Blocks.ts
  • app/lib/database/model/ThreadMessage.js
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/message/CallButton.tsx
  • app/views/RoomView/InteractionStore.tsx
  • app/containers/message/hooks/useMediaAutoDownload.tsx
  • app/containers/message/Context.ts
  • app/lib/database/model/Message.js
  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/message/Components/Attachments/Attachments.tsx
  • app/lib/database/model/Room.js
  • app/containers/message/MessageAvatar.tsx
  • app/views/ShareView/index.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/message/Content.test.tsx
  • app/lib/database/model/Subscription.js
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/views/RoomView/definitions.ts
  • app/lib/database/model/Thread.js
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/message/User.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Message.tsx
  • app/containers/message/interfaces.ts
  • app/views/RoomView/index.tsx
  • app/containers/message/index.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/containers/message/Components/Attachments/Audio.tsx
  • app/containers/message/Components/Attachments/Image/Container.tsx
  • app/containers/MessageComposer/components/Quotes/Quotes.tsx
  • app/containers/message/Broadcast.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/containers/message/Thread.tsx
  • app/containers/message/Content.tsx
  • app/containers/message/Components/Attachments/AttachedActions.tsx
  • app/containers/message/Urls.tsx
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/containers/message/Reactions.test.tsx
  • app/containers/ReactionsList/index.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/RepliedThread.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.test.ts
  • app/containers/message/Components/Attachments/Quote.tsx
  • app/containers/message/hooks/useMessage.ts
  • app/containers/message/hooks/useMessage.test.ts
  • app/lib/encryption/room.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/message/index.test.tsx
  • app/containers/message/Blocks.ts
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/message/CallButton.tsx
  • app/views/RoomView/InteractionStore.tsx
  • app/containers/message/hooks/useMediaAutoDownload.tsx
  • app/containers/message/Context.ts
  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/message/Components/Attachments/Attachments.tsx
  • app/containers/message/MessageAvatar.tsx
  • app/views/ShareView/index.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/message/Content.test.tsx
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/views/RoomView/definitions.ts
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/message/User.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Message.tsx
  • app/containers/message/interfaces.ts
  • app/views/RoomView/index.tsx
  • app/containers/message/index.tsx
{index.js,app/**/*.{js,jsx,ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the project’s Prettier style: tabs, single quotes, 130-character line width, no trailing commas, omit arrow-function parens when possible, and keep bracket spacing on the same line.

Files:

  • app/containers/message/Components/Attachments/Audio.tsx
  • app/containers/message/Components/Attachments/Image/Container.tsx
  • app/containers/MessageComposer/components/Quotes/Quotes.tsx
  • app/containers/message/Broadcast.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/lib/database/model/User.js
  • app/containers/message/Thread.tsx
  • app/containers/message/Content.tsx
  • app/containers/message/Components/Attachments/AttachedActions.tsx
  • app/lib/database/model/Permission.js
  • app/containers/message/Urls.tsx
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/lib/database/model/servers/Server.js
  • app/containers/message/Reactions.test.tsx
  • app/containers/ReactionsList/index.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/RepliedThread.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.test.ts
  • app/lib/database/model/CustomEmoji.js
  • app/lib/database/model/servers/User.js
  • app/containers/message/Components/Attachments/Quote.tsx
  • app/containers/message/hooks/useMessage.ts
  • app/containers/message/hooks/useMessage.test.ts
  • app/lib/encryption/room.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/lib/database/model/Setting.js
  • app/containers/message/index.test.tsx
  • app/containers/message/Blocks.ts
  • app/lib/database/model/ThreadMessage.js
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/message/CallButton.tsx
  • app/views/RoomView/InteractionStore.tsx
  • app/containers/message/hooks/useMediaAutoDownload.tsx
  • app/containers/message/Context.ts
  • app/lib/database/model/Message.js
  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/message/Components/Attachments/Attachments.tsx
  • app/lib/database/model/Room.js
  • app/containers/message/MessageAvatar.tsx
  • app/views/ShareView/index.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/message/Content.test.tsx
  • app/lib/database/model/Subscription.js
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/views/RoomView/definitions.ts
  • app/lib/database/model/Thread.js
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/message/User.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Message.tsx
  • app/containers/message/interfaces.ts
  • app/views/RoomView/index.tsx
  • app/containers/message/index.tsx
🧠 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/containers/message/Components/Attachments/Audio.tsx
  • app/containers/message/Components/Attachments/Image/Container.tsx
  • app/containers/MessageComposer/components/Quotes/Quotes.tsx
  • app/containers/message/Broadcast.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/containers/message/Thread.tsx
  • app/containers/message/Content.tsx
  • app/containers/message/Components/Attachments/AttachedActions.tsx
  • app/containers/message/Urls.tsx
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/containers/message/Reactions.test.tsx
  • app/containers/ReactionsList/index.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.ts
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/RepliedThread.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.test.ts
  • app/containers/message/Components/Attachments/Quote.tsx
  • app/containers/message/hooks/useMessage.ts
  • app/containers/message/hooks/useMessage.test.ts
  • app/lib/encryption/room.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/message/index.test.tsx
  • app/containers/message/Blocks.ts
  • app/containers/MessageComposer/hooks/useAutoSaveDraft.ts
  • app/containers/message/CallButton.tsx
  • app/views/RoomView/InteractionStore.tsx
  • app/containers/message/hooks/useMediaAutoDownload.tsx
  • app/containers/message/Context.ts
  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/message/Components/Attachments/Attachments.tsx
  • app/containers/message/MessageAvatar.tsx
  • app/views/ShareView/index.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.ts
  • app/containers/message/Content.test.tsx
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/views/RoomView/definitions.ts
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/message/User.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Message.tsx
  • app/containers/message/interfaces.ts
  • app/views/RoomView/index.tsx
  • app/containers/message/index.tsx
📚 Learning: 2026-06-25T18:37:44.793Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.tsx:101-141
Timestamp: 2026-06-25T18:37:44.793Z
Learning: In the Rocket.Chat React Native codebase, do not treat passing an `async` function directly to an event prop in React/React Native UI components (e.g., `onPress={async () => ...}` in TSX) as a “floating promises” CI-blocking lint issue—this repo does not enable the ESLint `no-floating-promises` rule (while `no-void` is enforced). Only raise robustness follow-ups when there are genuinely unhandled promise paths (e.g., fire-and-forget calls like `save()` that return a Promise that is neither awaited nor handled), and prefer making sure failure paths are explicitly handled/reported rather than blocking on lint-style floating-promise concerns.

Applied to files:

  • app/containers/message/Components/Attachments/Audio.tsx
  • app/containers/message/Components/Attachments/Image/Container.tsx
  • app/containers/MessageComposer/components/Quotes/Quotes.tsx
  • app/containers/message/Broadcast.tsx
  • app/containers/message/Components/Attachments/Video.tsx
  • app/containers/message/Thread.tsx
  • app/containers/message/Content.tsx
  • app/containers/message/Components/Attachments/AttachedActions.tsx
  • app/containers/message/Urls.tsx
  • app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
  • app/containers/message/Reactions.test.tsx
  • app/containers/ReactionsList/index.tsx
  • app/containers/MessageComposer/components/CancelEdit.tsx
  • app/containers/message/RepliedThread.tsx
  • app/containers/message/Components/Attachments/Quote.tsx
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/message/index.test.tsx
  • app/containers/message/CallButton.tsx
  • app/views/RoomView/InteractionStore.tsx
  • app/containers/message/hooks/useMediaAutoDownload.tsx
  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/message/Components/Attachments/Attachments.tsx
  • app/containers/message/MessageAvatar.tsx
  • app/views/ShareView/index.tsx
  • app/containers/message/Content.test.tsx
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/MessageComposer.tsx
  • app/containers/message/User.tsx
  • app/containers/message/Reactions.tsx
  • app/containers/message/Components/Attachments/Reply.tsx
  • app/containers/message/Message.stories.tsx
  • app/containers/message/Message.tsx
  • app/views/RoomView/index.tsx
  • app/containers/message/index.tsx
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/containers/message/Reactions.test.tsx
  • app/containers/message/hooks/useMessageAccessibilityLabel.test.ts
  • app/containers/message/hooks/useMessage.test.ts
  • app/containers/MessageComposer/hooks/useChooseMedia.test.tsx
  • app/containers/message/index.test.tsx
  • app/containers/message/Content.test.tsx
  • app/containers/MessageComposer/MessageComposer.test.tsx
📚 Learning: 2026-06-24T22:58:43.390Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7157
File: app/views/MessagesView/index.tsx:392-392
Timestamp: 2026-06-24T22:58:43.390Z
Learning: When wrapping a React Native component (e.g., via `withSafeAreaInsets`) ensure `hoistNonReactStatics` is only required if the wrapped component actually defines static properties/methods that consumers rely on. If the component has no statics (as in `app/views/MessagesView/index.tsx`), you can omit `hoistNonReactStatics` for this case.

Applied to files:

  • app/views/RoomView/InteractionStore.tsx
  • app/views/ShareView/index.tsx
  • app/views/RoomView/index.tsx
📚 Learning: 2026-03-15T13:55:42.038Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6911
File: app/containers/markdown/Markdown.stories.tsx:104-104
Timestamp: 2026-03-15T13:55:42.038Z
Learning: In Rocket.Chat React Native, the markdown parser requires a space between the underscore wrapping italic text and a mention sigil (_ mention _ instead of _mention_). Ensure stories and tests that include italic-wrapped mentions follow this form to guarantee proper parsing. Specifically, for files like app/containers/markdown/Markdown.stories.tsx, and any test/content strings that exercise italic-mentions, use the pattern _ mention _ (with spaces) to prevent the mention from being treated as plain text. Validate any test strings or story content accordingly.

Applied to files:

  • app/containers/message/Components/Attachments/Attachments.stories.tsx
  • app/containers/message/Message.stories.tsx
🔇 Additional comments (51)
CONTEXT.md (1)

55-63: LGTM!

CLAUDE.md (1)

12-12: LGTM!

app/lib/services/voip/docs/README.md (1)

15-15: LGTM!

app/views/RoomView/docs/ARCHITECTURE.md (1)

3-3: LGTM!

app/containers/message/hooks/useMessageAccessibilityLabel.ts (1)

4-27: LGTM!

Also applies to: 29-49

app/containers/message/hooks/useMessageAccessibilityLabel.test.ts (1)

4-11: LGTM!

app/containers/ReactionsList/index.tsx (1)

33-33: LGTM!

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

20-20: LGTM!

app/containers/message/Components/Attachments/AttachedActions.tsx (1)

28-28: LGTM!

app/containers/message/hooks/useMediaAutoDownload.tsx (1)

65-71: 🗄️ Data Integrity & Integration

Empty-string fallbacks need a contract check

Passing '' for missing id, baseUrl, or user into useFile(), formatAttachmentUrl(), and downloadMediaFile() may turn missing context into real cache or network keys. If those helpers don’t treat empty strings as invalid sentinels, this can produce malformed URLs or collisions.

app/containers/message/Components/Attachments/Audio.tsx (1)

26-27: LGTM!

app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx (1)

96-96: LGTM!

Also applies to: 119-119

app/containers/message/Components/Attachments/Image/Container.tsx (1)

55-55: LGTM!

app/containers/message/Components/Attachments/Video.tsx (1)

104-104: LGTM!

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

133-134: LGTM!

app/views/RoomView/InteractionStore.tsx (1)

44-79: LGTM!

app/views/RoomView/definitions.ts (1)

9-9: LGTM!

Also applies to: 63-67

app/views/RoomView/index.tsx (2)

101-101: LGTM!

Also applies to: 146-175, 224-226, 788-846, 848-858, 876-893, 1385-1507, 1642-1719


268-301: 🎯 Functional Correctness

Keep RoomView invalidating on room.onHold changes
renderFooter() still branches on room.onHold; if roomAttrsUpdate no longer includes that field, livechat hold/resume updates will be blocked by shouldComponentUpdate.

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

42-42: LGTM!

Also applies to: 84-94, 392-422

app/containers/MessageComposer/hooks/useChooseMedia.ts (2)

13-13: LGTM!


35-37: 🩺 Stability & Availability

Verify every media-picker composer path is wrapped in InteractionStoreContext. useMessageAction() and useSelectedMessages() crash without it, so any remaining MessageComposerContainer or useChooseMedia entry point outside the provider will fail.

app/containers/MessageComposer/MessageComposer.tsx (1)

8-8: LGTM!

Also applies to: 58-60

app/containers/MessageComposer/components/ComposerInput.tsx (1)

36-36: LGTM!

Also applies to: 54-56

app/containers/MessageComposer/components/Quotes/Quotes.tsx (1)

5-11: LGTM!

app/containers/message/Context.ts (1)

2-39: LGTM!

app/containers/message/interfaces.ts (1)

16-20: LGTM!

Also applies to: 30-66, 95-98

app/containers/message/Blocks.ts (1)

1-11: LGTM!

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

1-1: LGTM!

Also applies to: 11-21

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

1-1: LGTM!

Also applies to: 11-17

app/lib/database/model/ThreadMessage.js (1)

21-81: 🗄️ Data Integrity & Integration

Check WatermelonDB version compatibility
@json(..., { memo: true }) needs @nozbe/watermelondb 0.28.1+; if this repo pins an older release, keep the two-argument form.

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

19-45: LGTM!

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

52-100: LGTM!

app/containers/message/Components/Attachments/Attachments.tsx (1)

17-91: LGTM!

app/lib/database/model/Room.js (1)

11-29: LGTM!

app/lib/database/model/Setting.js (1)

17-17: LGTM!

app/lib/database/model/Thread.js (1)

21-77: 🗄️ Data Integrity & Integration

Thread.asPlain() should not expose memoized JSON fields by reference. If any caller mutates the returned arrays/objects, it will mutate the cached model snapshot in place; clone or freeze these fields before returning them.

app/containers/message/Components/Attachments/Quote.tsx (1)

13-27: LGTM!

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

21-21: LGTM!

Also applies to: 35-46, 78-82

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

56-57: 🎯 Functional Correctness

Normalize ts before passing it to MessageTime.

Widening ts to Date | string is fine, but the cast on Line 122 does not convert the runtime value. If the new string path is exercised, MessageTime still receives a string here. Please either normalize to Date at this boundary or verify that MessageTime now accepts strings end-to-end.

Also applies to: 122-122

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

42-44: 📐 Maintainability & Code Quality

Please type this callback at the boundary instead of casting to Function.

toggleFollowThread as Function removes the call signature exactly where this refactor is trying to tighten contracts. If ThreadDetails and MessageContext drifted during the migration, this cast will hide it until runtime. As per coding guidelines, "Use TypeScript for type safety; add explicit type annotations to function parameters and return types."

Source: Coding guidelines

app/lib/database/model/servers/Server.js (1)

38-38: LGTM!

app/lib/database/model/servers/User.js (1)

23-23: LGTM!

app/containers/message/hooks/useMessage.test.ts (1)

1-127: LGTM!

app/containers/message/index.test.tsx (1)

1-127: LGTM!

app/containers/MessageComposer/MessageComposer.test.tsx (1)

15-16: LGTM!

Also applies to: 119-139, 650-653, 685-687, 719-726, 735-737

app/containers/MessageComposer/hooks/useChooseMedia.test.tsx (1)

21-25: LGTM!

Also applies to: 53-55, 80-81

app/containers/message/Reactions.test.tsx (1)

25-26: LGTM!

Also applies to: 40-43

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

5-6: LGTM!

Also applies to: 67-98, 115-128, 300-300, 331-331, 2247-2265

app/containers/message/hooks/useMessage.ts (1)

109-114: 🩺 Stability & Availability

Investigate stale subscription callbacks after item changes. The callback closes over the subscribed item, so if the old subscription fires after a model swap it can overwrite snapshotRef with stale data.

app/lib/database/model/Message.js (1)

21-87: 🗄️ Data Integrity & Integration

Protect asPlain() from exposing memoized JSON objects. If any consumer mutates these fields in place, memo: true will make those changes stick to the model cache and leak across later snapshots/renders. Clone or freeze the JSON-backed fields before returning them, or keep them immutable end to end.

Comment thread app/containers/message/Components/Attachments/Reply.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
Comment thread app/lib/encryption/room.ts Outdated
await new Promise(resolve => setTimeout(resolve, 100));
this.messageComposerRef.current?.setInput(text);
this.setState({ selectedMessages });
this.interactionStore.getState().actions.setQuotes(selectedMessages);

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use interactionStore as the quote source of truth.

The UI now reads quote state from interactionStore, but send()/unmount still read this.state.action and this.state.selectedMessages. If any child updates the store directly, ShareView can send or restore stale quote data.

Proposed direction
-			this.finishShareView(text, this.state.selectedMessages);
+			this.finishShareView(text, this.interactionStore.getState().selectedMessages);
...
-		const { attachments, room, text, thread, action, selectedMessages } = this.state;
+		const { attachments, room, text, thread } = this.state;
+		const { action, selectedMessages } = this.interactionStore.getState();
...
-		const { selectedMessages } = this.state;
+		const { selectedMessages } = this.interactionStore.getState();

Also applies to: 385-390

🤖 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/ShareView/index.tsx` at line 243, ShareView is still using local
state as the quote source in send() and unmount, which can drift from the
interactionStore updates made through setQuotes(selectedMessages). Update
ShareView to read the current quote/action data from interactionStore everywhere
it needs quote state, and keep the local state in sync only if necessary. Use
the existing ShareView methods and fields around send(), componentWillUnmount,
this.state.action, and this.state.selectedMessages to replace stale reads with
store-backed values.

@github-actions

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.74.0.109241

@github-actions

Copy link
Copy Markdown

@diegolmello diegolmello left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Automated max-effort review — feat: modernize message rendering with reactive hooks

8 findings below as inline comments, ranked by severity.

  • High — the React Compiler silently bails on MessageContainer and useMessage (ref writes during render), so the 'use memo' directives there are no-ops. The new MessageContext value/handlers are recreated every render and the WatermelonDB observer re-subscribes every render, which undercuts the per-field memoization this PR is built around. Verified against the emitted babel output.
  • MediumareEqual compares a non-existent previousItem?._id (dead comparison → stale grouping); onLinkPress mis-routes plain links when attachments is undefined.
  • LowappendQuote lacks dedup; useRef(debounce(...)) re-allocates each render; getMessageTranslation bypasses the snapshot; ShareView keeps selectedMessages in two stores; RepliedThread's mount-only effect.

Findings #2 and #3 are pre-existing (carried from the class component) but re-exposed in the rewritten code. Posted as comments, not change requests.

Comment thread app/containers/message/index.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
actions: {
setEditing: messageId => set({ action: 'edit', selectedMessages: [messageId] }),
initQuote: messageId => set({ action: 'quote', selectedMessages: [messageId] }),
appendQuote: messageId => set(state => ({ selectedMessages: [...state.selectedMessages, messageId] })),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Low] appendQuote can insert duplicate ids.

[...state.selectedMessages, messageId] appends without a membership check. If the same message's quote action fires twice (double-tap, or re-selecting an already-selected message), its id appears twice in selectedMessagesprepareQuoteMessage emits a duplicate quote block on send and a duplicate chip renders.

Fix: no-op when already present, e.g. set(state => state.selectedMessages.includes(messageId) ? state : { selectedMessages: [...state.selectedMessages, messageId] }).

Comment thread app/containers/message/index.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
@@ -382,39 +386,40 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
const { selectedMessages } = this.state;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Low] selectedMessages is held in two places that must be hand-synced.

send() reads this.state.selectedMessages (L252/271) while the composer UI (quote chips) reads the Zustand store via useSelectedMessages(). Every mutation must update both (startShareView L242-243, onRemoveQuoteMessage here L386-389, and the send reset). It is paired correctly today, but it is a fragile invariant: a future edit that updates one and not the other silently diverges what is sent from what the user sees.

Fix: pick one source of truth — read selectedMessages from the store inside send() and drop the state field.

'use memo';

const { colors } = useTheme();
const { fetchThreadName } = useContext(MessageContext);
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);

useEffect(() => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Low] Mount-only effect (empty dependency array, }, []); at L24) closes over the first render's values.

useEffect(() => { if (!msg) fetch(); }, []) runs once and captures the initial tmid, id, fetchThreadName, and msg. If the row's thread metadata changes after mount without remounting (e.g. an encrypted message decrypts and tmsg becomes available), the name is never refetched and the row shows a stale/empty thread name. Low frequency because rows are keyed by message id, but the empty dep array drops values the effect reads.

Fix: include the real dependencies (tmid, id, msg/tmsg) or guard the fetch on the values that should retrigger it.

@diegolmello diegolmello left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Automated two-axis review — /review (Standards + Spec)

Posted as COMMENT (the PR author can't request changes on their own PR). Fixed point origin/develop @ 3549de64b; 65 files, +1613/-886.

🔴 One merge-blocker — see the inline thread on app/containers/message/hooks/useMessage.ts. useMessage calls experimentalSubscribe unconditionally, dropping the old container's if (item && item.experimentalSubscribe) guard. Files / Mentions / Starred / Pinned (MessagesView, REST) and non-encrypted Search (SearchMessagesView, REST) render the row with plain objects → TypeError on mount. This matches the deterministic Android E2E shard-11 failures (message-content-30, starred-messages-view, 3/3 retries). Those two views are unchanged by this PR, so they carry no inline anchor — root cause + fix live on the useMessage.ts line 111 thread.

Standards axis: 3 hard (no-noise comments ×2, 'use memo' on a non-component hook), 3 judgement (Duplicated Code, Middle Man, Speculative Generality).
Spec axis: 1 critical (above); NATIVE-1349 structural AC gaps (no separate compiler-memoized provider component; RoomView/context.ts still room: any, not split into individual fields); NATIVE-1348 mock not at the DB-service boundary; NATIVE-1351 snapshot testID="...-undefined"; scope creep (data fields in MessageContext).

The two axes are reported separately by design and not reranked against each other.

Comment thread app/containers/message/hooks/useMessage.ts Outdated
Comment thread app/containers/message/hooks/useMessage.ts Outdated
Comment thread app/containers/message/index.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
Comment thread app/containers/message/index.tsx Outdated
Comment thread app/views/RoomView/InteractionStore.tsx Outdated
Comment thread app/containers/message/Context.ts Outdated
id?: string;
rid?: string;
user?: { id?: string; username?: string; token?: string };
baseUrl?: string;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Spec axis (b) — scope creep. NATIVE-1350 authorizes moving handlers to MessageContext ("Interaction handlers reach leaves through MessageContext ONLY; handler fields removed from the data interfaces"). Carrying data fields here — baseUrl, user, replies, isEncrypted, e2e, translateLanguage, threadBadgeColor, id, rid — exceeds what any subtask authorizes. Confirm intended, or split data vs handlers.

e2e: undefined,
md: undefined,
comment: undefined,
experimentalSubscribe: jest.fn(() => jest.fn()),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Spec axis (a) — NATIVE-1348 partial. Spec: "model mocked at the DB-service boundary (mock active database + model subscription + snapshot; NO in-memory adapter)." Here experimentalSubscribe is stubbed inline on a POJO — no database.active / repository-level mock. Functional coverage is present, but not at the boundary the AC names.

text={announcement}
bannerClosed={bannerClosed}
closeBanner={this.closeBanner}
<InteractionStoreContext.Provider value={this.interactionStore}>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Spec axis (a) — NATIVE-1349 not met as written. Spec: "Function provider component between the Room view class and the Message list, compiler-memoized, single place where shared context values are built (stable references across Room re-renders)." Here InteractionStoreContext.Provider is inlined in the class render(); the InteractionProvider in InteractionStore.tsx exists but is unused. Also RoomView/context.ts still carries room: any rather than the individual Room-derived fields the AC enumerates. The observable "re-render only the edited row" behavior may still hold via useIsBeingEdited, but the structural AC is unmet — verify and decide fix-now vs follow-up.

"opacity": 1,
}
}
testID="message-editing-undefined"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Spec axis (a) — NATIVE-1351. Spec: "no unintended visual change." The regenerated snapshot adds testID="message-editing-undefined" to the Editing / EditingLargeFont stories — the undefined id means the story context lacks a real message id. Minor, but an unintended diff the AC says shouldn't appear.

Collect quote attachments from Promise.all and assign message.attachments
once after it resolves, instead of racing read-modify-write across the
concurrent decrypt branches (which dropped quotes on multi-quote messages).
Dead export with zero callers; consumers use the narrower hooks or read
getState().actions directly.
…reads

- Guard the model subscription so plain REST objects (search, starred,
  pinned, mentions, files) no longer throw 'experimentalSubscribe is not a
  function' on mount; serve a static snapshot when the model is not observable
- Remove render-body ref writes from MessageContainer and useMessage so the
  React Compiler memoizes both (stable subscribe/getSnapshot, snapshot cached
  by item identity; debounced press handler created once, latest closure via
  effect)
- areEqual compares previousItem.id (was ._id, always undefined on WatermelonDB
  models) and documents why item is intentionally omitted
- isMessageLink no longer treats plain links as message links when attachments
  is undefined
- read the translation from the useMessage snapshot instead of the live model
- drop redundant/historical comments

Adds a useMessage regression test for the non-observable plain-object path.
// useSyncExternalStore resubscribe on item change without any render-body
// ref writes.

const getSnapshot = useCallback((): IMessageSnapshot => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unnecessary if we use use memo

// ref writes.

const getSnapshot = useCallback((): IMessageSnapshot => {
if (!cacheRef.current || cacheRef.current.item !== item) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Are we sure this comparison works?

[item]
);

return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Dup props?


type SnapshotCache = { item: TAnyMessageModel; snapshot: IMessageSnapshot };

export const useMessage = (item: TAnyMessageModel): IMessageSnapshot => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

How could we achieve this without reinventing the wheel? We could use something from wmdb, no?

… store (NATIVE-1376)

The provider already opts into the React Compiler via 'use memo', so the
hand-rolled useCallback on revealIgnored is redundant; inline it and let
the compiler memoize.

Replace the two stale `@ts-ignore TODO` comments with `@ts-expect-error`
carrying the real reason (IMessage types ts as Date, IMessageFromServer as
string; the date op is valid at runtime). @ts-expect-error also fails if the
underlying type mismatch ever goes away, so the suppression cannot rot.
Remove the useContentData store fallback from Quote and Attachments and
pass the message's attachments explicitly from the Message.tsx callers,
which already subscribe to the content bag. This drops the ?? [] laundering
in Reply, where an undefined nested-attachment prop used to fall through to
the current message's store attachments and render the wrong quote.
The store factory left ignoredSeed possibly undefined, failing the tsc
gate against the required boolean in MessageStoreState.
Comment on lines +26 to +28
<Pinned testID={`${msg}-pinned`} />
<Encrypted />
<Edited testID={`${msg}-edited`} />

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Both component could get msg on leaves, making it possible to remove the prop

@@ -25,17 +25,24 @@ import {

type MessageStoreState = {
tick: number;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Why do we need this? Can't we do without it?

Comment thread app/containers/message/Preview.tsx Outdated
Comment on lines +23 to +27
user={user}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji}
rid={message.rid}
timeFormat={Message_TimeFormat}>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Props that come from settings shouldn't be props.

Comment thread app/containers/message/Time.tsx Outdated
}

const MessageTime = ({ timeFormat, style }: IMessageTime) => {
const MessageTime = ({ style }: IMessageTime) => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do we really need style here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should write tests for each component.
We place all tests inside tests folder.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should write stories for all components and move them to stories folder

The MessageContext-to-store refactor moved the jumpToMessage handler into
MessageRoomProvider but did not re-seed it in SearchMessagesView, so tapping
an in-message permalink in search results fell through to openLink (browser)
instead of jumping to the referenced message. Seed jumpToMessage with a
handler that parses the message id from the permalink and jumps within the
searched room, mirroring RoomView.jumpToMessageByUrl.
TMessageProps carried isBeingEdited and small, but neither was ever read:
MessageTouchable computes its own isBeingEdited from the interaction store,
and small was only ever the hardcoded <MessageAvatar small /> literal. Drop
both from the type and stop forwarding them, and the equally-unread
highlighted, down into Message and MessageInner.
The mirroring effect's comment claimed unchanged slices would not trigger a
notify; zustand setState notifies every listener on each render. Re-renders
are avoided at the selector level: stable handler and constant refs keep each
snapshot Object.is-equal. Fix the comment to describe the real mechanism.
…mirroring

Add characterization tests for store behavior introduced by the message-hooks
refactor that previously had no coverage:
- useMessageIgnored/revealIgnored: the isIgnored seed, and that reveal is
  sticky because manualUnignored is never reset.
- InteractionStore.useIsBeingEdited falls back to a module store (returns
  false) outside a provider; the action hooks throw without one.
- MessageRoomProvider mirrors updated props into its store post-mount, so a
  regression in the mirroring effect dependencies would be caught.
…s and stories folders (NATIVE-1393)

Move all presentational leaves into components/, the two zustand stores into stores/, and tests and stories into dedicated __tests__/ and components/stories/ folders. Rename the capital-cased Components/ to lowercase components/. Update all relative imports and the external importers of deep message paths.

Moves only: snapshot content is byte-identical and the public entry app/containers/message is unchanged.
RightIcons: drop the msg prop. Pinned and Edited now self-read the
message text via useMessageText to build their own testIDs, which also
fixes a latent bug where the header path rendered undefined-pinned /
undefined-edited testIDs (User rendered RightIcons without msg).

Time: drop the dead ts and style props (ts is read from the store; no
caller ever passed style).

Preview time format: MessageRoomProvider defaults timeFormat from the
Message_TimeFormat setting when the seed is absent, so RoomView and
Preview stop passing it. MessagesView and SearchMessagesView keep their
explicit full date-time override. Test suites that mount
MessageRoomProvider directly are wrapped in a redux Provider since the
provider now reads the setting.
The per-message store kept an ignoredSeed field mirrored from the
isIgnored prop every render. The seed never changes at runtime, so
move it into the message context value and keep only the mutable
manualUnignored latch in the store. useMessageIgnored now reads the
seed from context and the latch from the store. Behavior is identical.
Add a Storybook story per presentational leaf in the message module
(12 top-level + 6 RightIcons), each seeding the minimal message/room
store state to render a meaningful, deterministic branch. An aggregator
test renders every story to a committed snapshot so each leaf gains a
regression oracle. Hidden/own variants cover the null branches.
Cover the branching hooks and component the presentational stories do
not: useFile (persisted vs forwarded-file state), useMediaAutoDownload
(file-type resolution, cache/auto-download/download and onPress states),
and Attachments/Reply (null gate, file-download vs open-link onPress,
disabled handling, and sub-part conditionals).
Replace Function/any with concrete signatures, drop @ts-ignore/as Function casts now that underlying types align, and use indexed-access types for TInfoMessage.
Comment on lines +10 to +14
const { user, baseUrl, customEmojis } = useAppSelector(state => ({
user: getUserSelector(state),
baseUrl: state.server.server,
customEmojis: state.customEmojis
}));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Message should be able to read these values on leaves

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Wrong place

// refs, so each selector's snapshot stays Object.is-equal and no consumer re-renders.
useEffect(() => {
store.setState(resolvedState);
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No empty arrays on useEffects. We should even add a lint rule.

// The room-scoped fields this store owns. Tests and stories use pickMessageRoomState to
// derive MessageRoomProvider props from a single fixture object. Add a field to
// MessageRoomState → add its key here.
const ROOM_STATE_KEYS: (keyof MessageRoomState)[] = [

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Since we're using zustand, we shouldn't have to have this anymore

if (!ctx) {
throw new Error('Message hooks must be used within a MessageProvider');
}
return ctx;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should make it more similar to useMessageRoomStore.

const { baseUrl, user } = useContext(MessageContext);
const baseUrl = useBaseUrl();
const user = useMessageUser();
const API_Embed = useAppSelector(state => state.settings.API_Embed);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

useSetting

return `${baseUrl}/${_imageUrl}?rc_uid=${user?.id ?? ''}&rc_token=${user?.token ?? ''}`;
}, [url.image, url.url, baseUrl, user?.id, user?.token]);

useEffect(() => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Evaluate how we can improve this. Even if on a new scope.

import { type IRoomInfoParam } from '../../../views/SearchMessagesView';
import { useSetting } from '../../../lib/hooks/useSetting';

export type MessageRoomState = {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Most of this is invalid now, no?

Comment on lines +302 to +306
export const useOnPressRaw = (): MessageStoreState['onPress'] => {
const { store } = useMessageCtx();
return useStore(store, s => s.onPress);
};
export const useOnLongPressRaw = (): MessageStoreState['onLongPress'] => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Remove Raw, no?

};
};

export const useMessagePress = (): (() => void) => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ref logic is weird. We should use useDebounce instead and remove the effect.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109291

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.

1 participant