Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: messaging attachments + consent popup #1256

Merged
merged 7 commits into from
Dec 5, 2024

Conversation

thierryskoda
Copy link
Collaborator

@thierryskoda thierryskoda commented Nov 26, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced the Loader component for enhanced loading indicators.
    • Added AttachmentContainer and AttachmentLoading components for improved attachment handling.
    • Implemented DmConsentPopup and GroupConsentPopup for managing user consent in conversations.
    • Added MessageContainer, MessageContentContainer, and MessageLayout for better message structuring in chat.
    • Enhanced V3Conversation component for improved conversation handling and consent management.
    • Added MessageDateChange, MessageRemoteAttachment, and MessageReply components to enrich message interactions.
  • Bug Fixes

    • Updated various components to ensure they handle required parameters correctly, improving type safety and reducing potential runtime errors.
  • Refactor

    • Streamlined multiple components and hooks for better performance and clarity, including useGroupConsent, useConversationQuery, and useAllowGroupMutation.
    • Improved error handling and logging in various components to enhance observability.
  • Chores

    • Removed deprecated components and functions to clean up the codebase.

@thierryskoda thierryskoda changed the base branch from main to feature/v3-split November 26, 2024 18:02
Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces several significant changes across multiple components in a chat application. Key modifications include the replacement of the ActivityIndicator with a new Loader component, the introduction of new components for managing attachments and consent popups, and the removal of deprecated components. Additionally, there are updates to type definitions and function signatures to enforce stricter type safety, particularly regarding the required ConversationTopic parameter. The changes enhance the overall structure and functionality of the application, improving user experience and maintainability.

Changes

File Path Change Summary
components/ActivityIndicator/ActivityIndicator.tsx Replaced ActivityIndicator with Loader and added deprecation comment.
components/Chat/Attachment/AttachmentMessagePreview.tsx Deleted file containing attachment management components and hooks.
components/Chat/Attachment/attachment-container.tsx Added new AttachmentContainer component for layout and styling of attachments.
components/Chat/Attachment/attachment-loading.tsx Introduced AttachmentLoading component for displaying loading states.
components/Chat/Attachment/remote-attachment-image.tsx Added RemoteAttachmentImage component for displaying remote images with loading and error handling.
components/Chat/ChatGroupUpdatedMessage.tsx Updated content prop to message, added error handling, and introduced MessageDateChange component.
components/Chat/ConsentPopup/ConsentPopup.tsx Deleted consent popup component.
components/Chat/ConsentPopup/GroupConsentPopup.tsx Deleted group consent popup component.
components/Chat/ConsentPopup/consent-popup.design-system.tsx Added new components for consent popup UI.
components/Chat/ConsentPopup/dm-consent-popup.tsx Introduced DmConsentPopup for managing user consent in direct messages.
components/Chat/Message/MessageTail.tsx Deleted message tail component.
components/Chat/Message/MessageTimestamp.tsx Deleted message timestamp component.
components/Chat/Message/V3Message.tsx Refactored V3Message component for improved clarity and maintainability.
components/Chat/Message/components/message-container.tsx Added MessageContainer for structured message display.
components/Chat/Message/components/message-content-container.tsx Added MessageContentContainer for responsive message content layout.
components/Chat/Message/components/message-layout.tsx Added MessageLayout for comprehensive message display structure.
components/Chat/Message/components/message-repliable.tsx Renamed RepliableMessageWrapper to MessageRepliable.
components/Chat/Message/components/message-space-between-messages.tsx Added MessageSpaceBetweenMessages for consistent spacing between messages.
components/Chat/Message/message-content-types/message-remote-attachment.tsx Added MessageRemoteAttachment for handling remote attachment messages.
`components/Chat/Message/message-content-types/message-reply.tsx Added MessageReply for rendering reply messages with improved error handling.
components/Chat/Message/message-content-types/message-simple-text.tsx Added MessageSimpleText for displaying simple text messages.
components/Chat/Message/message-content-types/message-static-attachment.tsx Added MessageStaticAttachment for handling static attachments in messages.
`components/Chat/Message/message-date-change.tsx Added MessageDateChange for displaying date changes in messages.
`components/Chat/Message/message-timestamp.tsx Added MessageTimestamp for displaying message timestamps.
`components/Chat/Message/message-utils.tsx Introduced utility function convertNanosecondsToMilliseconds.
`components/Chat/Message/stores/message-store.tsx Simplified createMessageContextStore by removing DEFAULT_PROPS.
components/Conversation/V3Conversation.tsx Updated conversation handling and consent management logic.
`components/GroupAvatar.tsx Changed topic property in GroupAvatarProps to required type.
`containers/GroupScreenMembersTable.tsx Updated group member management logic to include a group parameter.
custom-eslint-plugin/padding-before-react-hooks.js Modified ESLint rule for blank lines before React hooks.
`design-system/loader.tsx Introduced new Loader component for loading states.
features/conversation-list/hooks/useMessageText.ts Updated import paths for utility functions.
features/conversation-list/useV3ConversationItems.ts Renamed and refactored variables for clarity in conversation filtering logic.
features/conversation/composer/add-attachment-button.tsx Enhanced media handling capabilities in AddAttachmentButton.
`features/conversation/composer/composer.tsx Updated message sending logic in Composer.
`features/conversation/composer/send-attachment-preview.tsx Replaced ActivityIndicator with Loader in SendAttachmentPreview.
`features/conversation/conversation-context.tsx Enhanced conversation consent management and updated types.
features/conversation/conversation-persisted-stores.ts Updated replyingToMessageId type for improved specificity.
features/conversation/conversation-service.ts Enhanced type safety and error handling in conversation service functions.
`features/conversation/dm-conversation.screen.tsx Introduced DmConversationScreen for managing direct messages.
`features/conversations/components/V3ConversationFromPeer.tsx Updated import paths for conversation query hooks.
features/conversations/utils/messageIsFromCurrentUser.ts Updated import paths for utility functions.
`features/search/components/NavigationChatButton.tsx Removed setTimeout for direct navigation to the "Conversation" screen.
hooks/useGroupConsent.ts Simplified useGroupConsent function signature and enhanced error handling.
hooks/useGroupCreator.ts Changed topic parameter to required type.
hooks/useGroupMembers.ts Changed topic parameter to required type.
navigation/useNavigation.tsx Introduced useRouteParams for accessing route parameters.
queries/QueryKeys.ts Added DM_CONSENT entry to QueryKeys enum.
queries/queryClient.ts Disabled retry mechanism for queries.
queries/useAddToGroupMutation.ts Changed topic parameter to required type.
queries/useAllowGroupMutation.ts Introduced new types and improved mutation functionality for group consent management.
queries/useBlockGroupMutation.ts Updated parameter handling for blocking groups.
queries/useConversationQuery.ts Restructured query functions for fetching conversation data.
queries/useConversationWithPeerQuery.ts Introduced new hook for fetching conversation data with a peer.
queries/useDmConsentStateQuery.ts Added hooks and functions for managing DM consent states.
queries/useDmPeerInbox.ts Renamed and refactored functions for DM inbox handling.
queries/useGroupConsentQuery.ts Enhanced type safety and updated function parameters.
queries/useGroupDescriptionMutation.ts Changed topic parameter to required type.
queries/useGroupMembersQuery.ts Changed topic parameter to required type.
queries/useGroupNameQuery.ts Updated import for conversation retrieval.
queries/useGroupPermissionPolicyQuery.ts Changed topic parameter to required type.
queries/useGroupPinnedFrameQuery.ts Changed topic parameter to required type.
queries/useGroupQuery.ts Changed topic parameter to required type.
queries/usePromoteToAdminMutation.ts Changed topic parameter to required type.
queries/usePromoteToSuperAdminMutation.ts Changed topic parameter to required type.
queries/useRemoveFromGroupMutation.ts Changed topic parameter to required type.
queries/useRevokeAdminMutation.ts Changed topic parameter to required type.
queries/useRevokeSuperAdminMutation.ts Changed topic parameter to required type.
screens/ConversationReadOnly.tsx Updated property name for passing message identifiers.
screens/Navigation/Navigation.tsx Introduced DmConversation route for navigation.
screens/NewConversation/NewConversation.tsx Enforced non-null assertion for addingToGroupTopic parameter.
screens/Profile.tsx Enhanced handling of group members and permissions.
utils/attachment/handleStaticAttachment.ts Deleted file containing static attachment handling logic.
utils/capture-error.ts Introduced new functions for structured error handling.
utils/groupUtils/groupActionHandlers.ts Updated consent parameter type for group actions.
utils/xmtpRN/contacts.ts Modified consent function to accept a single argument object.
utils/xmtpRN/conversations.ts Enhanced logging and error handling in conversation retrieval.
queries/MutationKeys.ts Removed allowGroupMutationKey function from MutationKeys enum.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant Loader
    participant MessageService
    participant ConsentService

    User->>App: Request to send message
    App->>Loader: Show loading indicator
    Loader->>App: Loading...
    App->>MessageService: Send message
    MessageService-->>App: Message sent
    App->>Loader: Hide loading indicator
    Loader->>App: Loading complete
Loading

Possibly related PRs

Suggested labels

3.0.0

Suggested reviewers

  • thierryskoda
  • nmalzieu

Poem

🐇 In the meadow, changes bloom,
With Loaders bright, dispelling gloom.
Attachments dance, consent takes flight,
In chat's embrace, all feels right.
So hop along, let messages flow,
In this new world, let friendships grow! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@thierryskoda thierryskoda force-pushed the ts/message-refactoring-attachments branch from 2f8e18d to 7d961f4 Compare November 26, 2024 18:05
Copy link
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: 27

🧹 Outside diff range and nitpick comments (71)
components/ActivityIndicator/ActivityIndicator.tsx (1)

1-1: Consider adding type re-exports for backward compatibility.

Since this is a TypeScript file, consider re-exporting any types that might have been associated with the original ActivityIndicator to ensure a smoother migration path.

import { Loader } from "@design-system/loader";
+import type { LoaderProps } from "@design-system/loader";
+export type ActivityIndicatorProps = LoaderProps;
components/Chat/Attachment/attachment-loading.tsx (2)

5-9: Consider if memoization is necessary.

While memoization is generally good for performance, for such a simple component that only depends on theme colors (which rarely change), it might be unnecessary overhead. However, if this component is used in a performance-critical part of the application with frequent parent re-renders, the memoization is justified.


5-9: Consider adding size customization.

The component could be more reusable if it accepted a size prop to customize the loader dimensions. This would allow it to adapt to different attachment types or sizes.

Here's a suggested implementation:

-export const AttachmentLoading = memo(function AttachmentLoading() {
+type AttachmentLoadingProps = {
+  size?: number;
+};
+
+export const AttachmentLoading = memo(function AttachmentLoading({
+  size = 24, // default size
+}: AttachmentLoadingProps) {
   const { theme } = useAppTheme();
 
-  return <Loader color={theme.colors.text.inverted.primary} />;
+  return <Loader color={theme.colors.text.inverted.primary} size={size} />;
 });
queries/queryClient.ts (1)

10-10: Consider documenting the retry strategy

The current comment is brief. Consider adding more comprehensive documentation about:

  • The rationale behind disabling React Query retries
  • How libXmtp's retry mechanism works
  • Any configuration needed for optimal retry behavior
-      retry: false, // libXmtp handles retries
+      retry: false,
+      // Disabled React Query's retry mechanism as libXmtp handles retries internally.
+      // This prevents duplicate retry attempts and ensures consistent retry behavior
+      // across all XMTP-related queries. See: <link-to-docs> for libXmtp retry configuration
components/Chat/Message/components/message-container.tsx (3)

15-22: Extract styles for better maintainability.

Consider extracting the styles into a separate constant or using a styling system to improve maintainability and reusability.

+const getContainerStyle = (fromMe: boolean) => ({
+  flex: 1,
+  alignItems: "flex-end" as const,
+  justifyContent: fromMe ? "flex-end" : "flex-start" as const,
+});
+
 export const MessageContainer = memo(function MessageContainer(props: {
   children: React.ReactNode;
   fromMe: boolean;
 }) {
   const { children, fromMe } = props;
-  const { theme } = useAppTheme();
 
   return (
     <HStack
-      style={{
-        // ...debugBorder("blue"),
-        flex: 1,
-        alignItems: "flex-end",
-        ...(fromMe
-          ? { justifyContent: "flex-end" }
-          : { justifyContent: "flex-start" }),
-      }}
+      style={getContainerStyle(fromMe)}
     >

16-16: Remove commented debug code.

Remove the commented debug border as it's not needed in production code.

-        // ...debugBorder("blue"),

5-8: Consider adding prop types interface.

For better code organization and reusability, consider extracting the props type into a separate interface.

+interface MessageContainerProps {
+  children: React.ReactNode;
+  fromMe: boolean;
+}
+
-export const MessageContainer = memo(function MessageContainer(props: {
-  children: React.ReactNode;
-  fromMe: boolean;
-}) {
+export const MessageContainer = memo(function MessageContainer(props: MessageContainerProps) {
components/Chat/Attachment/attachment-container.tsx (2)

5-7: Consider creating a specific props interface

The component currently accepts all VStack props through IVStackProps. Consider creating a more specific interface that only exposes the props that make sense for an attachment container.

+interface AttachmentContainerProps {
+  style?: StyleProp<ViewStyle>;
+  children: ReactNode;
+  // Add other specific props needed
+}

-export const AttachmentContainer = memo(function AttachmentContainer(
-  props: IVStackProps
+export const AttachmentContainer = memo(function AttachmentContainer(
+  props: AttachmentContainerProps

1-29: Add accessibility attributes and documentation

As this is a container for attachments, consider the following improvements:

  1. Add proper accessibility role and attributes
  2. Add JSDoc documentation explaining the component's purpose and usage
  3. Consider adding error boundaries for attachment rendering failures
+/**
+ * A container component for chat attachments that provides consistent styling and layout.
+ * Used as a wrapper for various types of attachments in the chat interface.
+ *
+ * @example
+ * <AttachmentContainer>
+ *   <AttachmentContent />
+ * </AttachmentContainer>
+ */
 export const AttachmentContainer = memo(function AttachmentContainer(
   props: IVStackProps
 ) {
   // ...
   return (
     <VStack
+      role="figure"
+      aria-label="Chat attachment"
       style={[
         // ...
       ]}
       {...rest}
     />
   );
 });
components/Chat/Message/message-content-types/message-remote-attachment.tsx (1)

20-28: Consider adding error boundaries for attachment rendering

While the render logic is clean, remote attachments might fail to load or render. Consider implementing error boundaries to gracefully handle such failures.

Example implementation:

import { ErrorBoundary } from '@/components/ErrorBoundary';

// Wrap the RemoteAttachmentImage with an error boundary
<MessageLayout>
  <ErrorBoundary fallback={<AttachmentError messageId={message.id} />}>
    <RemoteAttachmentImage
      messageId={message.id}
      remoteMessageContent={content}
      fitAspectRatio
    />
  </ErrorBoundary>
</MessageLayout>
components/Chat/Message/components/message-content-container.tsx (3)

17-17: Remove commented out code

The commented out code should be removed to maintain clean and maintainable code. If these are debugging aids, consider moving them to a development utility file.

- // ...debugBorder("red"),
  flex: 1,
- // alignSelf: fromMe ? "flex-end" : "flex-start",

Also applies to: 19-19


16-25: Consider extracting styles for better maintainability

The inline styles could be extracted into a separate constant or styled component for better organization and reusability.

const getContainerStyles = (fromMe: boolean, theme: Theme) => ({
  flex: 1,
  alignItems: 'flex-end' as const,
  maxWidth: '75%',
  ...(fromMe
    ? { paddingRight: theme.spacing.sm, justifyContent: 'flex-end' }
    : { paddingLeft: theme.spacing.sm, justifyContent: 'flex-start' }),
});

// Usage in JSX
<HStack style={getContainerStyles(fromMe, theme)}>

21-21: Consider making maxWidth configurable

The hardcoded maxWidth of 75% might not be suitable for all screen sizes or use cases. Consider making this configurable through props or theme variables.

 export const MessageContentContainer = memo(
   function MessageContentContainer(props: {
     children: React.ReactNode;
     fromMe: boolean;
+    maxWidth?: string | number;
   }) {
-    const { children, fromMe } = props;
+    const { children, fromMe, maxWidth = '75%' } = props;
     // ...
           maxWidth: maxWidth,
     // ...
   }
 );
design-system/loader.tsx (2)

4-26: Consider removing the "I" prefix from the interface name.

The type definition is well-documented with clear JSDoc comments. However, the "I" prefix for interface names is not a common convention in modern TypeScript. Consider renaming ILoaderProps to LoaderProps to align with contemporary TypeScript practices.

-type ILoaderProps = {
+type LoaderProps = {

28-39: Consider adding default props for better usability.

While the implementation is clean, consider adding default values for commonly used props to make the component more user-friendly out of the box.

 export const Loader = memo(function Loader(props: ILoaderProps) {
-  const { size, animating, color, hidesWhenStopped, style } = props;
+  const { 
+    size = 'small',
+    animating = true,
+    hidesWhenStopped = true,
+    color,
+    style 
+  } = props;
components/Chat/Message/message-content-types/message-simple-text.tsx (1)

12-14: Consider a more specific component name.

While the current name MessageSimpleText is descriptive, consider renaming it to XMTPSimpleTextMessage to better indicate its specific use with XMTP messages. This would improve clarity and maintainability as the codebase grows.

-export const MessageSimpleText = memo(function MessageSimpleText(props: {
+export const XMTPSimpleTextMessage = memo(function XMTPSimpleTextMessage(props: {
   message: DecodedMessage<[TextCodec]>;
 }) {
custom-eslint-plugin/padding-before-react-hooks.js (1)

47-54: Consider handling additional edge cases

The implementation correctly skips blank line enforcement for hooks at the start of a block. However, it might miss cases where the parent is not a BlockStatement.

Consider this more robust implementation:

-        // Check if the node is the first statement in the function
-        const parentFunction = node.parent;
-        if (
-          parentFunction.type === "BlockStatement" &&
-          parentFunction.body[0] === node
-        ) {
-          return;
-        }
+        // Check if the node is the first statement in its containing block/function
+        const parent = node.parent;
+        if (
+          // Regular block statements (function bodies, if blocks, etc)
+          (parent.type === "BlockStatement" && parent.body[0] === node) ||
+          // Arrow functions without block statements
+          (parent.type === "ArrowFunctionExpression" && parent.body === node)
+        ) {
+          return;
+        }
navigation/useNavigation.tsx (1)

25-27: LGTM! Consider adding JSDoc documentation.

The useRouteParams implementation is clean, type-safe, and follows the single responsibility principle. It provides a convenient way to access route parameters while maintaining type safety.

Consider adding JSDoc documentation to improve developer experience:

+/**
+ * Hook to access the current route parameters with type safety.
+ * @template ScreenName - The screen name from NavigationParamList
+ * @returns The route parameters for the current screen
+ */
export function useRouteParams<ScreenName extends keyof NavigationParamList>() {
  return useRoute<ScreenName>().params;
}
components/Chat/Message/message-date-change.tsx (4)

16-24: Add TypeScript interface for better type safety.

While the component works correctly, adding a TypeScript interface would improve maintainability and type safety.

+interface MessageDateChangeProps {
+  // Add any future props here
+}
+
-export const MessageDateChange = memo(function MessageDateChange() {
+export const MessageDateChange = memo(function MessageDateChange({}: MessageDateChangeProps) {

35-40: Consider using opacity-only animations to prevent layout shifts.

Using display: "none" in animations can cause layout shifts. Consider using only opacity for smoother animations.

 const timeInlineAnimatedStyle = useAnimatedStyle(() => {
   return {
-    display: showTimeAV.value ? "flex" : "none",
     opacity: interpolate(showTimeProgressAV.value, [0, 1], [0, 1]),
   };
 });

42-47: Optimize date calculation by moving it before the early return.

Moving the date calculation before the conditional return would prevent unnecessary calculations when showDateChange is false.

+  const messageDate = getRelativeDate(sentAt);
+
   if (!showDateChange) {
     return null;
   }
-
-  const messageDate = getRelativeDate(sentAt);

48-72: Remove commented debug code.

Clean up the code by removing the commented debug border line.

      style={{
-        // ...debugBorder("red"),
        alignSelf: "center",
        alignItems: "center",
components/Chat/Message/components/message-layout.tsx (3)

17-19: Add JSDoc documentation for the interface.

Consider adding JSDoc documentation to describe the purpose and usage of the IMessageLayoutProps interface.

+/**
+ * Props for the MessageLayout component.
+ * @property {ReactNode} children - The content to be rendered within the message layout.
+ */
 type IMessageLayoutProps = {
   children: ReactNode;
 };

34-36: Add accessibility attributes to the press handler.

The message press functionality should be accessible to screen readers.

 const handlePressMessage = useCallback(() => {
   toggleTime();
-}, [toggleTime]);
+}, [toggleTime]);
+
+const accessibilityProps = {
+  role: "button",
+  accessibilityLabel: "Toggle message timestamp",
+  accessibilityHint: "Double tap to show or hide the message time"
+};

Then update the Pressable component:

-<Pressable onPress={handlePressMessage}>{children}</Pressable>
+<Pressable onPress={handlePressMessage} {...accessibilityProps}>{children}</Pressable>

21-58: Consider wrapping with ErrorBoundary.

The component handles complex UI rendering and external data. Consider wrapping it with an ErrorBoundary to gracefully handle potential runtime errors.

Example implementation:

import { ErrorBoundary } from '@/components/ErrorBoundary';

export function MessageLayout({ children }: IMessageLayoutProps) {
  return (
    <ErrorBoundary fallback={<MessageLayoutFallback />}>
      {/* existing implementation */}
    </ErrorBoundary>
  );
}
features/conversation/composer/send-attachment-preview.tsx (2)

Line range hint 76-84: Consider improvements to the Loader implementation

While the Loader implementation works, there are two potential improvements:

  1. The transform values (-12) are hardcoded and might break with different loader sizes
  2. The color uses a literal hex value instead of the theme system

Consider this more maintainable approach:

 <Loader
   size="small"
-  color="#ffffff"
+  color={theme.colors.text.inverse}
   style={{
     position: "absolute",
     top: "50%",
     left: "50%",
-    transform: [{ translateX: -12 }, { translateY: -12 }],
+    transform: [
+      { translateX: -(theme.spacing.sizes.small / 2) },
+      { translateY: -(theme.spacing.sizes.small / 2) }
+    ],
   }}
 />

Remove unused error prop from SendAttachmentPreview component

The error prop is defined in the component's props interface and destructured in the component parameters, but it's not being used anywhere in the implementation. Remove it to maintain clean and accurate type definitions.

  • Remove error: boolean from SendAttachmentPreviewProps type
  • Remove error from the destructured parameters in the component definition
🔗 Analysis chain

Line range hint 13-19: Verify the unused error prop

The component accepts an error prop in its type definition, but it's not being used in the implementation. Either implement error handling UI or remove the unused prop.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any error handling UI implementations in related files
rg -g '*.tsx' -g '*.ts' 'SendAttachmentPreview.*error' ./features/conversation

Length of output: 80


Script:

#!/bin/bash
# Let's first check the implementation of SendAttachmentPreview component
ast-grep --pattern 'const SendAttachmentPreview = ({ $$$error$$$ }: SendAttachmentPreviewProps) => { $$$ }'

Length of output: 109


Script:

#!/bin/bash
# Let's check the entire content of the file to see the implementation
cat features/conversation/composer/send-attachment-preview.tsx

Length of output: 2669

queries/useConversationQuery.ts (3)

65-65: Remove unnecessary empty line

This empty line doesn't serve any purpose and can be removed to maintain code consistency.

      });
-

      return conversation;

Line range hint 82-89: Enhance error logging strategy

While the crash debug logging is helpful, consider:

  1. Adding structured logging with more context (e.g., account, topic)
  2. Implementing consistent error boundaries for better crash reporting

Consider enhancing the logging:

-      logger.info("[Crash Debug] queryFn fetching group");
+      logger.info("[Crash Debug] queryFn fetching group", {
+        account,
+        topicType: isV3Topic(topic) ? 'v3' : 'v2',
+        hasAccount: !!account,
+        hasTopic: !!topic
+      });

Line range hint 1-116: Consider documenting the sync strategy

With both conversation queries now using includeSync: true, it would be valuable to:

  1. Document the sync strategy in relation to attachments
  2. Consider implementing a configurable sync policy

Consider creating a dedicated configuration for sync strategies:

// syncConfig.ts
export const SYNC_POLICY = {
  CONVERSATIONS: {
    ALWAYS_SYNC: false,
    SYNC_WITH_ATTACHMENTS: true,
    // Add other sync scenarios
  }
} as const;
components/Chat/Message/message-timestamp.tsx (4)

3-3: Remove unused debug import

The debugBorder utility is imported but only used in a commented-out line. Consider removing both the import and the commented code to keep the codebase clean.

-import { debugBorder } from "@/utils/debug-style";

// Later in the code:
-          //   ...debugBorder("yellow"),

Also applies to: 77-77


29-34: Consider adding type safety to animation values

While the animation setup is correct, consider adding TypeScript types for the animation values to improve type safety and maintainability.

-  const showTimeProgressAV = useDerivedValue(() => {
+  const showTimeProgressAV = useDerivedValue<number>(() => {

36-40: Add timestamp validation and improve height fallback handling

Two potential improvements for robustness:

  1. Consider validating the timestamp format before passing it to getLocalizedTime
  2. The textHeight calculation could be more explicit about its fallback mechanism
-  const messageTime = sentAt ? getLocalizedTime(sentAt) : "";
+  const messageTime = sentAt && !isNaN(new Date(sentAt).getTime()) 
+    ? getLocalizedTime(sentAt) 
+    : "";

-  const textHeight = flattenStyles(
+  const textHeight = flattenStyles(
     getTextStyle(themed, { preset: "smaller" })
-  ).lineHeight;
+  ).lineHeight ?? 14; // Explicit fallback

42-66: LGTM! Well-implemented animation styles

The animation implementation is thorough and well-structured, properly handling all transform aspects. Consider adding comments to explain the animation behavior for future maintenance.

   const timeAnimatedStyle = useAnimatedStyle(() => {
+    // Animate height, opacity, and position with spring effect
     return {
features/search/components/NavigationChatButton.tsx (1)

45-50: Remove commented out navigation code.

Instead of keeping the old implementation in comments, rely on version control history to track these changes. This helps maintain cleaner, more maintainable code.

Apply this diff to remove the commented code:

-    // setTimeout(() => {
-    //   navigate("Conversation", {
-    //     mainConversationWithPeer: address,
-    //     focus: true,
-    //   });
-    // }, 300);
components/Chat/Message/components/message-repliable.tsx (3)

Line range hint 7-11: Consider tracking TODOs in issue tracker

These TODOs contain important future improvements that should be tracked:

  1. Migration to ReanimatedSwipeable with Expo SDK 52
  2. Performance optimization using SharedValue
  3. Haptic feedback refinements

Would you like me to create GitHub issues to track these improvements systematically?


Line range hint 71-81: Improve type safety and code maintainability

A few suggestions to enhance the implementation:

  1. Avoid unsafe type assertion with (translation as any)._value
  2. Extract animation threshold values to named constants
  3. Make the swipe threshold calculation more explicit

Consider this refactor:

+ const ANIMATION_THRESHOLD = 0.7;
+ const OPACITY_RANGE = [0, ANIMATION_THRESHOLD, 1];
+ const OPACITY_OUTPUT = [0, 0, 1];

  onSwipeableWillClose={() => {
    const translation = swipeableRef.current?.state.rowTranslation;
-   const translationValue = (translation as any)._value;
+   // TODO: Update type definition to include proper typing for rowTranslation
+   const translationValue = translation?._value ?? 0;
    const swipeDistance = translationValue - dragOffsetFromLeftEdge;
-   if (translation && v > xTresholdToReply) {
+   if (swipeDistance > xTresholdToReply) {
      Haptics.successNotificationAsync();
      onReply();
    }
  }}

Line range hint 44-65: Improve animation code readability

The animation logic is complex and could benefit from better organization:

  1. Extract animation constants
  2. Add comments explaining the animation stages
  3. Consider splitting transform calculations

Consider this refactor:

+ // Animation constants
+ const FADE_RANGE = [0, 0.7, 1];
+ const FADE_OUTPUT = [0, 0, 1];
+ const SCALE_RANGE = [0, 0.7, 1];
+ const SCALE_OUTPUT = [0, 0, 1];
+ 
+ // Helper function to create transform interpolation
+ const createTransform = (progress: Animated.AnimatedInterpolation) => ({
+   scale: progress.interpolate({
+     inputRange: SCALE_RANGE,
+     outputRange: SCALE_OUTPUT,
+     extrapolate: "clamp",
+   })
+ });

  style={{
    height: "100%",
    justifyContent: "center",
    paddingLeft: theme.spacing.sm,
    opacity: progressAV.interpolate({
-     inputRange: [0, 0.7, 1],
-     outputRange: [0, 0, 1],
+     inputRange: FADE_RANGE,
+     outputRange: FADE_OUTPUT,
    }),
-   transform: [
-     {
-       scale: progressAV.interpolate({
-         inputRange: [0, 0.7, 1],
-         outputRange: [0, 0, 1],
-         extrapolate: "clamp",
-       }),
-     },
-     // Remove unnecessary translation that doesn't move
-     {
-       translateX: progressAV.interpolate({
-         inputRange: [0, 0.8, 1],
-         outputRange: [0, 0, 0],
-         extrapolate: "clamp",
-       }),
-     },
-   ],
+   transform: [createTransform(progressAV)],
  }}
features/conversation/conversation-context.tsx (3)

Line range hint 79-93: Add error handling and loading state management

The conversation creation logic needs additional safeguards:

  1. Error handling for failed conversation creation
  2. Loading state management during async operations
  3. Validation of peerAddress format
  4. Protection against race conditions

Consider refactoring to:

 if (!conversation && peerAddress) {
+  try {
+    if (!ethers.utils.isAddress(peerAddress)) {
+      throw new Error('Invalid peer address format');
+    }
     const newConversation = await createConversationByAccount(
       currentAccount,
       peerAddress
     );
     updateNewConversation(newConversation.topic);
     await newConversation.send(payload);
     addConversationToConversationListQuery(
       currentAccount,
       newConversation
     );
+  } catch (error) {
+    console.error('Failed to create conversation:', error);
+    throw new Error('Failed to create conversation');
+  }
   return;
 }

Line range hint 98-122: Refactor message sending logic for better maintainability

The current implementation has several areas for improvement:

  1. Duplicate logic for handling attachments in different contexts
  2. Missing validation for attachments
  3. No error handling for failed message sending

Consider refactoring to:

+ const createMessagePayload = (text?: string, attachment?: RemoteAttachmentContent, referencedMessageId?: string) => {
+   if (referencedMessageId) {
+     return {
+       reply: {
+         reference: referencedMessageId,
+         content: attachment ? { remoteAttachment: attachment } : { text },
+       },
+     };
+   }
+   return attachment ? { remoteAttachment: attachment } : text;
+ };

- if (referencedMessageId) {
-   if (attachment) {
-     await sendCallback({
-       reply: {
-         reference: referencedMessageId,
-         content: { remoteAttachment: attachment },
-       },
-     });
-   }
-   if (text) {
-     await sendCallback({
-       reply: {
-         reference: referencedMessageId,
-         content: { text },
-       },
-     });
-   }
-   return;
- }

- if (attachment) {
-   await sendCallback({
-     remoteAttachment: attachment,
-   });
- }

- if (text) {
-   await sendCallback(text);
- }
+ try {
+   const payload = createMessagePayload(text, attachment, referencedMessageId);
+   if (payload) {
+     await sendCallback(payload);
+   }
+ } catch (error) {
+   console.error('Failed to send message:', error);
+   throw new Error('Failed to send message');
+ }

Line range hint 1-146: Consider architectural improvements for better state management

The current implementation could benefit from:

  1. Implementing React Error Boundaries for handling async operation failures
  2. Moving complex state logic to custom hooks
  3. Adding proper loading states for async operations
  4. Implementing retry mechanisms for failed operations

Consider:

  • Creating a separate useConversationState hook to manage conversation states
  • Implementing a proper error boundary component
  • Adding retry logic for failed operations
  • Using a state machine pattern for managing conversation lifecycle
components/Chat/Message/message-utils.tsx (3)

125-127: Consider using a named constant for the conversion factor.

While the conversion is correct, using a magic number (1000000) makes the code less maintainable. Consider defining it as a named constant at the top of the file.

+const NANOSECONDS_PER_MILLISECOND = 1_000_000;
+
 export function convertNanosecondsToMilliseconds(nanoseconds: number) {
-  return nanoseconds / 1000000;
+  return nanoseconds / NANOSECONDS_PER_MILLISECOND;
 }

Line range hint 18-29: Clean up commented code and address architectural TODO.

  1. The commented-out DecodedMessageAllTypes type should either be:

    • Removed if it's no longer needed
    • Uncommented if it's still required
    • Replaced with an updated version
  2. The TODO comment suggests architectural uncertainty. Consider moving these utility functions to either:

    • @xmtp/react-native-sdk if they're generic enough for the SDK
    • @utils/xmtpRN/messages.ts if they're application-specific

Would you like me to help create a GitHub issue to track this architectural decision?

Also applies to: 31-33


Line range hint 35-93: Restore type safety by replacing 'any' with proper types.

The type guard functions are using any type, which bypasses TypeScript's type checking. The commented-out annotations suggest these should use DecodedMessageWithCodecsType. This change would improve type safety and prevent potential runtime errors.

Apply this pattern to all type guard functions:

 export function isTextMessage(
-  message: any
+  message: DecodedMessageWithCodecsType
 ): message is DecodedMessage<[TextCodec]> {
   return getMessageContentType(message.contentTypeId) === "text";
 }
features/conversation/composer/add-attachment-button.tsx (3)

Line range hint 44-119: Consider extracting magic numbers into constants.

The component implementation looks good, but there are hardcoded values that could be moved to constants for better maintainability.

Consider applying this change:

+const BUTTON_DIMENSIONS = {
+  size: 36, // Value from Figma
+  iconSize: 20, // Value from Figma
+};

 export function AddAttachmentButton() {
   // ...
           style={{
             alignItems: "center",
             justifyContent: "center",
-            height: 36, // Value from Figma
-            width: 36, // Value from Figma
+            height: BUTTON_DIMENSIONS.size,
+            width: BUTTON_DIMENSIONS.size,
             backgroundColor: theme.colors.fill.minimal,
-            borderRadius: 36,
+            borderRadius: BUTTON_DIMENSIONS.size,
           }}
         >
           <Icon
             color={theme.colors.text.secondary}
             icon="plus"
-            size={20} // Value from figma
+            size={BUTTON_DIMENSIONS.iconSize}
           />

Line range hint 121-134: Consider platform-agnostic status bar handling.

The status bar handling is iOS-specific, but wrapping it in a platform check might make the code more maintainable.

Consider this approach:

+const handleStatusBar = (hidden: boolean) => {
+  if (Platform.OS === 'ios') {
+    setStatusBarHidden(hidden, 'fade');
+  }
+};

 async function pickMedia() {
-  if (Platform.OS === "ios") {
-    setStatusBarHidden(true, "fade");
-  }
+  handleStatusBar(true);
   const asset = await pickMediaFromLibrary();
-  if (Platform.OS === "ios") {
-    setStatusBarHidden(false, "fade");
-  }
+  handleStatusBar(false);
   if (!asset) return;
   handleAttachmentSelected(asset);
 }

Line range hint 143-196: Improve error handling and function organization.

The handleAttachmentSelected function is quite long and has nested error handling that could be improved. Consider:

  1. Resetting status on error
  2. Separating concerns into smaller functions
  3. Consistent error handling approach

Here's a suggested refactor:

+async function processAndEncryptAttachment(
+  uri: string,
+  dimensions: { height: number; width: number }
+) {
+  const resizedImage = await compressAndResizeImage(uri);
+  let mimeType = mime.getType(resizedImage.uri);
+  if (!mimeType) {
+    const match = resizedImage.uri.match(DATA_MIMETYPE_REGEX);
+    if (match && match[1]) {
+      mimeType = match[1];
+    }
+  }
+
+  const currentAccount = getCurrentAccount()!;
+  return encryptRemoteAttachment(
+    currentAccount,
+    resizedImage.uri,
+    mimeType || undefined
+  );
+}

 async function handleAttachmentSelected(asset: ImagePicker.ImagePickerAsset) {
   if (asset) {
     try {
       setComposerMediaPreview({
         mediaURI: asset.uri,
         status: "picked",
         mimeType: null,
         dimensions: {
           height: asset.height,
           width: asset.width,
         },
       });

       setComposerMediaPreviewStatus("uploading");
-      const resizedImage = await compressAndResizeImage(asset.uri);
-      // ... encryption logic ...
+      const encryptedAttachment = await processAndEncryptAttachment(
+        asset.uri,
+        { height: asset.height, width: asset.width }
+      );

       try {
         const uploadedAttachment = await uploadRemoteAttachment(
           getCurrentAccount()!,
           encryptedAttachment
         );

         setUploadedRemoteAttachment(uploadedAttachment);
         setComposerMediaPreviewStatus("uploaded");
       } catch (error) {
         sentryTrackMessage("ATTACHMENT_UPLOAD_ERROR", { error });
+        setComposerMediaPreviewStatus("error");
       }
     } catch (error) {
       sentryTrackError(error);
+      setComposerMediaPreviewStatus("error");
     }
   }
 }
components/Chat/ChatGroupUpdatedMessage.tsx (1)

Line range hint 76-141: Consider refactoring duplicate code in member components

ChatGroupMemberLeft and ChatGroupMemberJoined components share significant code. Consider extracting the common logic into a shared component.

Create a shared component:

type ChatGroupMemberActionProps = {
  inboxId: InboxId;
  action: "joined" | "left";
};

function ChatGroupMemberAction({ inboxId, action }: ChatGroupMemberActionProps) {
  // ... shared implementation ...
  return (
    <HStack style={themed($memberContainer)}>
      <Pressable
        style={themed($pressableContent)}
        onPress={() => navigate("Profile", { address: firstSocials.address ?? "" })}
      >
        <Avatar size={theme.avatarSize.xs} uri={avatarUri} name={readableName} />
        <ChatGroupUpdateText weight="bold">{readableName} </ChatGroupUpdateText>
      </Pressable>
      <ChatGroupUpdateText>
        {translate(action === "joined" ? "group_member_joined" : "group_member_left")}
      </ChatGroupUpdateText>
    </HStack>
  );
}
containers/GroupScreenMembersTable.tsx (4)

18-18: Consider grouping related imports together

The imports could be better organized by grouping them into these categories:

  1. React/React Native core
  2. Components/UI
  3. Hooks/Queries
  4. Types
  5. Utils
+ // React/RN core
import { FC, memo, useMemo } from "react";
import { Alert, StyleSheet, Text, View, useColorScheme } from "react-native";

+ // Hooks & Queries
import { useGroupPermissionPolicyQuery } from "@queries/useGroupPermissionPolicyQuery";

+ // Types
import type { GroupWithCodecsType } from "@utils/xmtpRN/client";
import type { ConversationTopic } from "@xmtp/react-native-sdk";

Also applies to: 20-22


Line range hint 115-120: Enhance error handling with specific error messages

The current error handling uses generic "An error occurred" messages across all operations. Consider:

  1. Providing operation-specific error messages
  2. Adding error context to logs
  3. Handling specific error types differently
 try {
   await promoteToAdmin(a.inboxId);
 } catch (e) {
-  logger.error(e);
-  Alert.alert("An error occurred");
+  logger.error('Failed to promote admin', {
+    error: e,
+    userId: a.inboxId,
+    operation: 'promoteToAdmin'
+  });
+  Alert.alert(
+    "Admin Promotion Failed",
+    "Unable to promote member to admin. Please try again later."
+  );
 }

Also applies to: 126-131, 137-142, 148-153, 159-164


Line range hint 52-156: Optimize memoization strategy for better performance

The tableViewItems calculation has a large dependency array which could trigger unnecessary recalculations. Consider:

  1. Breaking down the calculation into smaller memoized parts
  2. Extracting the action sheet handler into a separate memoized callback
+ const getMemberActionHandler = useCallback((
+   member: { inboxId: string, address: string },
+   isSuperAdmin: boolean,
+   isAdmin: boolean,
+   isCurrentUser: boolean,
+   preferredName: string
+ ) => {
+   const {
+     options,
+     cancelButtonIndex,
+     // ... rest of the action sheet options
+   } = getGroupMemberActions(
+     groupPermissionPolicy,
+     isCurrentUser,
+     isSuperAdmin,
+     isAdmin,
+     currentAccountIsSuperAdmin
+   );
+   // ... rest of the action handler
+ }, [groupPermissionPolicy, currentAccountIsSuperAdmin, /* other deps */]);

 const tableViewItems = useMemo(() => {
   const items: TableViewItemType[] = [];
   const groupMembers = sortGroupMembersByAdminStatus(members, currentAccount);
   
   groupMembers.forEach((a) => {
     // ... existing member processing
-    items.push({
+    const item = {
       id: a.inboxId,
       title: `${preferredName}${isCurrentUser ? " (you)" : ""}`,
-      action: () => { ... },
+      action: () => getMemberActionHandler(a, isSuperAdmin, isAdmin, isCurrentUser, preferredName),
       // ... rest of the item
-    });
+    };
+    items.push(item);
   });
   return items;
 }, [/* reduced dependency array */]);

Line range hint 165-182: Add accessibility support for better user experience

The TableView items should include accessibility properties to support screen readers and other assistive technologies.

 const useStyles = () => {
   const colorScheme = useColorScheme();
 
   return StyleSheet.create({
     tableViewRight: {
       flexDirection: "row",
       alignItems: "center",
+      accessibilityRole: "text",
     },
     adminText: {
       fontSize: 17,
       color: textSecondaryColor(colorScheme),
+      accessibilityLabel: "Admin status indicator",
     },
   });
 };

+ // In TableView items
+ rightView: (
+   <View 
+     style={styles.tableViewRight}
+     accessible={true}
+     accessibilityLabel={`${preferredName} ${isSuperAdmin ? 'Super Admin' : isAdmin ? 'Admin' : 'Member'}`}
+   >
components/Conversation/V3Conversation.tsx (1)

190-199: Consider performance optimizations for message rendering

The message rendering implementation looks correct, but there might be room for performance improvements:

  1. Consider implementing windowing/virtualization for better performance with large message lists
  2. The nextMessageId and previousMessageId props provide good context for message grouping
screens/Navigation/Navigation.tsx (2)

1-1: Consider standardizing import path styles.

The imports mix alias paths (@/features/...) with relative paths (../../features/...). Consider standardizing to use alias paths consistently for better maintainability and readability.

Also applies to: 13-25


77-79: Add JSDoc documentation for the DmConversation navigation parameter.

Consider adding documentation to explain the purpose and expected format of peerAddress. This will help other developers understand how to use this navigation route correctly.

Example:

+  /**
+   * Direct message conversation screen
+   * @param peerAddress - The blockchain address or identifier of the peer user
+   */
   DmConversation: {
     peerAddress: string;
   };
utils/xmtpRN/conversations.ts (1)

449-458: Consider extracting timing logic to a utility function

While the logging is valuable, the timing calculation pattern is repeated throughout the file. Consider extracting it to reduce duplication.

+ const measureOperationTime = async (operationName: string, operation: () => Promise<any>) => {
+   const start = new Date().getTime();
+   const result = await operation();
+   const end = new Date().getTime();
+   logger.debug(
+     `[XMTPRN Conversations] ${operationName} took ${(end - start) / 1000} sec`
+   );
+   return result;
+ };

  // Usage example:
- const retryFindStart = new Date().getTime();
- conversation = await client.conversations.findDmByAddress(peer);
- const retryFindEnd = new Date().getTime();
- logger.debug(
-   `[XMTPRN Conversations] Retry find DM took ${
-     (retryFindEnd - retryFindStart) / 1000
-   } sec`
- );
+ conversation = await measureOperationTime(
+   "Retry find DM",
+   () => client.conversations.findDmByAddress(peer)
+ );
components/Chat/Message/V3Message.tsx (2)

80-84: Clean up unused style properties

The style prop in the <VStack> component is currently an empty object with a commented-out line. If the debug styling is no longer needed, consider removing the style prop to simplify the code.

Apply this diff to remove the unused style prop:

-      <VStack
-        style={
-          {
-            // ...debugBorder(),
-          }
-        }
-      >
+      <VStack>

114-133: Remove large block of commented-out code

There is a significant block of commented-out code from lines 114 to 133. If this code is no longer needed, consider removing it to keep the codebase clean and maintainable.

components/Chat/Message/message-content-types/message-reply.tsx (5)

39-42: Implement the handling when replyMessageContent is null

There's a TODO comment indicating that the handling for when replyMessageContent is null is incomplete. Please consider implementing this case to properly manage scenarios where the reply message content is missing.

Would you like assistance in implementing this handling or creating a GitHub issue to track this task?


44-48: Implement the handling when replyMessageContent is a string

There's a TODO comment suggesting rendering a simple bubble message when replyMessageContent is a string. Please consider implementing this to handle messages where the content is plain text.

Would you like help in implementing this or creating a GitHub issue to track this?


60-60: Typo in comment: 'vertial' should be 'vertical'

In the comment, "vertial" should be corrected to "vertical" for clarity.

Apply this diff to fix the typo:

- // Because for reply bubble we want the padding to be same for horizontal and vertial
+ // Because for reply bubble we want the padding to be same for horizontal and vertical

229-229: Typo in comment: 'chached' should be 'cached'

In the comment, "chached" should be corrected to "cached" for clarity.

Apply this diff to fix the typo:

- // Needed that in case we need to see the content of a message that is not in the chached list
+ // Needed that in case we need to see the content of a message that is not in the cached list

186-202: Consider refactoring duplicated code for rendering RemoteAttachmentImage

The code for rendering RemoteAttachmentImage here is similar to the code in the MessageReply component (lines 74-90). Refactoring this into a shared component or utility function can enhance reusability and reduce code duplication.

features/conversation/dm-conversation.screen.tsx (2)

52-52: Avoid redundant double-negation in enabled option

The double negation !!peerAddress is unnecessary because peerAddress will be coerced to a boolean in the context of the enabled option. You can simplify the code by directly passing Boolean(peerAddress) or simply peerAddress.

Apply this diff to simplify the code:

-        enabled: !!peerAddress,
+        enabled: Boolean(peerAddress),

75-75: Simplify conditional rendering without double negation

Using !!conversation in the conditional rendering is unnecessary since conversation will be coerced to a boolean value automatically. Removing the double negation makes the code cleaner.

Apply this diff to simplify the code:

-          {!!conversation ? (
+          {conversation ? (
🧰 Tools
🪛 Biome (1.9.4)

[error] 75-75: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

features/conversation/composer/composer.tsx (6)

Line range hint 77-133: Add error handling for onSend calls

In the send function, calls to onSend are not wrapped in try...catch blocks. If onSend throws an error, it could leave the application in an inconsistent state without proper user feedback. Consider adding error handling to manage potential errors from onSend.

Apply this diff to handle errors:

      // Sending remote attachment
      try {
        await onSend({
          remoteAttachment: uploadedRemoteAttachment,
          ...(replyingToMessageId && {
            referencedMessageId: replyingToMessageId,
          }),
        });
      } catch (error) {
        // Handle error accordingly, e.g., show an error message to the user
        sentryTrackError(error);
      }

      // Sending text message
      try {
        await onSend({
          text: inputValue,
          ...(replyingToMessageId && {
            referencedMessageId: replyingToMessageId,
          }),
        });
      } catch (error) {
        // Handle error accordingly
        sentryTrackError(error);
      }

Line range hint 491-497: Add cleanup function to useEffect to prevent memory leaks

The useEffect hook registers a listener but doesn't clean it up when the component unmounts. This could lead to memory leaks. Return a cleanup function to remove the listener when the component is unmounted.

Apply this diff to fix the issue:

useEffect(() => {
  const unsubscribe = listenToComposerInputValueChange((value, previousValue) => {
    if (previousValue && !value) {
      inputRef.current?.clear();
    }
  });
+  return () => {
+    unsubscribe();
+  };
}, []);

Line range hint 312-416: Refactor ReplyPreviewMessageContent for better maintainability

The ReplyPreviewMessageContent component has multiple if conditions checking for different message types. As the number of message types increases, this can become difficult to maintain. Consider refactoring using a mapping or a switch-case structure to improve readability and scalability.

For example, you could refactor the conditions as:

const messageTypeRenderMap: { [key: string]: JSX.Element } = {
  staticAttachment: <Text>Static attachment</Text>,
  transactionReference: <Text>Transaction</Text>,
  reaction: <Text>Reaction</Text>,
  readReceipt: <Text>Read Receipt</Text>,
  groupUpdated: <Text>Group updates</Text>,
  remoteAttachment: <Text>Remote Attachment</Text>,
  coinbasePayment: <Text>Coinbase Payment</Text>,
};

const messageType = getMessageType(replyMessage);

return messageTypeRenderMap[messageType] || <Text numberOfLines={1}>{clearedMessage}</Text>;

Line range hint 165-184: Ensure accessibility for SendButton component

The SendButton lacks accessibility labels, which might affect users relying on assistive technologies. Add an accessibilityLabel prop to the IconButton to improve accessibility.

Apply this diff to enhance accessibility:

<IconButton
  hitSlop={theme.spacing.xs}
  size="sm"
  onPress={onPress}
  disabled={!canSend}
  iconName="arrow.up"
+ accessibilityLabel="Send Message"
/>

Line range hint 475-507: Handle potential platform-specific issues in onKeyPress event

The onKeyPress event handler checks for event.altKey, event.metaKey, and event.shiftKey, which may not be available on all platforms or in certain environments. Ensure that these properties are checked safely to prevent runtime errors.

Apply this diff to safeguard property access:

onKeyPress={(event: any) => {
  if (Platform.OS !== "web") {
    return;
  }

  const { key, altKey, metaKey, shiftKey } = event.nativeEvent;
  
  if (
    key === "Enter" &&
    !altKey &&
    !metaKey &&
    !shiftKey
  ) {
    event.preventDefault();
    onSubmitEditing();
  }
}}

Line range hint 433-458: Simplify isLandscape calculation

The calculation of isLandscape repeats the dimension checks. Simplify the logic to improve readability.

Apply this diff to refactor the condition:

const { width, height } = mediaPreview?.dimensions || {};

const isLandscape = width && height ? width > height : false;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b46a58a and 7d961f4.

⛔ Files ignored due to path filters (1)
  • ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • components/ActivityIndicator/ActivityIndicator.tsx (1 hunks)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx (0 hunks)
  • components/Chat/Attachment/attachment-container.tsx (1 hunks)
  • components/Chat/Attachment/attachment-loading.tsx (1 hunks)
  • components/Chat/Attachment/remote-attachment-image.tsx (1 hunks)
  • components/Chat/ChatGroupUpdatedMessage.tsx (2 hunks)
  • components/Chat/Message/MessageTail.tsx (0 hunks)
  • components/Chat/Message/MessageTimestamp.tsx (0 hunks)
  • components/Chat/Message/V3Message.tsx (3 hunks)
  • components/Chat/Message/components/message-container.tsx (1 hunks)
  • components/Chat/Message/components/message-content-container.tsx (1 hunks)
  • components/Chat/Message/components/message-layout.tsx (1 hunks)
  • components/Chat/Message/components/message-repliable.tsx (1 hunks)
  • components/Chat/Message/components/message-space-between-messages.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-reply.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-simple-text.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-static-attachment.tsx (1 hunks)
  • components/Chat/Message/message-date-change.tsx (1 hunks)
  • components/Chat/Message/message-timestamp.tsx (1 hunks)
  • components/Chat/Message/message-utils.tsx (1 hunks)
  • components/Chat/Message/stores/message-store.tsx (0 hunks)
  • components/Conversation/V3Conversation.tsx (6 hunks)
  • containers/GroupScreenMembersTable.tsx (1 hunks)
  • custom-eslint-plugin/padding-before-react-hooks.js (1 hunks)
  • design-system/loader.tsx (1 hunks)
  • features/conversation-list/hooks/useMessageText.ts (1 hunks)
  • features/conversation/composer/add-attachment-button.tsx (2 hunks)
  • features/conversation/composer/composer.tsx (9 hunks)
  • features/conversation/composer/send-attachment-preview.tsx (2 hunks)
  • features/conversation/conversation-context.tsx (2 hunks)
  • features/conversation/conversation-service.ts (0 hunks)
  • features/conversation/dm-conversation.screen.tsx (1 hunks)
  • features/conversations/utils/messageIsFromCurrentUser.ts (1 hunks)
  • features/search/components/NavigationChatButton.tsx (1 hunks)
  • navigation/useNavigation.tsx (1 hunks)
  • queries/queryClient.ts (1 hunks)
  • queries/useConversationQuery.ts (1 hunks)
  • queries/useV3ConversationListQuery.ts (1 hunks)
  • screens/Navigation/Navigation.tsx (3 hunks)
  • utils/attachment/handleStaticAttachment.ts (0 hunks)
  • utils/xmtpRN/conversations.ts (3 hunks)
💤 Files with no reviewable changes (6)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx
  • components/Chat/Message/MessageTail.tsx
  • components/Chat/Message/MessageTimestamp.tsx
  • components/Chat/Message/stores/message-store.tsx
  • features/conversation/conversation-service.ts
  • utils/attachment/handleStaticAttachment.ts
✅ Files skipped from review due to trivial changes (2)
  • features/conversation-list/hooks/useMessageText.ts
  • features/conversations/utils/messageIsFromCurrentUser.ts
🧰 Additional context used
🪛 Biome (1.9.4)
features/conversation/dm-conversation.screen.tsx

[error] 75-75: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 143-171: This code is unreachable

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

🔇 Additional comments (38)
components/Chat/Attachment/attachment-loading.tsx (1)

1-3: LGTM! Clean import structure.

The imports are well-organized, using absolute paths and following a logical grouping.

components/Chat/Message/components/message-space-between-messages.tsx (2)

1-3: LGTM! Clean imports following best practices.

The imports are well-organized, using absolute paths and importing only what's needed.


5-10: Verify the spacing value's usage across different screen sizes

The fixed 4xs spacing might need adjustment for different screen sizes or message contexts.

✅ Verification successful

The 4xs spacing is consistently used across message components

The spacing value 4xs is used consistently throughout the Chat message components for small visual separations, including:

  • Message bubble margins
  • Message reactions spacing
  • Date change component gaps
  • Space between messages

The codebase shows a deliberate pattern of using this specific spacing value for micro-level spacing in the message UI, and there's no indication that it needs responsive adjustment as it maintains visual consistency at the micro level.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of this spacing value in message-related components
rg -l "4xs" --type tsx --glob "components/Chat/**/*"

# Look for responsive handling in other message components
ast-grep --pattern 'useBreakpointValue|useResponsiveValue' --lang typescript

Length of output: 159


Script:

#!/bin/bash
# Search for other usages of this spacing value in message-related components
rg -l "4xs" components/Chat/

# Look for responsive handling in message components
rg "useBreakpointValue|useResponsiveValue" components/Chat/

# Check theme spacing usage patterns
rg "theme\.spacing\[" components/Chat/

Length of output: 1060

queries/queryClient.ts (1)

10-10: Verify libXmtp retry mechanism implementation

While disabling React Query's retry mechanism in favor of libXmtp's makes sense to avoid duplicate retry attempts, we should verify that libXmtp properly handles all failure scenarios.

✅ Verification successful

Based on the search results, I can see that libXmtp does implement a comprehensive retry mechanism:

  1. There's a dedicated retryWithBackoff utility (utils/retryWithBackoff.ts) that's used throughout the XMTP integration
  2. The XMTP sync functionality uses this retry mechanism with specific configurations:
    • 5 retries
    • Initial delay of 1000ms
    • Exponential backoff factor of 2
    • Maximum delay of 30000ms (30 seconds)
  3. This retry mechanism is applied to critical operations:
    • Streaming conversations: streamConversations(account)
    • Streaming messages: streamAllMessages(account)
    • Database reconnection: reconnectXmtpClientsDbConnections()

Disabling React Query's retry mechanism is the correct approach

The codebase shows a well-implemented retry strategy in libXmtp that handles various failure scenarios with exponential backoff. Having React Query's retry mechanism enabled would lead to duplicate retry attempts and potential race conditions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for libXmtp retry implementation and configuration
rg -A 5 "retry|reconnect" --type ts

# Look for error handling in libXmtp usage
ast-grep --pattern 'catch ($_) {
  $$$
}'

Length of output: 19938

components/Chat/Attachment/attachment-container.tsx (1)

1-3: Verify VStack usage for attachment container

The component uses VStack for layout. Consider if other layout components like Box or AspectRatio from the design system might be more suitable for an attachment container, especially given the fixed aspect ratio requirement.

✅ Verification successful

Based on the search results, I can now provide a final response regarding the VStack usage in the attachment container:

VStack is the appropriate layout component for the attachment container

The codebase consistently uses VStack for similar container components, particularly in attachment and message-related components. Evidence shows:

  • VStack is used across multiple attachment-related components (remote-attachment-image.tsx, send-attachment-preview.tsx)
  • The component properly handles layout requirements through props as seen in the implementation
  • No alternative layout components (Box, AspectRatio) appear to be used for similar purposes in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check available layout components in design system
fd -e tsx -e ts . | rg "@design-system/(Box|AspectRatio|Container)"

# Check how VStack is used in other attachment-related components
rg -A 3 "VStack.*attachment" 

Length of output: 100


Script:

#!/bin/bash
# Let's try a broader search for design system components and their usage patterns
rg -l "@design-system" --type ts --type tsx

# Search for attachment-related components and their layout patterns
rg -A 5 "export.*Attachment" --type ts --type tsx

# Check VStack usage patterns in the codebase
rg -A 3 "import.*VStack" --type ts --type tsx

Length of output: 227


Script:

#!/bin/bash
# Let's try without file type flags
rg -l "@design-system"

# Search for attachment-related components and their layout patterns
rg -A 5 "export.*Attachment"

# Check VStack usage patterns in the codebase
rg -A 3 "import.*VStack"

Length of output: 39323

components/Chat/Message/message-content-types/message-remote-attachment.tsx (3)

1-8: LGTM! Clean imports and well-defined types

The imports are properly organized and the type definition is clear, utilizing the SDK types appropriately.


10-12: LGTM! Well-structured component declaration

Good use of memo with explicit function naming for better debugging experience.


1-29: Verify integration with the chat message system

As this is a new component for handling remote attachments, let's verify its integration with the chat system.

✅ Verification successful

Based on the gathered information, I can now provide a final response:

Remote attachment handling is properly integrated

The component is well-integrated into the chat system with proper type handling:

  • Used correctly in V3Message component for rendering
  • Type checking implemented via isRemoteAttachmentMessage utility
  • Codec registration present in both Android and iOS native implementations
  • Proper typing with RemoteAttachmentCodec throughout the codebase
  • Integration with the composer component for handling attachments
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how MessageRemoteAttachment is integrated into the chat system

# Check for message type handling registration
rg -A 5 "MessageRemoteAttachment" --type typescript

# Check for RemoteAttachmentCodec usage to ensure consistent typing
rg "RemoteAttachmentCodec" --type typescript

# Look for potential test files
fd "message-remote-attachment.test" --type typescript

Length of output: 398


Script:

#!/bin/bash
# Let's try without type filters since typescript extension isn't recognized
# Check for MessageRemoteAttachment usage
rg "MessageRemoteAttachment"

# Check for RemoteAttachmentCodec usage
rg "RemoteAttachmentCodec"

# Look for message type handling or registration patterns
rg -A 5 "messageTypes|registerMessageType|ContentTypeRemoteAttachment"

# Find test files
fd "test" -e tsx -e ts

Length of output: 3279


Script:

#!/bin/bash
# Let's check the V3Message component to understand how message types are handled
rg -A 10 "V3Message" components/Chat/Message/V3Message.tsx

# Check the message utils implementation for remote attachment handling
rg -A 10 "isRemoteAttachment" components/Chat/Message/message-utils.tsx

# Look at the composer implementation for remote attachments
rg -A 10 "RemoteAttachmentCodec" features/conversation/composer/composer.tsx

Length of output: 1765

components/Chat/Message/components/message-content-container.tsx (2)

5-11: LGTM: Component structure and props look good!

The component is properly memoized and has well-defined props with appropriate types.


5-31: Verify memoization effectiveness

The component is memoized, but since it receives a children prop, the effectiveness of memoization might be limited if the children are frequently changing or creating new references.

✅ Verification successful

Let me analyze the usage of MessageContentContainer based on the output. The search results show that MessageContentContainer is used in message-layout.tsx with children that include V3MessageSenderAvatar when fromMe is false. Let me verify the children prop patterns with a more focused search.


Based on the search results, I can now make a determination about the memoization effectiveness. The MessageContentContainer is used in message-layout.tsx where its children consist of conditionally rendered V3MessageSenderAvatar (when !fromMe) and a Pressable component wrapping the passed children. The children prop changes are controlled and limited to message-specific content.

Memoization is appropriate and effective here

The memoization implementation is justified because:

  1. The component only has two props: fromMe (boolean) and children
  2. The fromMe prop is a primitive boolean that will have referential stability
  3. While it receives children, they are part of a controlled message rendering flow and won't cause unnecessary re-renders
  4. The component handles layout and styling logic that should be memoized to prevent recalculation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of MessageContentContainer to analyze children prop patterns
rg -l "MessageContentContainer" | xargs rg -A 5 "MessageContentContainer"

# Look for potential memo key dependencies
ast-grep --pattern 'memo($$$)'

Length of output: 162549


Script:

#!/bin/bash
# Look for the implementation of V3MessageSenderAvatar and any other children
rg -A 5 "V3MessageSenderAvatar" 

# Search for any other usages of MessageContentContainer
rg -B 3 -A 3 "MessageContentContainer"

Length of output: 5379

design-system/loader.tsx (1)

1-2: LGTM! Clean and minimal imports.

The imports are well-organized and include only the necessary dependencies.

components/Chat/Message/message-content-types/message-simple-text.tsx (3)

1-11: LGTM! Well-organized imports with clear dependencies.

The imports are properly structured and demonstrate good separation of concerns between UI components, state management, and messaging SDK.


19-21: LGTM! Clean state management implementation.

The use of context with selective state extraction demonstrates good performance practices by preventing unnecessary re-renders.


1-35: Verify integration with message type system.

As this is a new message type component, ensure it's properly integrated with the message type registry or factory system.

✅ Verification successful

Let me gather more information about the message type system to verify the integration.


Let me check the main Message component to understand how different message types are handled.


Based on the code analysis, I can now provide the final response:

Integration with message type system is properly implemented

The MessageSimpleText component is correctly integrated within the message type system:

  • It's properly handled in V3Message.tsx through the isTextMessage type guard
  • The component receives the necessary props and context through MessageContextStoreProvider
  • The message type is correctly identified using the getMessageContentType utility
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for message type registration patterns
rg -A 5 "registerMessageType|messageTypeRegistry|MessageType" components/Chat/

# Look for similar message type components
fd "message-.+-text\.tsx?" components/Chat/Message/message-content-types/

Length of output: 226


Script:

#!/bin/bash
# Look for message type handling or registration in the Chat directory
rg -l "type.*Message" components/Chat/

# Check for any message factory or type mapping
rg "export.*type.*Message" components/Chat/

# Look for files that might handle message rendering
fd "message-renderer|message-factory|message-handler" components/Chat/

Length of output: 1178


Script:

#!/bin/bash
# Check the Message component implementation
cat components/Chat/Message/Message.tsx

# Look for message type handling in V3Message
cat components/Chat/Message/V3Message.tsx

# Check message utils for type-related functionality
cat components/Chat/Message/message-utils.tsx

Length of output: 30738

components/Chat/Message/message-date-change.tsx (2)

1-15: LGTM! Well-organized imports.

The imports are properly structured with clear separation between internal components, design system, theme, and utilities.


1-72: Verify integration with MessageLayout component.

The component looks well-implemented, but let's verify its integration with the MessageLayout component.

✅ Verification successful

Integration with MessageLayout and ChatGroupUpdatedMessage is properly implemented

The MessageDateChange component is correctly integrated:

  • In MessageLayout, it's rendered at the top of the message layout structure
  • In ChatGroupUpdatedMessage, it's used within the VStack container for group update notifications
  • Both implementations align with the component's purpose of showing date changes in the message flow
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how MessageDateChange is used in MessageLayout
ast-grep --pattern 'import { MessageDateChange } from "$_"'

# Check the usage pattern
ast-grep --pattern '<MessageDateChange $$$>'

Length of output: 360


Script:

#!/bin/bash
# Let's check how MessageDateChange is used in message-layout.tsx
ast-grep --pattern 'function MessageLayout() {
  $$$
  return (
    $$$
    <MessageDateChange $$$>
    $$$
  )
}'

# Also check the usage in ChatGroupUpdatedMessage
ast-grep --pattern 'function ChatGroupUpdatedMessage() {
  $$$
  return (
    $$$
    <MessageDateChange $$$>
    $$$
  )
}'

Length of output: 243


Script:

#!/bin/bash
# Let's check the content of these files directly
cat components/Chat/Message/components/message-layout.tsx
cat components/Chat/ChatGroupUpdatedMessage.tsx

Length of output: 9290

components/Chat/Message/components/message-layout.tsx (2)

1-15: LGTM! Well-organized imports.

The imports are logically organized and use consistent absolute path format.


21-58: LGTM! Well-structured component implementation.

The component demonstrates:

  • Proper use of hooks and memoization
  • Clean separation of concerns
  • Logical conditional rendering
features/conversation/composer/send-attachment-preview.tsx (1)

1-11: LGTM! Clean import organization

The imports are well-organized and the Loader component is correctly imported from the design system.

queries/useConversationQuery.ts (1)

Line range hint 52-64: Verify performance impact of enabling sync

Setting includeSync: true could impact performance as it may trigger additional data synchronization. Consider:

  1. Adding a comment explaining why sync is required
  2. Evaluating if sync is necessary for all conversation fetches

Let's check for other sync usage patterns:

components/Chat/Message/message-timestamp.tsx (2)

19-28: LGTM! Well-structured component setup

The component is properly memoized and efficiently uses context hooks with selective state extraction.


73-89: Verify the width setting in different layout contexts

The width is set to "100%". Please verify this works correctly in all parent layout contexts, especially in different screen sizes and orientations.

✅ Verification successful

Width setting of 100% is appropriate for the MessageTimestamp component

The width setting of "100%" is correct as the MessageTimestamp is used within a proper layout hierarchy:

  • It's rendered within the MessageLayout component as a standalone element
  • It's positioned between MessageDateChange and MessageRepliable components in a vertical stack
  • The AnimatedVStack with 100% width ensures the timestamp spans the full width of its container while keeping content centered

The layout context is well-defined and the width setting works as intended for the component's purpose.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for parent components that might affect the layout
ast-grep --pattern 'import { MessageTimestamp } from "@/components/Chat/Message/message-timestamp"
$$$
<$_>
  <MessageTimestamp $$$/>
</$_>'

Length of output: 142


Script:

#!/bin/bash
# Let's try a different approach to find MessageTimestamp usage
rg -l "MessageTimestamp" --type tsx

# Then let's look at the content of files that use it
rg "MessageTimestamp" -B 5 -A 5 --type tsx

Length of output: 134


Script:

#!/bin/bash
# Search for MessageTimestamp usage without file type restriction
rg -l "MessageTimestamp"

# Then look at the content of files that use it
rg "MessageTimestamp" -B 5 -A 5

Length of output: 5381

components/Chat/Message/components/message-repliable.tsx (1)

Line range hint 19-23: LGTM! Component rename improves naming consistency

The rename from RepliableMessageWrapper to MessageRepliable follows a better naming convention by leading with the domain name. The use of memo and clean props interface is appropriate.

features/conversation/conversation-context.tsx (1)

Line range hint 134-146: Address TODO and improve type safety for conversation states

The context provider implementation has several areas needing attention:

  1. Incomplete implementation for blocked conversations (TODO comment)
  2. Missing type safety for conversation states
  3. Lack of documentation for context values

Would you like me to help implement the blocked conversation state handling and add proper TypeScript types for the conversation states?

Let's verify the conversation states implementation:

features/conversation/composer/add-attachment-button.tsx (1)

1-14: LGTM! Well-organized imports and type definitions.

The imports are logically grouped and the new utilities for media handling and error tracking enhance the component's capabilities. The removal of the export from SelectedAttachment type is appropriate since it's only used internally.

Also applies to: 38-42

queries/useV3ConversationListQuery.ts (2)

22-22: LGTM! Verify usage in dependent modules.

The export of V3ConversationListType aligns well with the PR objectives for DM conversation improvements. This change enhances type safety across components that need to work with conversation data.

Let's verify the impact on dependent modules:

✅ Verification successful

Export of V3ConversationListType is being used correctly

The type is already being imported and used in features/conversation/dm-conversation.screen.tsx, which aligns with the PR's objectives for DM conversation improvements. No conflicting type definitions were found in the codebase, and the existing usage demonstrates that the export is necessary and properly integrated.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all files that might need updates due to the newly exported type
# Look for files that might have been using a local type definition

# Search for any local declarations of similar types
rg -t typescript "type\s+[^=]*ConversationList[^=]*=" --glob '!queries/useV3ConversationListQuery.ts'

# Search for potential usage of this type
rg -t typescript "ConversationList.*Type" --glob '!queries/useV3ConversationListQuery.ts'

Length of output: 265


Script:

#!/bin/bash
# Let's try without the typescript file type flag since it's not recognized
# Search for any local declarations of similar types
rg "type\s+[^=]*ConversationList[^=]*=" --type-add 'ts:*.{ts,tsx}' --type ts --glob '!queries/useV3ConversationListQuery.ts'

# Search for potential usage of this type
rg "ConversationList.*Type" --type-add 'ts:*.{ts,tsx}' --type ts --glob '!queries/useV3ConversationListQuery.ts'

# Also search for imports from this file to see if the type is already being used
rg "from ['\"]\./queries/useV3ConversationListQuery" --type-add 'ts:*.{ts,tsx}' --type ts

Length of output: 1288


22-22: Consider attachment metadata in conversation list types

Given that this PR introduces messaging attachments, consider whether the ConversationWithCodecsType includes necessary metadata for handling attachments (e.g., attachment counts, types, or preview data).

Let's check the codec type definitions:

components/Chat/ChatGroupUpdatedMessage.tsx (2)

1-1: Verify MessageDateChange component integration

The new MessageDateChange component has been imported but its implementation needs verification to ensure it receives the correct props.

Also applies to: 14-15, 23-23

✅ Verification successful

MessageDateChange component integration is correctly implemented

The verification shows that:

  • The MessageDateChange component is properly implemented in components/Chat/Message/message-date-change.tsx
  • It's being used consistently across the codebase, including in message layouts and group update messages
  • The component is memoized and uses the message context store to access required props (sentAt and showDateChange)
  • Its usage in ChatGroupUpdatedMessage.tsx aligns with the pattern used in other message components
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for MessageDateChange implementation and usage
ast-grep --pattern 'export function MessageDateChange($_) {
  $$$
}'

Length of output: 70


Script:

#!/bin/bash
# Let's try a different approach to find MessageDateChange component
# First, let's check the actual file path
fd "message-date-change.tsx" --type f

# Then search for MessageDateChange usage patterns
rg "MessageDateChange" -A 3

# Also search for export patterns in the file
rg -g "message-date-change*" "export"

Length of output: 3086


44-44: MessageDateChange component might be missing required props

The MessageDateChange component is rendered without any props. Verify if it needs timestamp or other data from the message object.

components/Conversation/V3Conversation.tsx (4)

5-5: LGTM: Import organization is clean and logical

The imports are well-organized and properly grouped by feature/component, following good architectural practices.

Also applies to: 7-7, 13-24


179-181: Props type definition looks good

The transition to using messageIds instead of raw data is a good architectural choice, providing better control over message rendering.


Line range hint 221-231: LGTM: Clean keyboard handling implementation

The KeyboardFiller component is well-implemented with proper handling of keyboard animations and safe area insets.

Let's verify if this component is being reused elsewhere as it's now exported:

✅ Verification successful

Component reusability confirmed: KeyboardFiller is used in multiple locations

The KeyboardFiller component is indeed being reused as intended, specifically in:

  • features/conversation/dm-conversation.screen.tsx
  • components/Conversation/V3Conversation.tsx (its own definition and usage)

This validates the decision to export the component and confirms its reusability across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for KeyboardFiller usage across the codebase
rg "KeyboardFiller" --type typescript

Length of output: 72


Script:

#!/bin/bash
# Search for KeyboardFiller usage with ripgrep, without type filter
rg "KeyboardFiller"

# Also search for potential imports of this component
rg "import.*KeyboardFiller"

Length of output: 364


Line range hint 203-216: Address TODO and commented performance configurations

There are several commented-out performance configurations that should be addressed:

  1. The estimatedItemSize TODO needs to be implemented
  2. The maintainVisibleContentPosition configuration might be important for message list behavior

Let's check if these configurations are used in similar components:

✅ Verification successful

Performance configurations should be implemented based on existing patterns

Based on the codebase analysis:

  1. estimatedItemSize is actively used in other components:

    • ChatDumb.tsx uses estimatedItemSize={34}
    • Chat.tsx uses estimatedItemSize={80}
    • EmojiRowList.tsx uses estimatedItemSize={49}
  2. The maintainVisibleContentPosition comment about Android glitches is consistent with other components:

    • Similar configuration is commented out in Chat.tsx with the same glitch concern
    • Other components also show Android-specific handling for scroll indicators

Recommendations:

  • Implement estimatedItemSize={34} as it matches the existing pattern in ChatDumb.tsx
  • Keep maintainVisibleContentPosition commented out as the Android glitch is a known issue across the codebase
  • Keep the Android-specific scroll indicator handling as it's consistent with the codebase pattern
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for FlatList performance configurations in other components
rg -A 3 "estimatedItemSize|maintainVisibleContentPosition" --type typescript

Length of output: 113


Script:

#!/bin/bash
# Search for FlatList performance configurations in other components, using different file extensions
rg -A 3 "estimatedItemSize|maintainVisibleContentPosition" --type ts --type tsx

# Also search for FlatList usage to understand common patterns
rg -A 3 "FlatList" --type ts --type tsx

Length of output: 177


Script:

#!/bin/bash
# Search for FlatList performance configurations and usage patterns
rg -A 3 "estimatedItemSize|maintainVisibleContentPosition|FlatList"

# Also look for any performance-related comments or TODOs in the conversation components
fd -e tsx -e ts "Conversation" --exec rg -A 3 "TODO|performance|glitch"

Length of output: 18014

screens/Navigation/Navigation.tsx (1)

Line range hint 1-149: Overall changes look good!

The DM conversation feature is well-integrated into the existing navigation structure. The changes maintain the established patterns and hierarchy of the navigation system.

utils/xmtpRN/conversations.ts (3)

428-435: LGTM: Well-structured performance logging

The added debug logging for the initial DM find attempt provides valuable performance metrics with consistent formatting.


462-464: LGTM: Appropriate error logging

Good use of error-level logging to capture failed conversation retrieval after sync, which will help with debugging in production.


484-486: LGTM: Clear total time logging

The modified log message clearly indicates this is the total operation time, which improves clarity for debugging performance issues.

features/conversation/dm-conversation.screen.tsx (1)

280-290: 🛠️ Refactor suggestion

Include topic in useEffect dependency array

In the useDmHeader function, the topic variable is used inside the useEffect, but it's not included in the dependency array. This could lead to the header not updating when topic changes.

Apply this diff to include topic in the dependency array:

   useEffect(() => {
     navigation.setOptions({
       headerTitle: () => <DmConversationTitle topic={topic!} />,
     });
-  }, [navigation]);
+  }, [navigation, topic]);

Likely invalid or redundant comment.

Comment on lines +3 to +6
/**
* @deprecated Use the Loader component from @design-system instead
*/
export default Loader;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Migration to design-system Loader component is incomplete

Several files are still using the deprecated ActivityIndicator component directly:

  • features/conversations/components/V3ConversationFromPeer.tsx
  • screens/Profile.tsx
  • screens/GroupInvite.tsx
  • screens/NewConversation/NewGroupSummary.tsx
  • screens/NewConversation/NewConversation.tsx
  • screens/Navigation/ConversationRequestsListNav.ios.tsx
  • components/Recommendations/Recommendations.tsx
  • components/Onboarding/ConnectViaWallet/ConnectViaWallet.tsx
  • And several other components

The deprecation notice is well-documented, but the actual migration to the new Loader component from @design-system is not complete. These instances need to be updated to use the new component for consistency.

🔗 Analysis chain

LGTM! Well-documented deprecation notice.

The deprecation notice and re-export approach provides a clean migration path while maintaining backward compatibility.

Let's verify the migration status across the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for remaining ActivityIndicator usages and verify Loader adoption

# Check for direct imports of ActivityIndicator
echo "Checking for ActivityIndicator imports..."
rg -l "import.*ActivityIndicator.*from.*components/ActivityIndicator"

# Check for Loader adoption
echo "Checking for Loader adoption..."
rg -l "import.*Loader.*from.*@design-system/loader"

# Look for potential spots that might need migration
echo "Checking for potential ActivityIndicator JSX usage..."
rg "<ActivityIndicator" -A 2

Length of output: 5596

Comment on lines +5 to +10
export const MessageSpaceBetweenMessages = memo(
function MessageSpaceBetweenMessages() {
const { theme } = useAppTheme();
return <VStack style={{ height: theme.spacing["4xs"] }} />;
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add TypeScript types and documentation

While the implementation is clean, it would benefit from better typing and documentation:

+interface MessageSpaceBetweenMessagesProps {
+  /** Optional custom spacing override */
+  spacing?: keyof Theme['spacing'];
+}

+/**
+ * Renders a consistent vertical space between chat messages
+ * using the design system's spacing scale
+ */
 export const MessageSpaceBetweenMessages = memo(
-  function MessageSpaceBetweenMessages() {
+  function MessageSpaceBetweenMessages({ 
+    spacing = "4xs" 
+  }: MessageSpaceBetweenMessagesProps) {
     const { theme } = useAppTheme();
-    return <VStack style={{ height: theme.spacing["4xs"] }} />;
+    return <VStack style={{ height: theme.spacing[spacing] }} />;
   }
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const MessageSpaceBetweenMessages = memo(
function MessageSpaceBetweenMessages() {
const { theme } = useAppTheme();
return <VStack style={{ height: theme.spacing["4xs"] }} />;
}
);
interface MessageSpaceBetweenMessagesProps {
/** Optional custom spacing override */
spacing?: keyof Theme['spacing'];
}
/**
* Renders a consistent vertical space between chat messages
* using the design system's spacing scale
*/
export const MessageSpaceBetweenMessages = memo(
function MessageSpaceBetweenMessages({
spacing = "4xs"
}: MessageSpaceBetweenMessagesProps) {
const { theme } = useAppTheme();
return <VStack style={{ height: theme.spacing[spacing] }} />;
}
);

}) {
const { children, fromMe } = props;

const { theme } = useAppTheme();
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused theme import and hook.

The useAppTheme hook is imported and called, but the theme object is never used in the component.

-import { useAppTheme } from "@/theme/useAppTheme";
-
 export const MessageContainer = memo(function MessageContainer(props: {
   children: React.ReactNode;
   fromMe: boolean;
 }) {
   const { children, fromMe } = props;
-
-  const { theme } = useAppTheme();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { theme } = useAppTheme();
const { children, fromMe } = props;

Comment on lines +15 to +24
{
overflow: "hidden",
borderRadius: theme.borderRadius.sm,
width: "100%",
justifyContent: "center",
alignItems: "center",
backgroundColor: theme.colors.fill.tertiary,
aspectRatio: 1.5, // Default aspect ratio for attachments
},
style,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Consider consistent aspect ratio handling and review tertiary fill usage

  1. The aspect ratio of 1.5 is consistently used across attachment components:

    • attachment-container.tsx
    • remote-attachment-image.tsx
      Making it configurable would require changes in both components for consistency.
  2. The theme.colors.fill.tertiary color is used in a limited set of components:

    • Attachment components (for image backgrounds)
    • BottomSheetHandleBar (for visual decoration)
      This appears to be intentional design system usage for subtle visual elements.
// In both attachment-container.tsx and remote-attachment-image.tsx
+interface AttachmentContainerProps {
+  aspectRatio?: number;
+  // ... other props
+}

 style={[
   {
     overflow: "hidden",
     borderRadius: theme.borderRadius.sm,
     width: "100%",
     justifyContent: "center",
     alignItems: "center",
     backgroundColor: theme.colors.fill.tertiary,
-    aspectRatio: 1.5, // Default aspect ratio for attachments
+    aspectRatio: props.aspectRatio ?? 1.5,
   },
   style,
 ]}
🔗 Analysis chain

Make aspect ratio configurable and verify color usage

Two suggestions for improvement:

  1. The aspect ratio is hardcoded but commented as "default". Consider making it configurable through props.
  2. Verify if theme.colors.fill.tertiary is the correct background color for attachments across different themes.
+interface AttachmentContainerProps {
+  aspectRatio?: number;
+  // ... other props
+}

 style={[
   {
     overflow: "hidden",
     borderRadius: theme.borderRadius.sm,
     width: "100%",
     justifyContent: "center",
     alignItems: "center",
     backgroundColor: theme.colors.fill.tertiary,
-    aspectRatio: 1.5, // Default aspect ratio for attachments
+    aspectRatio: props.aspectRatio ?? 1.5,
   },
   style,
 ]}
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check color usage in other attachment components
rg "fill\.tertiary.*attachment"

# Check if there are any theme-specific overrides for attachments
rg "attachment.*theme" 

Length of output: 58


Script:

#!/bin/bash
# Let's try a broader search for color usage in attachment-related files
fd "attachment" --type f --exec rg "theme\.colors" {} \;

# Search for any files with "attachment" in their name
fd "attachment" --type f

# Look for theme color usage patterns
rg "theme\.colors\.fill\.tertiary" -A 2

Length of output: 2232

Comment on lines +15 to +18
if (typeof content === "string") {
// TODO
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Empty TODO needs clarification or implementation

The string content handling returns null without explanation, and the TODO comment lacks context. This could lead to confusion or missed edge cases.

Please either:

  1. Implement the string content handling, or
  2. Add a comment explaining why string content is ignored and when this case occurs
 if (typeof content === "string") {
-  // TODO
+  // TODO: Handle string content case
+  // This case occurs when [...explain scenario...]
+  // Current behavior: Ignore string content until [...explain future implementation...]
   return null;
 }

Committable suggestion skipped: line range outside the PR's diff.

parseFloat(content.contentLength) <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE
) {
const decryptedLocalAttachment = await fetchAndDecodeRemoteAttachment({
account: getCurrentAccount()!,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle potential null value from getCurrentAccount()

Using the non-null assertion operator ! after getCurrentAccount() can lead to runtime errors if the account is null or undefined. Consider handling the case when no current account is available.

Apply this diff to handle the potential null value:

     const decryptedLocalAttachment = await fetchAndDecodeRemoteAttachment({
-      account: getCurrentAccount()!,
+      const account = getCurrentAccount();
+      if (!account) {
+        throw new Error("Current account is not available.");
+      }
+      account: account,
       messageId: messageId,
       remoteAttachmentContent: content,
     });

Alternatively, adjust the function to manage the absence of a current account appropriately.

Committable suggestion skipped: line range outside the PR's diff.

mutationFn: async (args: {
conversation: ConversationWithCodecsType;
text?: string;
remoteAttachment?: RemoteAttachmentContentt;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in type RemoteAttachmentContentt

The type RemoteAttachmentContentt appears to have a typo. It should likely be RemoteAttachmentContent. This typo can lead to type errors and issues during compilation.

Apply this diff to correct the type:

-            remoteAttachment?: RemoteAttachmentContentt;
+            remoteAttachment?: RemoteAttachmentContent;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
remoteAttachment?: RemoteAttachmentContentt;
remoteAttachment?: RemoteAttachmentContent;

Comment on lines +231 to +236
function useNewConversationHeader(peerAddresss: string) {
const navigation = useRouter();

useEffect(() => {
navigation.setOptions({
headerTitle: () => <NewConversationTitle peerAddress={peerAddresss} />,
});
}, [peerAddresss, navigation]);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in parameter name peerAddresss

There's a typo in the function parameter peerAddresss; it should be peerAddress. This typo can cause bugs due to referencing an undefined variable.

Apply this diff to correct the parameter name and its usage:

-function useNewConversationHeader(peerAddresss: string) {
+function useNewConversationHeader(peerAddress: string) {

   useEffect(() => {
     navigation.setOptions({
-        headerTitle: () => <NewConversationTitle peerAddress={peerAddresss} />,
+        headerTitle: () => <NewConversationTitle peerAddress={peerAddress} />,
     });
-  }, [peerAddresss, navigation]);
+  }, [peerAddress, navigation]);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function useNewConversationHeader(peerAddresss: string) {
const navigation = useRouter();
useEffect(() => {
navigation.setOptions({
headerTitle: () => <NewConversationTitle peerAddress={peerAddresss} />,
});
}, [peerAddresss, navigation]);
}
function useNewConversationHeader(peerAddress: string) {
const navigation = useRouter();
useEffect(() => {
navigation.setOptions({
headerTitle: () => <NewConversationTitle peerAddress={peerAddress} />,
});
}, [peerAddress, navigation]);
}

Comment on lines 141 to 167
return;

if (referencedMessageId) {
if (remoteAttachment) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { remoteAttachment },
},
});
}
if (text) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { text },
},
});
}
return;
}

if (remoteAttachment) {
await conversation.send({
remoteAttachment,
});
}

if (text) {
await conversation.send(text);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unreachable code after return statement

The return; statement at line 141 causes the function to exit early, making the subsequent code unreachable. This means the message sending logic will not execute, and messages will not be sent as intended.

Apply this diff to remove the unintended return; statement:

-            return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return;
if (referencedMessageId) {
if (remoteAttachment) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { remoteAttachment },
},
});
}
if (text) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { text },
},
});
}
return;
}
if (remoteAttachment) {
await conversation.send({
remoteAttachment,
});
}
if (text) {
await conversation.send(text);
}
if (referencedMessageId) {
if (remoteAttachment) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { remoteAttachment },
},
});
}
if (text) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { text },
},
});
}
return;
}
if (remoteAttachment) {
await conversation.send({
remoteAttachment,
});
}
if (text) {
await conversation.send(text);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 143-171: This code is unreachable

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

Comment on lines 67 to 72
export type IComposerSendArgs = {
text?: string;
remoteAttachment?: RemoteAttachmentContent | null;
referencedMessageId?: MessageId | null;
};

type IComposerProps = {
onSend: (args: IComposerSendArgs) => Promise<void>;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure IComposerSendArgs properties are non-nullable where appropriate

In the IComposerSendArgs type, properties like remoteAttachment and referencedMessageId are defined as nullable. If these properties are expected to be either a value or undefined, consider removing the null to enforce stricter type checking.

Apply this diff to update the type definitions:

export type IComposerSendArgs = {
  text?: string;
- remoteAttachment?: RemoteAttachmentContent | null;
- referencedMessageId?: MessageId | null;
+ remoteAttachment?: RemoteAttachmentContent;
+ referencedMessageId?: MessageId;
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type IComposerSendArgs = {
text?: string;
remoteAttachment?: RemoteAttachmentContent | null;
referencedMessageId?: MessageId | null;
};
type IComposerProps = {
onSend: (args: IComposerSendArgs) => Promise<void>;
};
export type IComposerSendArgs = {
text?: string;
remoteAttachment?: RemoteAttachmentContent;
referencedMessageId?: MessageId;
};
type IComposerProps = {
onSend: (args: IComposerSendArgs) => Promise<void>;
};

@thierryskoda thierryskoda marked this pull request as ready for review November 27, 2024 21:18
@thierryskoda thierryskoda requested a review from a team as a code owner November 27, 2024 21:18
@thierryskoda thierryskoda changed the title feat: messaging attachments + new dm convo improvements feat: messaging attachments + consent popup Nov 27, 2024
Copy link
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: 35

🧹 Outside diff range and nitpick comments (134)
components/Chat/Attachment/attachment-loading.tsx (1)

5-9: Consider adding TypeScript types for better type safety.

Since this is a TypeScript file, consider adding proper type definitions to make the component more maintainable and type-safe.

-export const AttachmentLoading = memo(function AttachmentLoading() {
+interface AttachmentLoadingProps {
+  size?: 'small' | 'medium' | 'large';
+}
+
+export const AttachmentLoading = memo(function AttachmentLoading({
+  size = 'medium'
+}: AttachmentLoadingProps) {
   const { theme } = useAppTheme();
 
-  return <Loader color={theme.colors.text.inverted.primary} />;
+  return <Loader 
+    color={theme.colors.text.inverted.primary}
+    size={size}
+  />;
 });
components/Chat/Message/components/message-space-between-messages.tsx (1)

5-10: Add displayName for better debugging

Adding a displayName property will improve component identification in React DevTools and error messages.

 export const MessageSpaceBetweenMessages = memo(
   function MessageSpaceBetweenMessages() {
     const { theme } = useAppTheme();
     return <VStack style={{ height: theme.spacing["4xs"] }} />;
   }
 );
+MessageSpaceBetweenMessages.displayName = 'MessageSpaceBetweenMessages';
queries/queryClient.ts (1)

Line range hint 10-17: Consider granular retry configuration instead of global disable.

Given that this PR introduces new features for attachments and DM conversations, a global retry disable might be too broad. Consider configuring retries per query key or type:

 export const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
-      retry: false, // libXmtp handles retries
+      retry: (failureCount, error) => {
+        // Let libXmtp handle messaging retries
+        if (error instanceof XmtpError) return false;
+        // Allow retries for other queries (e.g., attachment uploads)
+        return failureCount < 3;
+      },
       gcTime: GC_TIME,
       staleTime: STALE_TIME,
       structuralSharing: false,

This approach would:

  • Allow libXmtp to handle messaging-related retries
  • Maintain retry capability for other operations like attachment uploads
  • Provide more resilient error handling for non-messaging features
queries/useGroupPermissionPolicyQuery.ts (1)

Line range hint 12-19: Remove unnecessary non-null assertions and checks.

Since topic is now a required parameter, we can simplify the code by removing redundant checks:

  return useQuery({
-   queryKey: groupPermissionPolicyQueryKey(account, topic!),
+   queryKey: groupPermissionPolicyQueryKey(account, topic),
    queryFn: () => {
      if (!group) {
        return;
      }
      return group.permissionPolicySet();
    },
-   enabled: !!group && !!topic,
+   enabled: !!group,
  });
queries/useGroupPinnedFrameQuery.ts (3)

Line range hint 15-17: Remove unnecessary null check for topic

Since topic is now a required parameter (not optional), the !topic check is redundant and creates dead code. This condition can never be true.

Apply this diff to fix the issue:

    queryFn: async () => {
-     if (!group || !topic) {
+     if (!group) {
        return;
      }

Line range hint 13-13: Remove unnecessary non-null assertion

Since topic is now a required parameter, the non-null assertion (topic!) in groupPinnedFrameQueryKey is unnecessary.

Apply this diff:

-   queryKey: groupPinnedFrameQueryKey(account, topic!),
+   queryKey: groupPinnedFrameQueryKey(account, topic),

Line range hint 20-20: Verify enabled condition

The enabled condition still checks for !!topic which is redundant since topic is now required and will always be truthy.

Apply this diff:

-   enabled: !!group && !!topic,
+   enabled: !!group,
components/Chat/Message/components/message-container.tsx (2)

16-16: Remove commented debug code.

Remove the commented debug border line as it's not needed in production code.

     style={{
-      // ...debugBorder("blue"),
       flex: 1,

15-23: Optimize style object construction.

The conditional style object can be simplified using template literals for better readability and maintenance.

     style={{
       flex: 1,
       alignItems: "flex-end",
-      ...(fromMe
-        ? { justifyContent: "flex-end" }
-        : { justifyContent: "flex-start" }),
+      justifyContent: fromMe ? "flex-end" : "flex-start",
     }}
queries/useGroupQuery.ts (1)

Line range hint 14-24: Remove redundant null checks and type assertions

Since topic is now a required parameter (not optional), we can simplify the code by removing unnecessary null checks and type assertions:

  return useQuery({
    ...options,
-   queryKey: conversationQueryKey(account, topic!),
+   queryKey: conversationQueryKey(account, topic),
    queryFn: async () => {
-     if (!topic) {
-       return null;
-     }
      return getGroupByTopicByAccount({
        account,
        topic,
        includeSync,
      });
    },
-   enabled: !!topic,
+   enabled: true,
  });

These changes:

  1. Remove unnecessary non-null assertion (!) in queryKey
  2. Remove redundant null check in queryFn
  3. Simplify enabled condition since topic is always defined
components/Chat/Attachment/attachment-container.tsx (2)

1-7: Consider creating a specific props interface for AttachmentContainer

While using IVStackProps works, creating a dedicated interface would make the component's API more explicit and maintainable.

+interface AttachmentContainerProps extends IVStackProps {
+  // Add specific props here if needed in the future
+  aspectRatio?: number;
+}

-export const AttachmentContainer = memo(function AttachmentContainer(
-  props: IVStackProps
+export const AttachmentContainer = memo(function AttachmentContainer(
+  props: AttachmentContainerProps

1-29: Consider documenting the component's role in the attachment system

This component seems to be a foundational building block for attachments. Consider adding JSDoc comments to document its purpose, usage examples, and relationship with other attachment-related components.

+/**
+ * A container component for chat attachments that provides consistent styling and layout.
+ * Used as a base component for various types of attachments in the chat interface.
+ *
+ * @example
+ * <AttachmentContainer>
+ *   <AttachmentContent />
+ * </AttachmentContainer>
+ */
 export const AttachmentContainer = memo(function AttachmentContainer(
utils/capture-error.ts (2)

6-12: Consider enhancing type safety and error handling

While the basic implementation is functional, consider these improvements:

  1. Add type narrowing for better error handling
  2. Add error transformation/sanitization before sending to Sentry

Consider this enhanced implementation:

-export function captureError(error: unknown) {
+export function captureError(error: unknown) {
+  // Narrow the error type
+  const errorObject = error instanceof Error 
+    ? error 
+    : new Error(String(error));
+
   if (config.env === "dev") {
-    logger.error(error);
+    logger.error(errorObject);
   }
 
-  sentryTrackError(error);
+  // Sanitize sensitive information if needed
+  const sanitizedError = sanitizeError(errorObject);
+  sentryTrackError(sanitizedError);
 }
+
+function sanitizeError(error: Error): Error {
+  // Add your sanitization logic here
+  return error;
+}

29-33: Consider enhancing user feedback and i18n support

The generic error message might not provide enough context to users. Consider:

  1. Adding i18n support for the error message
  2. Providing more context-specific friendly messages

Consider this enhanced implementation:

+import { t } from '@/utils/i18n';
+
+type ErrorContext = 'upload' | 'network' | 'permission' | 'default';
+
 export function captureErrorWithFriendlyToast(
-  error: unknown
+  error: unknown,
+  context: ErrorContext = 'default'
 ) {
+  const friendlyMessages = {
+    upload: t('errors.upload_failed'),
+    network: t('errors.network_error'),
+    permission: t('errors.permission_denied'),
+    default: t('errors.generic_error')
+  };
+
   captureErrorWithToast(error, {
-    message: "Something went wrong",
+    message: friendlyMessages[context],
   });
 }
components/Chat/Message/message-content-types/message-remote-attachment.tsx (2)

10-13: LGTM! Well-structured component declaration with performance optimization

Good use of memo with a named function for better debugging experience.

Consider adding JSDoc comments to document the component's purpose:

+/**
+ * Renders a remote attachment message with support for images
+ * @param {IMessageRemoteAttachmentProps} props - Component props
+ */
export const MessageRemoteAttachment = memo(function MessageRemoteAttachment({

20-28: Consider adding error handling for failed attachments

While the render logic is clean, there's no visible error handling for cases where the attachment fails to load.

Consider adding error boundaries or fallback UI:

 <MessageLayout>
   <RemoteAttachmentImage
     messageId={message.id}
     remoteMessageContent={content}
     fitAspectRatio
+    onError={(error) => {
+      // Handle error case
+      return <AttachmentError error={error} />;
+    }}
   />
 </MessageLayout>
components/Chat/Message/components/message-content-container.tsx (2)

5-9: Consider extracting props interface

For better reusability and documentation, consider extracting the props interface:

+interface MessageContentContainerProps {
+  children: React.ReactNode;
+  fromMe: boolean;
+}
+
 export const MessageContentContainer = memo(
-  function MessageContentContainer(props: {
-    children: React.ReactNode;
-    fromMe: boolean;
-  }) {
+  function MessageContentContainer(props: MessageContentContainerProps) {

17-17: Remove commented out code

Clean up the codebase by removing commented-out code. If these are debugging helpers, consider moving them to a development utilities file.

-          // ...debugBorder("red"),
           flex: 1,
-          // alignSelf: fromMe ? "flex-end" : "flex-start",

Also applies to: 19-19

queries/useDmPeerInbox.ts (1)

Line range hint 14-27: Consider enhancing error messages and type assertions

While the implementation is solid, we could improve the error handling and type safety:

Consider applying these improvements:

 export const useDmPeerInboxId = (account: string, topic: ConversationTopic) => {
   const { data: conversation } = useConversationQuery(account, topic);
   return useQuery({
     queryKey: dmPeerInboxIdQueryKey(account, topic),
     queryFn: () => {
       if (!conversation) {
-        throw new Error("Conversation not found");
+        throw new Error(`Conversation not found for account ${account} and topic ${topic}`);
       }
       if (conversation.version !== ConversationVersion.DM) {
-        throw new Error("Conversation is not a DM");
+        throw new Error(`Invalid conversation version: expected ${ConversationVersion.DM}, got ${conversation.version}`);
       }
       return getDmPeerInbox(conversation);
     },
     enabled: !!conversation && conversation.version === ConversationVersion.DM,
   });
 };

The enhanced error messages will make debugging easier by providing more context about the failure conditions.

design-system/loader.tsx (3)

4-26: Consider enhancing type safety and design system integration

While the type definition is well-documented, consider these improvements:

  1. Define default values for commonly used props
  2. Constrain the color prop to your design system's color palette for consistency

Example enhancement:

+import { Theme } from '@/theme';
+
 type ILoaderProps = {
   size?: "small" | "large";
   animating?: boolean;
-  color?: string;
+  color?: keyof Theme['colors'];
   hidesWhenStopped?: boolean;
   style?: StyleProp<ViewStyle>;
 };

28-39: Consider accessibility and default behavior improvements

The implementation is clean, but consider these enhancements:

  1. Add accessibilityLabel for screen readers
  2. Provide default props for common use cases
  3. Consider if memo is necessary for this simple component

Example enhancement:

-export const Loader = memo(function Loader(props: ILoaderProps) {
+export const Loader = function Loader({
+  size = 'small',
+  animating = true,
+  color,
+  hidesWhenStopped = true,
+  style,
+}: ILoaderProps) {
-  const { size, animating, color, hidesWhenStopped, style } = props;
   return (
     <ActivityIndicator
+      accessibilityLabel="Loading"
+      accessibilityRole="progressbar"
       size={size}
       animating={animating}
       color={color}
       hidesWhenStopped={hidesWhenStopped}
       style={style}
     />
   );
-});
+};

1-39: Consider specialized loader variants for common use cases

Since this is a design system component used in attachment loading, consider:

  1. Creating specialized variants (e.g., AttachmentLoader, InlineLoader)
  2. Documenting integration patterns with the attachment system
  3. Adding Storybook stories to showcase different use cases

This would improve reusability and maintain consistency across the application.

queries/useConversationWithPeerQuery.ts (2)

18-18: Enhance debug logging with peer information

The current debug message could be more descriptive by including the peer address being queried.

-logger.info("[Crash Debug] queryFn fetching conversation with peer");
+logger.info(`[Crash Debug] Fetching conversation with peer: ${peer}`);

14-16: Prevent options from overriding critical query configuration

Spreading options first allows overriding of critical query configuration. Consider moving the spread after the core configuration.

return useQuery({
-  ...options,
   queryKey: conversationWithPeerQueryKey(account, peer),
+  ...options,
components/Chat/Message/message-content-types/message-simple-text.tsx (1)

12-35: Consider implementing a message type registry pattern.

As the messaging system grows, you might need to handle more message types. Consider implementing a message type registry pattern that would allow for:

  • Easy registration of new message types
  • Consistent handling of different message formats
  • Simplified testing and maintenance

Example approach:

interface MessageTypeHandler {
  type: string;
  component: React.ComponentType<MessageProps>;
  validator: (message: DecodedMessage) => boolean;
}

const messageTypeRegistry = new Map<string, MessageTypeHandler>();
queries/useAddToGroupMutation.ts (1)

Line range hint 22-26: Improve error handling and return type safety

The mutation function has several areas that could be improved:

  1. The error case returns undefined without proper typing
  2. Missing explicit error messages for invalid states
  3. The return type isn't explicitly defined

Consider this improvement:

  mutationFn: async (addresses: string[]): Promise<string[] | void> => {
    if (!group || !account) {
-     return;
+     throw new Error('Missing required group or account');
    }
    await group.addMembers(addresses);
    return addresses;
  },
screens/ConversationReadOnly.tsx (2)

Line range hint 42-42: Consider adding error handling for failed message fetches.

The component only handles the loading state but doesn't account for potential fetch errors. Consider adding error handling to improve user experience.

- const { data: messages, isLoading: isLoadingMessages } =
+ const { data: messages, isLoading: isLoadingMessages, error } =
    useConversationPreviewMessages(currentAccount, topic!);

+ if (error) {
+   return <ErrorMessage message="Failed to load messages" />;
+ }

  if (isLoadingMessages) {
    return null;
  }

Line range hint 41-41: Remove unsafe non-null assertions.

The code uses non-null assertions (!) for currentAccount and topic, which could lead to runtime errors. Consider proper null checking or providing meaningful error states.

- const currentAccount = useCurrentAccount()!;
+ const currentAccount = useCurrentAccount();
+ if (!currentAccount) {
+   return <ErrorMessage message="No account selected" />;
+ }

  const { theme } = useAppTheme();

  const topic = useConversationCurrentTopic();
+ if (!topic) {
+   return <ErrorMessage message="No conversation selected" />;
+ }

  const { data: messages, isLoading: isLoadingMessages } =
-   useConversationPreviewMessages(currentAccount, topic!);
+   useConversationPreviewMessages(currentAccount, topic);

Also applies to: 31-31

queries/useBlockGroupMutation.ts (1)

Line range hint 19-23: Make topic parameter required to avoid non-null assertions

The function uses non-null assertions (!) with topic while allowing it to be undefined in the parameter type. This creates a potential runtime error risk.

Consider making topic required in the function signature:

-export const useBlockGroupMutation = (
-  account: string,
-  topic: ConversationTopic | undefined
-) => {
+export const useBlockGroupMutation = (
+  account: string,
+  topic: ConversationTopic
+) => {

Then remove the null check in mutationFn since it's no longer needed:

   mutationFn: async () => {
-    if (!topic || !account) {
+    if (!account) {
       return;
     }
components/Chat/ConsentPopup/consent-popup.design-system.tsx (3)

1-6: Standardize import paths for design system components

There's an inconsistency in how design system components are imported:

  • Button uses @design-system/
  • Other components use @/design-system/

This inconsistency could lead to confusion and maintenance issues.

Standardize the imports to use the same path pattern:

-import { Button } from "@design-system/Button/Button";
+import { Button } from "@/design-system/Button/Button";

18-40: LGTM! Consider extracting shared styles

The implementation is clean and follows best practices. Good use of theme spacing and proper style merging.

Consider extracting common styles between this and ConsentPopupContainer into a shared styles object or theme tokens, as both components share similar spacing patterns. This would improve maintainability and consistency.


42-67: Consider enhancing type safety for style prop

The implementation is clean, but we could improve type safety for the style prop to ensure only valid React Native styles are passed.

Consider using ViewStyle type:

+import { ViewStyle } from 'react-native';

 export function ConsentPopupContainer({
   children,
-  style,
+  style?: ViewStyle | ViewStyle[],
   ...rest
 }: IVStackProps) {

This would provide better TypeScript hints and catch invalid style properties at compile time.

queries/useGroupConsentQuery.ts (1)

Line range hint 14-25: Remove redundant topic check in enabled condition

Since topic is now a required parameter, the !!topic check in the enabled condition is redundant.

  return useQuery({
    queryKey: groupConsentQueryKey(account, topic!),
    queryFn: async () => {
      const consent = await group!.consentState();
      return consent;
    },
-   enabled: !!group && !!topic,
+   enabled: !!group,
    initialData: group?.state,
    ...queryOptions,
  });
queries/useDmConstentStateQuery.ts (2)

7-7: Consider using an interface instead of type alias for better extensibility

While the type alias works, using an interface would make it easier to extend the consent data structure in the future if needed.

-type DmConsentQueryData = ConsentState;
+interface DmConsentQueryData extends ConsentState {
+  // Additional fields can be added here in the future
+}

24-44: Extract common parameter type for utility functions

The utility functions share similar parameter structure which could be extracted to reduce duplication.

+interface DmConsentQueryParams {
+  account: string;
+  topic: ConversationTopic;
+}

-export const getDmConsentQueryData = (args: {
-  account: string;
-  topic: ConversationTopic;
-}) => {
+export const getDmConsentQueryData = (args: DmConsentQueryParams) => {
   // ... rest of the implementation
 };

-export const setDmConsentQueryData = (args: {
-  account: string;
-  topic: ConversationTopic;
+export const setDmConsentQueryData = (args: DmConsentQueryParams & {
   consent: DmConsentQueryData;
 }) => {
   // ... rest of the implementation
 };
queries/useConversationQuery.ts (1)

17-28: Consider improving type safety by avoiding non-null assertions

While the enabled: !!topic check prevents execution with undefined topic, the non-null assertions (!) in queryKey and queryFn could be handled more safely.

Consider this safer approach:

export const useConversationQuery = (
  account: string,
  topic: ConversationTopic | undefined,
  options?: Partial<UseQueryOptions<ConversationQueryData | null | undefined>>
) => {
+ if (!topic) {
+   throw new Error("Topic is required for conversation query");
+ }
  return useQuery({
    ...options,
-   queryKey: conversationQueryKey(account, topic!),
-   queryFn: () => getConversation(account, topic!),
+   queryKey: conversationQueryKey(account, topic),
+   queryFn: () => getConversation(account, topic),
    enabled: !!topic,
  });
};
queries/useGroupDescriptionMutation.ts (4)

5-6: Remove commented-out import statement

The commented import refreshGroup is not being used and appears to be related to the commented-out code in the onSuccess handler. Clean up by removing unused code to maintain codebase clarity.

-// import { refreshGroup } from "../utils/xmtpRN/conversations";

Also applies to: 13-13


Line range hint 28-33: Remove redundant null check in onMutate handler

The topic null check in onMutate is unnecessary since topic is now a required parameter.

  onMutate: async (groupDescription: string) => {
-   if (!topic) {
-     return;
-   }
    await cancelGroupDescriptionQuery(account, topic);
    const previousGroupDescription = getGroupDescriptionQueryData(

Line range hint 41-52: Simplify onError handler

The topic null check in onError is unnecessary, and the error handling can be simplified.

  onError: (error, _variables, context) => {
    logger.warn("onError useGroupDescriptionMutation");
    sentryTrackError(error);
    if (context?.previousGroupDescription === undefined) {
      return;
    }
-   if (!topic) {
-     return;
-   }
    setGroupDescriptionQueryData(
      account,
      topic,
      context.previousGroupDescription
    );
  },

Remove commented code in onSuccess handler

Based on the codebase analysis, the refreshGroup function is not implemented in the conversations utility file, and all its imports and usages across the codebase are commented out. This indicates an intentional removal of this functionality. The commented code in the onSuccess handler should be removed to maintain clean code.

🔗 Analysis chain

Line range hint 53-57: Clean up or implement commented code in onSuccess

The onSuccess handler contains a commented-out refreshGroup call. Either implement the refresh functionality or remove the comment if it's no longer needed.

Let's check if refreshGroup is still used elsewhere in the codebase:


Let me check if this is a consistent pattern across the codebase by looking at the implementation of refreshGroup:


Let me try a different approach to find the implementation:


Let me check the content of the conversations.ts file:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for refreshGroup usage
rg "refreshGroup" --type ts

Length of output: 1860


Script:

#!/bin/bash
# Search for refreshGroup implementation
ast-grep --pattern 'export const refreshGroup = $_'

# Also check if it's defined somewhere else
ast-grep --pattern 'const refreshGroup = $_'

Length of output: 100


Script:

#!/bin/bash
# Search for refreshGroup in utils/xmtpRN/conversations since that's where it was imported from
rg -A 5 "refreshGroup" utils/xmtpRN/conversations.ts

# Also check if the file exists
fd conversations.ts

Length of output: 159


Script:

#!/bin/bash
# View the content of conversations.ts
cat utils/xmtpRN/conversations.ts

Length of output: 14536

queries/useRevokeAdminMutation.ts (5)

Line range hint 22-28: Remove redundant null check for topic

Since topic is now a required parameter, the !topic check is redundant and can be removed.

  mutationFn: async (inboxId: InboxId) => {
-   if (!group || !account || !topic) {
+   if (!group || !account) {
      return;
    }
    await group.removeAdmin(inboxId);
    return inboxId;
  },

Line range hint 29-44: Remove unnecessary null check in onMutate

The !topic check at the start of onMutate is unnecessary since topic is now required.

  onMutate: async (inboxId: InboxId) => {
-   if (!topic) {
-     return;
-   }
    await cancelGroupMembersQuery(account, topic);

    const previousGroupMembers = getGroupMembersQueryData(account, topic);

Line range hint 45-54: Enhance error handling and remove redundant check

  1. Remove the redundant !topic check
  2. Consider adding more detailed error logging
  onError: (error, _variables, context) => {
-   logger.warn("onError useRevokeAdminMutation");
+   logger.warn("Failed to revoke admin privileges", { error, inboxId: _variables });
    sentryTrackError(error);
    if (context?.previousGroupMembers === undefined) {
      return;
    }
-   if (!topic) {
-     return;
-   }
    setGroupMembersQueryData(account, topic, context.previousGroupMembers);
  },

Line range hint 55-58: Remove commented code

The commented out refreshGroup call should either be removed or implemented if needed.

  onSuccess: (data, variables, context) => {
    logger.debug("onSuccess useRevokeAdminMutation");
-   // refreshGroup(account, topic);
  },

Line range hint 21-21: Consider using a more specific mutation key

The ! operator in the mutation key suggests it was designed for the previous optional topic. Now that topic is required, this can be simplified.

- mutationKey: revokeAdminMutationKey(account, topic!),
+ mutationKey: revokeAdminMutationKey(account, topic),
navigation/useNavigation.tsx (1)

25-27: LGTM! Consider adding JSDoc documentation.

The implementation is clean, type-safe, and follows good functional programming principles. To improve maintainability, consider adding JSDoc documentation explaining the purpose and usage of this utility hook.

+/**
+ * A type-safe hook to access route parameters for the current screen.
+ * @template ScreenName - The name of the screen in NavigationParamList
+ * @returns The route parameters for the current screen
+ * @example
+ * const { conversationId } = useRouteParams<"Conversation">();
+ */
 export function useRouteParams<ScreenName extends keyof NavigationParamList>() {
   return useRoute<ScreenName>().params;
 }
queries/useRevokeSuperAdminMutation.ts (3)

Line range hint 24-51: Remove redundant null checks for topic

Since topic is now a required parameter (not optional), the null checks are redundant and can be safely removed.

Apply these changes:

  mutationFn: async (inboxId: InboxId) => {
-   if (!group || !account || !topic) {
+   if (!group || !account) {
      return;
    }
    await group.removeSuperAdmin(inboxId);
    return inboxId;
  },
  onMutate: async (inboxId: InboxId) => {
-   if (!topic) {
-     return;
-   }
    await cancelGroupMembersQuery(account, topic);

    const previousGroupMembers = getGroupMembersQueryData(account, topic);
    if (!previousGroupMembers) {
      return;
    }
    // ... rest of the code
  },
  onError: (error, _variables, context) => {
    logger.warn("onError useRevokeSuperAdminMutation");
    sentryTrackError(error);
-   if (!topic) {
-     return;
-   }
    if (context?.previousGroupMembers === undefined) {
      return;
    }
    setGroupMembersQueryData(account, topic, context.previousGroupMembers);
  },

Line range hint 1-15: Clean up commented code

There are commented imports and function calls that should either be removed or uncommented if needed.

Either remove or uncomment these lines:

-// import { refreshGroup } from "../utils/xmtpRN/conversations";

And in the onSuccess callback:

-// refreshGroup(account, topic);

Line range hint 22-22: Remove unnecessary non-null assertion

The mutation key uses a non-null assertion (!) on topic which is no longer needed since topic is now required.

- mutationKey: revokeSuperAdminMutationKey(account, topic!),
+ mutationKey: revokeSuperAdminMutationKey(account, topic),
queries/usePromoteToSuperAdminMutation.ts (5)

13-13: Clean up commented code

There's commented out code related to refreshGroup both in imports and onSuccess callback. Either remove it completely or document why it's kept as a reference.

Also applies to: 57-57


Line range hint 24-29: Remove redundant null check for topic

Since topic is now a required parameter (not optional), the null check !topic in mutationFn is redundant and can be removed. TypeScript guarantees that topic will be defined.

 mutationFn: async (inboxId: InboxId) => {
-  if (!group || !account || !topic) {
+  if (!group || !account) {
     return;
   }
   await group.addSuperAdmin(inboxId);
   return inboxId;
 },

Line range hint 31-33: Remove redundant topic check in onMutate

The check and error throw for !topic is redundant since topic is now a required parameter. TypeScript ensures this at compile time.

 onMutate: async (inboxId: InboxId) => {
-  if (!topic) {
-    throw new Error("Topic is required");
-  }
   await cancelGroupMembersQuery(account, topic);

Line range hint 47-54: Simplify error recovery logic

The !topic check in onError is redundant since topic is now a required parameter. The error recovery logic can be simplified.

 onError: (error, _variables, context) => {
   logger.warn("onError usePromoteToSuperAdminMutation");
   sentryTrackError(error);
   if (context?.previousGroupMembers === undefined) {
     return;
   }
-  if (!topic) {
-    return;
-  }
   setGroupMembersQueryData(account, topic, context.previousGroupMembers);
 },

Line range hint 22-61: Consider implementing a consistent error handling strategy

The mutation implementation mixes TypeScript type guarantees with runtime checks inconsistently. Consider implementing a more structured error handling approach:

  1. Use TypeScript's type system to handle compile-time guarantees (as started with making topic required)
  2. Implement proper error types for runtime errors (e.g., network errors, permission errors)
  3. Consider using a Result type or similar pattern for better error handling

This would make the code more maintainable and error handling more predictable.

queries/useRemoveFromGroupMutation.ts (1)

Line range hint 24-29: Improve type safety in mutation handlers

  1. The mutationFn should return a consistent type instead of undefined
  2. The onMutate context type should be explicitly defined

Consider these improvements:

+ type RemoveFromGroupContext = {
+   previousGroupMembers: typeof previousGroupMembers | undefined;
+ };

  mutationFn: async (inboxIds: InboxId[]) => {
    if (!group || !account) {
-     return;
+     return inboxIds;
    }
    await group.removeMembersByInboxId(inboxIds);
    return inboxIds;
  },
- onMutate: async (inboxIds: InboxId[]) => {
+ onMutate: async (inboxIds: InboxId[]): Promise<RemoveFromGroupContext> => {
    await cancelGroupMembersQuery(account, topic);
    const removeSet = new Set(inboxIds);

    const previousGroupMembers = getGroupMembersQueryData(account, topic);
    if (!previousGroupMembers) {
-     return;
+     return { previousGroupMembers: undefined };
    }

Also applies to: 31-43

utils/groupUtils/groupActionHandlers.ts (2)

Line range hint 13-68: Consider modernizing the implementation.

Here are some suggestions to improve the code:

  1. Use constants for consent states instead of string literals
  2. Consider using Promises instead of callbacks for better async handling
  3. Add type safety for action sheet indices

Here's a suggested refactor:

// Define constants
const CONSENT_STATES = {
  DENIED: 'denied'
} as const;

// Modern implementation
export const groupRemoveRestoreHandler = (
  consent: ConsentState | undefined,
  colorScheme: ColorSchemeName,
  groupName: string | undefined,
  allowGroup: GroupActionHandler,
  blockGroup: GroupActionHandler
): Promise<boolean> => {
  const showOptions = async (
    options: string[],
    title: string,
    actions: (() => void)[]
  ): Promise<boolean> => {
    return new Promise((resolve) => {
      showActionSheetWithOptions(
        {
          options,
          cancelButtonIndex: options.length - 1,
          destructiveButtonIndex: consent === CONSENT_STATES.DENIED ? undefined : [0, 1],
          title,
          ...actionSheetColors(colorScheme),
        },
        (selectedIndex?: number) => {
          if (selectedIndex !== undefined && selectedIndex < actions.length) {
            actions[selectedIndex]();
            resolve(true);
          } else {
            resolve(false);
          }
        }
      );
    });
  };

  return consent === CONSENT_STATES.DENIED
    ? showOptions(
        [
          translate("restore"),
          translate("restore_and_unblock_inviter"),
          translate("cancel"),
        ],
        `${translate("restore")} ${groupName}?`,
        [
          () => allowGroup({ includeAddedBy: false, includeCreator: false }),
          () => allowGroup({ includeAddedBy: true, includeCreator: false }),
        ]
      )
    : showOptions(
        [
          translate("remove"),
          translate("remove_and_block_inviter"),
          translate("cancel"),
        ],
        `${translate("remove")} ${groupName}?`,
        [
          () => blockGroup({ includeAddedBy: false, includeCreator: false }),
          () => blockGroup({ includeAddedBy: true, includeCreator: false }),
        ]
      );
};

Line range hint 13-21: Improve error handling and input validation.

The current implementation has several potential issues:

  1. The groupName parameter being undefined could lead to confusing UI messages
  2. Missing validation for required action handlers
  3. Errors from allowGroup and blockGroup handlers aren't propagated

Consider adding these safeguards:

  1. Validate required parameters
  2. Add error handling for the action handlers
  3. Provide a default group name for the UI
 export const groupRemoveRestoreHandler = (
   consent: ConsentState | undefined,
   colorScheme: ColorSchemeName,
   groupName: string | undefined,
   allowGroup: GroupActionHandler,
   blockGroup: GroupActionHandler
 ) => {
+  if (!allowGroup || !blockGroup) {
+    throw new Error('Action handlers are required');
+  }
+  const safeGroupName = groupName || translate('this_group');
   return (callback: (success: boolean) => void) => {
components/Chat/Message/message-date-change.tsx (4)

16-24: Consider adding TypeScript interface for component props

The component is well-structured with proper memoization and context usage. However, even though it currently doesn't accept props, it's good practice to explicitly define the props interface for future maintainability.

+interface MessageDateChangeProps {}
+
-export const MessageDateChange = memo(function MessageDateChange() {
+export const MessageDateChange = memo(function MessageDateChange({}: MessageDateChangeProps) {

26-31: Consider memoizing animation configuration

While the current implementation is correct, the spring configuration object could be memoized to prevent unnecessary recreations.

+const springConfig = {
+  damping: SICK_DAMPING,
+  stiffness: SICK_STIFFNESS,
+};
+
 const showTimeProgressAV = useDerivedValue(() => {
-  return withSpring(showTimeAV.value ? 1 : 0, {
-    damping: SICK_DAMPING,
-    stiffness: SICK_STIFFNESS,
-  });
+  return withSpring(showTimeAV.value ? 1 : 0, springConfig);
 });

33-40: Consider explicit null handling for sentAt

While the current implementation handles undefined sentAt gracefully, it would be more explicit to handle this case with proper TypeScript null checking.

-const messageTime = sentAt ? getLocalizedTime(sentAt) : "";
+const messageTime = sentAt != null ? getLocalizedTime(sentAt) : "";

42-47: Move messageDate computation inside the return statement

Since messageDate is only used when showDateChange is true, it would be more efficient to move its computation inside the return statement to avoid unnecessary calculations.

 if (!showDateChange) {
   return null;
 }

-const messageDate = getRelativeDate(sentAt);
-
 return (
+  const messageDate = getRelativeDate(sentAt);
   <AnimatedHStack
queries/usePromoteToAdminMutation.ts (3)

18-18: LGTM! Making topic required improves type safety.

The change to make topic a required parameter is a good improvement for type safety.

Since topic is now required, we can remove the redundant !topic checks in the mutation functions. This includes checks in mutationFn, onMutate, and onError.


Line range hint 23-29: Remove redundant null checks and non-null assertions.

Since topic is now a required parameter:

  1. The !topic check in mutationFn is redundant
  2. The non-null assertion topic! in mutationKey is unnecessary

Apply these changes:

-    mutationKey: promoteAdminMutationKey(account, topic!),
+    mutationKey: promoteAdminMutationKey(account, topic),
     mutationFn: async (inboxId: InboxId) => {
-      if (!group || !account || !topic) {
+      if (!group || !account) {
         return;
       }

Line range hint 35-57: Clean up optimistic update and error handling logic.

Remove the redundant !topic checks in onMutate and onError callbacks since topic is now required.

Apply these changes:

     onMutate: async (inboxId: InboxId) => {
-      if (!topic) {
-        return;
-      }
       await cancelGroupMembersQuery(account, topic);

       const previousGroupMembers = getGroupMembersQueryData(account, topic);
       if (!previousGroupMembers) {
         return;
       }
       // ... rest of the code ...
     },
     onError: (error, _variables, context) => {
       logger.warn("onError usePromoteToAdminMutation");
       sentryTrackError(error);
       if (context?.previousGroupMembers === undefined) {
         return;
       }
-      if (!topic) {
-        return;
-      }
       setGroupMembersQueryData(account, topic, context.previousGroupMembers);
     },
components/Chat/Message/components/message-layout.tsx (3)

17-19: Add JSDoc documentation for the type definition.

Consider adding JSDoc documentation to describe the purpose of the component and the expected content of children.

+/**
+ * Props for the MessageLayout component.
+ * @property {ReactNode} children - The message content to be rendered within the layout.
+ */
 type IMessageLayoutProps = {
   children: ReactNode;
 };

24-26: Add error handling for missing context values.

The destructured values from context should be validated to handle potential undefined cases gracefully.

-  const { senderAddress, fromMe, messageId } = useMessageContextStoreContext(
-    useSelect(["senderAddress", "fromMe", "messageId"])
-  );
+  const { senderAddress = '', fromMe = false, messageId = '' } = useMessageContextStoreContext(
+    useSelect(["senderAddress", "fromMe", "messageId"])
+  ) ?? {};
+
+  if (!messageId) {
+    console.warn('MessageLayout: Missing required context values');
+    return null;
+  }

21-58: Consider performance optimization for message lists.

Since this component will be rendered for each message in a conversation, consider wrapping it with React.memo to prevent unnecessary re-renders when parent components update.

-export function MessageLayout({ children }: IMessageLayoutProps) {
+export const MessageLayout = React.memo(function MessageLayout({ 
+  children 
+}: IMessageLayoutProps) {
   // ... component implementation ...
-}
+});
features/conversation/composer/send-attachment-preview.tsx (2)

Line range hint 76-84: Consider improvements to the Loader implementation

While the loading overlay implementation is solid, there are a few suggestions for improvement:

  1. The transform values (-12px) appear to be magic numbers that might break if the Loader size changes
  2. The white color is hardcoded rather than using the theme system

Consider this improvement:

 <Loader
   size="small"
-  color="#ffffff"
+  color={theme.colors.text.inverse}
   style={{
     position: "absolute",
     top: "50%",
     left: "50%",
-    transform: [{ translateX: -12 }, { translateY: -12 }],
+    transform: [
+      { translateX: -(theme.sizes.loaderSmall / 2) },
+      { translateY: -(theme.sizes.loaderSmall / 2) }
+    ],
   }}
 />

Remove unused error prop and document Android color handling

  1. The error prop is defined but not used in the SendAttachmentPreview component. The prop is passed from AttachmentsPreview component with value mediaPreview.status === "error", but no error UI is rendered. Either implement error handling UI or remove the unused prop.
  2. The Android-specific color handling in useEffect needs documentation to explain its purpose:
useEffect(() => {
  setAndroidSystemColor("#000000");
  return () => {
    setAndroidColors(colorScheme);
  };
}, [colorScheme]);
🔗 Analysis chain

Line range hint 13-19: Address architectural considerations

  1. The error prop is defined in the component props but not utilized in the implementation. Either implement error handling UI or remove the unused prop.
  2. Consider documenting the Android-specific color handling in the useEffect hook, as it's not immediately clear why this is necessary.

Let's check if the error state is handled elsewhere in the codebase:

Also applies to: 29-35

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for references to SendAttachmentPreview with error handling
rg -A 5 "SendAttachmentPreview.*error" --type typescript

Length of output: 93


Script:

#!/bin/bash
# Search for references to SendAttachmentPreview with error handling, using broader file patterns
rg -A 5 "SendAttachmentPreview.*error" --type ts --type tsx

# Find the implementation to check error prop usage
ast-grep --pattern 'const SendAttachmentPreview = ({ $$$, error, $$$ }: SendAttachmentPreviewProps) => {
  $$$
}'

# Find all files that might contain SendAttachmentPreview
fd -e ts -e tsx | xargs rg "SendAttachmentPreview"

Length of output: 761


Script:

#!/bin/bash
# Let's check the actual implementation and usage
cat features/conversation/composer/send-attachment-preview.tsx

# Check how it's being used in the composer
cat features/conversation/composer/composer.tsx

Length of output: 20057

components/Chat/ConsentPopup/group-consent-popup.tsx (3)

31-44: Replace magic string with a typed constant

The hardcoded "unknown" string with an inline comment is not maintainable. Consider creating a typed constant to represent this state.

+const UNKNOWN_INVITER = "unknown" as const;
+
 const onBlock = useCallback(async () => {
   groupRemoveRestoreHandler(
-    "unknown", // To display "Remove & Block inviter"
+    UNKNOWN_INVITER,
     colorScheme,
     groupName,
     allowGroup,
     blockGroup
   )((success: boolean) => {

46-55: Consider more specific error handling

The error handling could be more specific to provide better feedback to users.

 const onAccept = useCallback(async () => {
   try {
     await allowGroup({
       includeCreator: false,
       includeAddedBy: false,
     });
   } catch (error) {
-    captureErrorWithToast(error);
+    if (error instanceof PermissionError) {
+      captureErrorWithToast(translate('permission_denied'));
+    } else {
+      captureErrorWithToast(error);
+    }
   }
 }, [allowGroup]);

18-20: Extract props interface and add runtime validation

Consider extracting the props interface and adding runtime validation for the topic prop to ensure type safety at runtime.

+interface GroupConsentPopupProps {
+  topic: ConversationTopic;
+}
+
-export function GroupConsentPopup(props: { topic: ConversationTopic }) {
+export function GroupConsentPopup(props: GroupConsentPopupProps) {
   const { topic } = props;
+  
+  if (!topic) {
+    console.error('GroupConsentPopup: topic is required');
+    return null;
+  }
components/Chat/Message/message-timestamp.tsx (5)

3-3: Remove unused import debugBorder

The debugBorder utility is imported but only used in a commented-out line. Consider removing this unused import to keep the codebase clean.

-import { debugBorder } from "@/utils/debug-style";

29-34: Consider documenting animation constants

The spring animation uses SICK_DAMPING and SICK_STIFFNESS constants. Consider adding documentation explaining the chosen values and their visual effect.


36-40: Extract fallback height as a constant

The fallback height of 14 (used later in the code) should be defined as a named constant at the top of the file for better maintainability.

+const DEFAULT_TEXT_HEIGHT = 14;

export const MessageTimestamp = memo(function MessageTimestamp() {

77-77: Remove commented code

Remove the commented-out debug border line to maintain clean code.


42-66: Consider extracting animation configuration

The animation configuration contains multiple interpolation ranges and values. Consider extracting these into a configuration object for better maintainability and reusability.

+const TIMESTAMP_ANIMATION_CONFIG = {
+  scale: {
+    input: [0, 1],
+    output: [0, 1],
+  },
+  translateY: {
+    input: [0, 1],
+    output: [theme.spacing.xl, 0],
+  },
+  opacity: {
+    input: [0, 1],
+    output: [0, 1],
+  },
+};

const timeAnimatedStyle = useAnimatedStyle(() => {
  return {
    height: interpolate(
      showTimeProgressAV.value,
      [0, 1],
      [0, textHeight || DEFAULT_TEXT_HEIGHT]
    ),
    opacity: interpolate(
      showTimeProgressAV.value,
      TIMESTAMP_ANIMATION_CONFIG.opacity.input,
      TIMESTAMP_ANIMATION_CONFIG.opacity.output
    ),
    // ... similar refactoring for other properties
  };
}, [textHeight]);
queries/useGroupMembersQuery.ts (3)

19-19: Remove unnecessary non-null assertion

Since topic is now required, the non-null assertion (!) in groupMembersQueryKey(account, topic!) is no longer needed.

-    queryKey: groupMembersQueryKey(account, topic!),
+    queryKey: groupMembersQueryKey(account, topic),

Line range hint 23-42: Consider reducing code duplication between hooks

Both hooks share identical queryFn implementation. Consider extracting the common logic into a shared function.

const createGroupMembersQueryFn = (group: Group | undefined) => async () => {
  if (!group) {
    return {
      byId: {},
      byAddress: {},
      ids: [],
    };
  }
  const updatedMembers = await group.members();
  return entifyWithAddress(
    updatedMembers,
    (member) => member.inboxId,
    (member) => getCleanAddress(member.addresses[0])
  );
};

Also applies to: 48-67


Line range hint 17-42: Consider enhancing error handling

The current implementation silently returns empty data structures when the group is not found. Consider adding proper error handling to distinguish between different failure scenarios (e.g., group not found, network errors, permission issues).

Example approach:

type GroupMembersError = 
  | { type: 'GROUP_NOT_FOUND' }
  | { type: 'NETWORK_ERROR'; error: Error }
  | { type: 'PERMISSION_DENIED' };

// Then use in the query with error typing
useQuery<GroupMembersSelectData, GroupMembersError>
components/Chat/Message/components/message-repliable.tsx (2)

Line range hint 7-11: Track technical debt for Expo SDK 52 upgrade.

The TODOs indicate several performance improvements that will be possible with Expo SDK 52:

  1. Migration to ReanimatedSwipeable
  2. Better performance using SharedValue
  3. More precise haptic feedback based on swipe progress

Would you like me to create GitHub issues to track these improvements for when Expo SDK 52 becomes available?


Line range hint 73-82: Improve type safety and simplify threshold calculation.

The current implementation has two potential improvements:

  1. The type assertion (translation as any)._value is unsafe and could break at runtime.
  2. The threshold calculation could be simplified.

Consider this safer implementation:

  onSwipeableWillClose={() => {
    const translation = swipeableRef.current?.state.rowTranslation;
-   const translationValue = (translation as any)._value;
-   const v = translationValue - dragOffsetFromLeftEdge;
-   if (translation && v > xTresholdToReply) {
+   if (translation?._value && translation._value - dragOffsetFromLeftEdge > xTresholdToReply) {
      Haptics.successNotificationAsync();
      onReply();
    }
  }}
features/conversation/conversation-persisted-stores.ts (1)

Line range hint 51-55: Fix persistence key mismatch in partialize function

There's a mismatch between the state property name and the persistence key:

  • State property is named composerMediaPreview
  • Persistence key is mediaPreview

This will cause the media preview state to not persist correctly.

 partialize: (state) => ({
   inputValue: state.inputValue,
   replyingToMessageId: state.replyingToMessageId,
-  mediaPreview: state.composerMediaPreview,
+  composerMediaPreview: state.composerMediaPreview,
 }),
components/Chat/Message/message-utils.tsx (3)

125-127: Consider improving the robustness and maintainability of the time conversion utility.

While the conversion logic is correct, consider these improvements:

  1. Define the conversion factor as a named constant
  2. Add input validation for negative numbers
  3. Add JSDoc documentation explaining the purpose and parameters

Here's a suggested implementation:

+const NANOSECONDS_PER_MILLISECOND = 1_000_000;
+
+/**
+ * Converts a timestamp from nanoseconds to milliseconds
+ * @param nanoseconds - The number of nanoseconds to convert
+ * @returns The equivalent number of milliseconds
+ * @throws {Error} If the input is negative
+ */
 export function convertNanosecondsToMilliseconds(nanoseconds: number) {
-  return nanoseconds / 1000000;
+  if (nanoseconds < 0) {
+    throw new Error('Nanoseconds value cannot be negative');
+  }
+  return nanoseconds / NANOSECONDS_PER_MILLISECOND;
 }

Line range hint 32-34: Address the architectural decision in the TODO comment.

The comment indicates uncertainty about the proper location for these utility functions. This should be addressed to improve code organization.

Consider these factors for the decision:

  1. If these are generic XMTP message utilities, they belong in @xmtp/react-native-sdk
  2. If they're specific to your application's message handling, move them to @utils/xmtpRN/messages.ts

Would you like me to create a GitHub issue to track this architectural decision?


Line range hint 36-89: Improve type safety in message type guard functions.

All type guard functions are using any instead of proper typing, which bypasses TypeScript's type checking. The commented code suggests these should use DecodedMessageWithCodecsType.

Apply this pattern to all type guard functions:

 export function isTextMessage(
-  message: any
+  message: DecodedMessageWithCodecsType
 ): message is DecodedMessage<[TextCodec]> {
   return getMessageContentType(message.contentTypeId) === "text";
 }
features/conversation/composer/add-attachment-button.tsx (3)

Line range hint 91-102: Consider extracting hardcoded dimensions to constants.

The component contains hardcoded values for dimensions (height: 36, width: 36, size: 20) with comments indicating they're from Figma. Consider extracting these to a constants file for better maintainability.

+// In constants/dimensions.ts
+export const ATTACHMENT_BUTTON = {
+  CONTAINER_SIZE: 36,
+  ICON_SIZE: 20,
+};

-        height: 36, // Value from Figma
-        width: 36, // Value from Figma
+        height: ATTACHMENT_BUTTON.CONTAINER_SIZE,
+        width: ATTACHMENT_BUTTON.CONTAINER_SIZE,

-        size={20} // Value from figma
+        size={ATTACHMENT_BUTTON.ICON_SIZE}

Line range hint 117-133: Add cleanup for temporary media files.

The media handling functions should clean up temporary files after upload to prevent device storage issues.

async function pickMedia() {
  if (Platform.OS === "ios") {
    setStatusBarHidden(true, "fade");
  }
  const asset = await pickMediaFromLibrary();
  if (Platform.OS === "ios") {
    setStatusBarHidden(false, "fade");
  }
-  if (!asset) return;
+  try {
+    if (!asset) return;
+    await handleAttachmentSelected(asset);
+  } finally {
+    // Cleanup temporary file if it exists
+    if (asset?.uri) {
+      await FileSystem.deleteAsync(asset.uri, { idempotent: true });
+    }
+  }
-  handleAttachmentSelected(asset);
}

Line range hint 135-190: Consider adding more granular status updates.

The attachment upload process could benefit from more detailed status updates to improve user experience.

async function handleAttachmentSelected(asset: ImagePicker.ImagePickerAsset) {
  if (asset) {
    try {
      setComposerMediaPreview({
        mediaURI: asset.uri,
        status: "picked",
        mimeType: null,
        dimensions: {
          height: asset.height,
          width: asset.width,
        },
      });

-      setComposerMediaPreviewStatus("uploading");
+      setComposerMediaPreviewStatus("compressing");
      const resizedImage = await compressAndResizeImage(asset.uri);

+      setComposerMediaPreviewStatus("preparing");
      let mimeType = mime.getType(resizedImage.uri);
      if (!mimeType) {
        const match = resizedImage.uri.match(DATA_MIMETYPE_REGEX);
        if (match && match[1]) {
          mimeType = match[1];
        }
      }

+      setComposerMediaPreviewStatus("encrypting");
      const currentAccount = getCurrentAccount()!;
      const encryptedAttachment = await encryptRemoteAttachment(
        currentAccount,
        resizedImage.uri,
        mimeType || undefined
      );

      try {
+        setComposerMediaPreviewStatus("uploading");
        const uploadedAttachment = await uploadRemoteAttachment(
          currentAccount,
          encryptedAttachment
        );

        setUploadedRemoteAttachment(uploadedAttachment);
        setComposerMediaPreviewStatus("uploaded");
      } catch (error) {
        sentryTrackMessage("ATTACHMENT_UPLOAD_ERROR", { error });
+        setComposerMediaPreviewStatus("error");
      }
    } catch (error) {
      sentryTrackError(error);
+      setComposerMediaPreviewStatus("error");
    }
  }
}
queries/useV3ConversationListQuery.ts (4)

Line range hint 38-43: Use template literals for logging messages

Consider using template literals for better readability and consistency.

- logger.debug(
-   `[ConversationListQuery] Fetching conversation list from network took ${
-     (afterSync - beforeSync) / 1000
-   } sec`
- );
+ logger.debug(
+   `[ConversationListQuery] Fetching conversation list from network took ${(afterSync - beforeSync) / 1000}s`
+ );

Line range hint 71-77: Enhance error handling with specific error types

The error handling could be more specific to help with debugging and error recovery.

  } catch (error) {
+   if (error instanceof Error) {
      logger.error(
-       `[ConversationListQuery] Error fetching conversation list from network ${context}`,
-       error
+       `[ConversationListQuery] Error fetching conversation list from network ${context}: ${error.message}`,
+       { error, context }
      );
+   }
    throw error;
  }

Line range hint 134-146: Consider using optimistic updates for better UX

The addConversationToConversationListQuery function directly modifies the query cache. Consider implementing optimistic updates to improve user experience.

 export const addConversationToConversationListQuery = (
   account: string,
   conversation: ConversationWithCodecsType
 ) => {
-  const previousConversationsData = getConversationListQueryData(account);
-  if (!previousConversationsData) {
-    setConversationListQueryData(account, [conversation]);
-    return;
-  }
-  setConversationListQueryData(account, [
-    conversation,
-    ...previousConversationsData,
-  ]);
+  queryClient.setQueryData(
+    conversationListKey(account),
+    (old: V3ConversationListType | undefined) => {
+      if (!old) return [conversation];
+      return [conversation, ...old];
+    }
+  );
 };

Line range hint 148-171: Use immutable updates for message updates

The updateMessageToConversationListQuery function could benefit from a more functional approach using immutable updates.

 export const updateMessageToConversationListQuery = (
   account: string,
   message: DecodedMessageWithCodecsType
 ) => {
-  const previousConversationsData = getConversationListQueryData(account);
-  if (!previousConversationsData) return;
-  const conversation = previousConversationsData.find(
-    (c) => c.topic === message.topic
-  );
-  if (!conversation) return;
-  const newConversations: V3ConversationListType =
-    previousConversationsData.map((c) => {
-      if (c.topic === message.topic) {
-        c.lastMessage = message;
-        return c;
-      }
-      return c;
-    });
-  setConversationListQueryData(account, newConversations);
+  queryClient.setQueryData(
+    conversationListKey(account),
+    (old: V3ConversationListType | undefined) => {
+      if (!old) return;
+      if (!old.some(c => c.topic === message.topic)) return old;
+      return old.map(c => 
+        c.topic === message.topic
+          ? { ...c, lastMessage: message }
+          : c
+      );
+    }
+  );
 };
features/conversation/conversation-service.ts (1)

Line range hint 221-227: Consider enhancing error handling strategy

While logging the error is good, consider these improvements:

  1. Add more context to the error message (e.g., account details)
  2. Consider throwing an error instead of returning an empty state, as this might hide issues
 if (!topic) {
-    logger.error("No topic in getCurrentConversationMessages");
+    logger.error(
+      `No topic in getCurrentConversationMessages for account: ${currentAccount.address}`
+    );
+    throw new Error("Cannot get conversation messages: No topic found");
-    return {
-      byId: {},
-      ids: [],
-      reactions: {},
-    };
 }
utils/xmtpRN/contacts.ts (2)

Line range hint 12-24: Remove commented-out code in refreshConsentList.

The function contains commented-out code that should either be implemented or removed to maintain code cleanliness.

-  // const consentList = await client.preferences.;
+  // TODO: Implement consent list retrieval
   const end = new Date().getTime();
   logger.debug(
     `[XMTPRN Contacts] Refreshed consent list in ${(end - start) / 1000} sec`
   );
-  // return consentList;
+  return [];  // Return empty array until implementation is complete

Line range hint 1-270: Consider extracting common patterns into utility functions.

The file has several patterns that could be extracted to reduce duplication:

  1. Timing measurement for operations
  2. Error handling for missing client
  3. Common logging patterns

Consider creating utility functions like:

// For timing operations
const withTiming = async <T>(
  operation: string,
  fn: () => Promise<T>
): Promise<T> => {
  logger.debug(`[XMTPRN Contacts] ${operation}`);
  const start = new Date().getTime();
  const result = await fn();
  const end = new Date().getTime();
  logger.debug(
    `[XMTPRN Contacts] ${operation} completed in ${(end - start) / 1000} sec`
  );
  return result;
};

// For client operations
const withClient = async <T>(
  account: string,
  fn: (client: ConverseXmtpClientType) => Promise<T>
): Promise<T> => {
  const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
  if (!client) {
    throw new Error("Client not found");
  }
  return fn(client);
};

This would simplify the implementation of functions like consentToGroupsOnProtocolByAccount:

export const consentToGroupsOnProtocolByAccount = async (args: {
  account: string;
  groupIds: string[];
  consent: "allow" | "deny";
}) => {
  const { account, groupIds, consent } = args;
  return withClient(account, (client) =>
    withTiming(
      `Consenting to groups: ${groupIds.join(", ")}`,
      () => consentToGroupsOnProtocol({ client, groupIds, consent })
    )
  );
};
containers/GroupScreenMembersTable.tsx (1)

Line range hint 91-134: Enhance error handling for member management actions

The current error handling uses generic "An error occurred" messages. Consider providing more specific error messages and handling different error types appropriately.

Here's a suggested improvement for one of the handlers (apply similar pattern to others):

  case promoteAdminIndex:
    logger.debug("Promoting member...");
    try {
      await promoteToAdmin(a.inboxId);
    } catch (e) {
      logger.error(e);
-     Alert.alert("An error occurred");
+     const errorMessage = e instanceof Error ? e.message : "Unknown error occurred";
+     Alert.alert(
+       "Failed to promote member",
+       `Unable to promote ${preferredName} to admin: ${errorMessage}`,
+       [{ text: "OK" }]
+     );
    }
    break;
screens/Navigation/Navigation.tsx (1)

153-156: Consider moving DmConversation to its own navigation function

Other conversation-related screens are organized in their own navigation functions (e.g., ConversationNav, NewConversationNav). Consider moving the DmConversation screen to a separate DmConversationNav function for consistency and better maintainability.

function DmConversationNav() {
  return (
    <NativeStack.Screen
      name="DmConversation"
      component={DmConversationScreen}
      options={{
        headerTitle: "Direct Message",
        headerTitleStyle: headerTitleStyle(colorScheme),
        animation: navigationAnimation
      }}
    />
  );
}
screens/GroupInvite.tsx (2)

Line range hint 95-157: Improve polling mechanism robustness

The current polling implementation has several areas for improvement:

  1. Hard-coded polling attempts and timeout
  2. No exponential backoff
  3. No cleanup on component unmount
  4. Deeply nested conditional logic

Consider refactoring to:

  1. Extract polling logic into a custom hook
  2. Implement exponential backoff
  3. Add cleanup on unmount
  4. Flatten the conditional logic

Example implementation:

const useGroupJoinPolling = (joinRequestId: string) => {
  const [status, setStatus] = useState<GroupJoinRequestStatus>('PENDING');
  const mounted = useRef(true);

  useEffect(() => {
    const MAX_ATTEMPTS = 10;
    const BASE_DELAY = 500;
    let attempt = 0;

    const poll = async () => {
      while (mounted.current && attempt < MAX_ATTEMPTS) {
        try {
          const joinRequestData = await getGroupJoinRequest(joinRequestId);
          if (joinRequestData.status !== 'PENDING') {
            setStatus(joinRequestData.status);
            break;
          }
          attempt++;
          await new Promise(r => setTimeout(r, BASE_DELAY * Math.pow(1.5, attempt)));
        } catch (error) {
          logger.error('[GroupInvite] Polling failed:', error);
          setStatus('ERROR');
          break;
        }
      }
    };

    poll();
    return () => {
      mounted.current = false;
    };
  }, [joinRequestId]);

  return status;
};

Line range hint 38-47: Simplify state management and improve error handling

The current implementation mixes multiple state variables and has complex conditional rendering logic. Consider consolidating the state management and improving error handling consistency.

Suggested improvements:

type JoinState = {
  status: 'IDLE' | 'PENDING' | 'SUCCESS' | 'ERROR' | 'REJECTED';
  isPolling: boolean;
  error?: string;
};

const [joinState, setJoinState] = useState<JoinState>({
  status: 'IDLE',
  isPolling: false
});

// In the render method:
const renderJoinStatus = () => {
  switch (joinState.status) {
    case 'IDLE':
      return (
        <Button
          action="primary"
          title={translate("join_group")}
          style={styles.cta}
          onPress={joinGroup}
        />
      );
    case 'PENDING':
      return (
        <Button
          action="primary"
          title={translate("joining")}
          style={styles.cta}
          disabled
        />
      );
    case 'SUCCESS':
      return (
        <Text style={styles.accepted}>
          {translate("group_already_joined")}
        </Text>
      );
    case 'ERROR':
    case 'REJECTED':
      return (
        <Text style={styles.error}>
          {translate(joinState.status === 'ERROR' ? 
            "group_join_error" : 
            "group_join_invite_invalid"
          )}
        </Text>
      );
  }
};

This approach:

  1. Consolidates related states into a single state object
  2. Makes state transitions more explicit
  3. Centralizes status-based rendering logic
  4. Improves maintainability

Also applies to: 191-227

components/GroupAvatar.tsx (1)

Line range hint 205-213: Fix type safety issue with topic parameter

There's a type safety issue where an optional topic prop is being used with a non-null assertion. This could lead to runtime errors if topic is undefined.

Consider one of these solutions:

  1. Make topic required in props (preferred):
type GroupAvatarProps = {
-  topic?: ConversationTopic;
+  topic: ConversationTopic;
}
  1. Add null check before using topic:
- const { members: groupMembers } = useGroupMembers(
-   topic!,
+ const { members: groupMembers } = topic ? useGroupMembers(
+   topic,
    onConversationListScreen
      ? {
          refetchOnWindowFocus: false,
          refetchOnMount: false,
          refetchOnReconnect: false,
          refetchInterval: false,
          staleTime: Infinity,
          enabled: !!topic && !pendingGroupMembers,
        }
-     : undefined
+     : undefined)
+  : { members: undefined };

The first solution is preferred as it:

  1. Aligns with the reported changes in the AI summary
  2. Makes the contract clearer - if a topic is required for the component to work, it should be a required prop
  3. Catches potential issues at compile-time rather than runtime
utils/xmtpRN/conversations.ts (1)

Line range hint 428-484: Consider extracting timing logic into a utility function

The timing measurement pattern is repeated throughout the function. Consider extracting it into a utility function to reduce code duplication and improve maintainability.

Example implementation:

const measureTime = async <T>(
  operation: string,
  fn: () => Promise<T>
): Promise<T> => {
  const start = new Date().getTime();
  const result = await fn();
  const end = new Date().getTime();
  logger.debug(
    `[XMTPRN Conversations] ${operation} took ${(end - start) / 1000} sec`
  );
  return result;
};

// Usage example:
const conversation = await measureTime(
  'Find DM',
  () => client.conversations.findDmByAddress(peer)
);
screens/NewConversation/NewConversation.tsx (3)

Line range hint 81-89: Improve error handling for group operations

The current error handling in handleRightAction could be more informative and consistent:

  1. The generic "An error occurred" message doesn't help users understand what went wrong
  2. The error is console.logged but not properly structured for debugging

Consider this improved error handling:

  try {
    await addMembers(group.members.map((m) => m.address));
    navigation.goBack();
  } catch (e) {
    setLoading(false);
-   console.error(e);
-   Alert.alert("An error occured");
+   const errorMessage = e instanceof Error ? e.message : 'Failed to add members to the group';
+   console.error('[NewConversation] Add members error:', e);
+   Alert.alert(
+     "Error Adding Members",
+     errorMessage,
+     [{ text: "OK", onPress: () => setLoading(false) }]
+   );
  }

Line range hint 155-238: Optimize component performance

The search functionality could benefit from several performance optimizations:

  1. The search logic is complex and could trigger unnecessary re-renders
  2. State updates could be batched
  3. Expensive operations could be memoized

Consider these performance improvements:

// 1. Memoize the search function
const searchForValue = useCallback(async (searchValue: string) => {
  // ... existing search logic
}, [currentAccount]);

// 2. Memoize the profile search results transformation
const filteredSearchResults = useMemo(() => {
  const searchResultsToShow = { ...status.profileSearchResults };
  if (group.enabled && group.members) {
    group.members.forEach((member) => {
      delete searchResultsToShow[member.address];
    });
  }
  if (members) {
    members?.ids?.forEach((memberId: InboxId) => {
      const member = members.byId[memberId];
      const address = getCleanAddress(member.addresses[0]);
      delete searchResultsToShow[address];
    });
  }
  return searchResultsToShow;
}, [status.profileSearchResults, group.enabled, group.members, members]);

// 3. Use reducer for complex state management
type SearchStatus = {
  loading: boolean;
  error: string;
  inviteToConverse: string;
  profileSearchResults: Record<string, ProfileSocials>;
};

const searchReducer = (state: SearchStatus, action: SearchAction): SearchStatus => {
  switch (action.type) {
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    case 'SET_ERROR':
      return { ...state, error: action.payload, loading: false };
    // ... other cases
  }
};

Line range hint 293-318: Enhance accessibility support

The component could benefit from improved accessibility features to better support users with disabilities:

  1. Missing accessibility labels on interactive elements
  2. No screen reader announcements for loading states
  3. Color-only indicators

Consider these accessibility improvements:

  <Button
    variant="text"
    picto="person.2"
    text="New group"
+   accessibilityLabel="Create new group conversation"
+   accessibilityHint="Double tap to start creating a new group"
    style={styles.newGroupButton}
    onPress={() => {
      setGroup({ enabled: true, members: [] });
    }}
  />

  {status.loading && (
    <ActivityIndicator
      style={styles.mainActivityIndicator}
+     accessibilityLabel="Searching for contacts"
+     accessibilityLiveRegion="polite"
    />
  )}

  <Text
    style={[styles.message, styles.error]}
+   accessibilityRole="alert"
+   accessibilityLiveRegion="polite"
  >
    {status.error}
  </Text>
screens/Profile.tsx (3)

Line range hint 163-173: Enhance balance refresh implementation

The artificial delay implementation is good for UX, but could be more robust.

Consider these improvements:

 const manuallyRefreshBalance = useCallback(async () => {
+  if (refreshingBalance) return; // Prevent multiple concurrent refreshes
   setRefreshingBalance(true);
   const now = new Date().getTime();
-  await refreshBalanceForAccount(userAddress, 0);
-  const after = new Date().getTime();
-  if (after - now < 1000) {
-    await new Promise((r) => setTimeout(r, 1000 - after + now));
-  }
-  setRefreshingBalance(false);
+  try {
+    await Promise.all([
+      refreshBalanceForAccount(userAddress, 0),
+      new Promise(resolve => setTimeout(resolve, 1000))
+    ]);
+  } catch (error) {
+    // Handle error appropriately
+    console.error('Failed to refresh balance:', error);
+  } finally {
+    setRefreshingBalance(false);
+  }
 }, [userAddress]);

Line range hint 450-489: Enhance group management error handling and race condition prevention

The group management operations (promote, revoke) could benefit from better error handling and race condition prevention.

Consider these improvements:

 async (selectedIndex?: number) => {
   if (selectedIndex === 0 && peerId) {
+    const currentMember = groupMembers.byAddress[peerAddress];
+    if (!currentMember) {
+      Alert.alert('Error', 'Member no longer exists in the group');
+      return;
+    }
+    try {
       await promoteToAdmin(peerId);
+      Alert.alert('Success', 'Member has been promoted to admin');
+    } catch (error) {
+      Alert.alert('Error', 'Failed to promote member. Please try again.');
+      console.error('Failed to promote member:', error);
+    }
   }
 }

Also consider implementing optimistic updates to improve the user experience while maintaining data consistency.


Line range hint 293-296: Enhance error feedback mechanism

The error display could be more informative and actionable for users.

Consider enhancing the error component:

-<Text style={styles.errorText}>{translate("client_error")}</Text>
+<View style={styles.errorContent}>
+  <Text style={styles.errorText}>{translate("client_error")}</Text>
+  <TouchableOpacity onPress={manuallyRefreshBalance}>
+    <Text style={styles.errorAction}>{translate("try_again")}</Text>
+  </TouchableOpacity>
+</View>
components/Chat/ConsentPopup/dm-consent-popup.tsx (2)

85-90: Unify error handling methods for consistency between handleBlock and handleAccept.

In handleBlock, errors are handled using captureError and showSnackbar. In handleAccept, errors are handled using captureErrorWithToast. Consider using a consistent error handling approach in both functions to improve maintainability and user experience.

Also applies to: 102-105


87-89: Provide more descriptive error messages for better user feedback.

The error messages displayed are generic ("Error consenting"). Consider providing more specific error messages that help the user understand what went wrong, such as "Failed to update consent settings. Please try again."

Also applies to: 103-105

components/Chat/Message/V3Message.tsx (4)

22-22: Use consistent import paths

The import path for MessageStaticAttachment is using an absolute path, whereas other imports use relative paths. For consistency and maintainability, consider updating the import to use a relative path.

Apply this diff to update the import:

- import { MessageStaticAttachment } from "@/components/Chat/Message/message-content-types/message-static-attachment";
+ import { MessageStaticAttachment } from "./message-content-types/message-static-attachment";

79-79: Use proper logging instead of console.log

Using console.log for logging errors is not ideal in production code. Consider using a dedicated logging library or console.error for error messages to improve error handling and debugging.

Apply this diff to update the logging method:

-      console.log("no message found", messageId);
+      console.error("No message found", messageId);

87-87: Remove commented-out code for cleaner codebase

The ...debugBorder() line is commented out within the style prop. If this debug code is no longer needed, consider removing it to keep the code clean. If it is needed for future debugging, you might leave a comment explaining its purpose.


119-138: Consider removing unused commented-out code

There is a block of commented-out code at the end of the file. If this code is no longer needed, consider removing it to reduce clutter and improve readability. If it's intended for future use, consider adding a comment explaining its purpose.

hooks/useGroupConsent.ts (4)

27-29: Consider renaming 'IGroupConsentOptions' to 'GroupConsentOptions'

Since IGroupConsentOptions is defined as a type rather than an interface, the I prefix might be misleading. Consider renaming it to GroupConsentOptions for clarity and consistency with TypeScript naming conventions.


121-121: Unnecessary non-null assertion on 'topic'

The non-null assertion operator ! on topic may be unnecessary here, as topic is already defined as a non-nullable ConversationTopic. Removing the ! can improve code readability.

Apply this diff to remove the unnecessary assertion:

-const { mutateAsync: blockGroupMutation, isPending: isBlockingGroup } =
-  useBlockGroupMutation(account, topic!);
+const { mutateAsync: blockGroupMutation, isPending: isBlockingGroup } =
+  useBlockGroupMutation(account, topic);

Line range hint 155-161: Await the asynchronous function to ensure proper execution

The function consentToInboxIdsOnProtocolByAccount appears to be asynchronous. Calling it without await may lead to unhandled promises or unexpected behavior, such as the function not completing before the enclosing function returns.

Apply this diff to fix the issue:

 if (inboxIdsToDeny.length > 0) {
-  consentToInboxIdsOnProtocolByAccount({
+  await consentToInboxIdsOnProtocolByAccount({
     account,
     inboxIds: inboxIdsToDeny,
     consent: "deny",
   });
 }

147-155: Refactor to eliminate code duplication between allow and block functions

The logic for constructing inboxIdsToDeny in blockGroup mirrors the construction of inboxIdsToAllow in allowGroupMutation. Extracting this shared logic into a helper function can enhance maintainability and reduce code duplication.

Consider creating a helper function like this:

function getInboxIds({
  includeAddedBy,
  includeCreator,
  group,
  groupCreator,
}: {
  includeAddedBy?: boolean;
  includeCreator?: boolean;
  group?: GroupType;
  groupCreator?: InboxId;
}): InboxId[] {
  const inboxIds: InboxId[] = [];

  if (includeAddedBy && group?.addedByInboxId) {
    inboxIds.push(group.addedByInboxId);
  }

  if (includeCreator && groupCreator) {
    inboxIds.push(groupCreator);
  }

  return inboxIds;
}

Then use it in both allowGroupMutation and blockGroup functions.

features/conversation/conversation-context.tsx (2)

Line range hint 91-105: Add error handling in conversation creation and message sending

When creating a new conversation and sending a message, exceptions might occur during createConversationByAccount or newConversation.send(payload). Consider adding error handling to manage potential failures and improve robustness.

Apply this diff to add try-catch blocks for error handling:

if (!conversation && peerAddress) {
+  try {
    const newConversation = await createConversationByAccount(
      currentAccount,
      peerAddress
    );
    updateNewConversation(newConversation.topic);
    await newConversation.send(payload);
    addConversationToConversationListQuery(
      currentAccount,
      newConversation
    );
    return;
+  } catch (error) {
+    // Handle error (e.g., log error, show notification)
+    console.error('Failed to create conversation or send message:', error);
+  }
}

166-166: Implement the logic for isBlockedConversation or remove the placeholder

The isBlockedConversation property uses a placeholder with a TODO comment:

isBlockedConversation: conversation?.state === "denied", // TODO: implement this

Would you like assistance in implementing the logic for isBlockedConversation? I can help by providing an implementation based on the intended behavior. If this feature is not yet needed, consider removing the placeholder to avoid confusion.

components/Chat/ChatGroupUpdatedMessage.tsx (1)

47-60: Refactor duplicate components into a single reusable component

The ChatGroupMemberJoined and ChatGroupMemberLeft components share a significant amount of duplicated code. Consider refactoring them into a single component to improve maintainability and reduce code duplication.

Here's how you might refactor the components:

type IChatGroupMemberUpdateProps = {
  inboxId: InboxId;
  translationKey: TxKeyPath;
};

function ChatGroupMemberUpdate({ inboxId, translationKey }: IChatGroupMemberUpdateProps) {
  const { themed } = useAppTheme();
  const currentAccount = useCurrentAccount();
  const { data } = useInboxProfileSocialsQuery(currentAccount!, inboxId);
  const { theme } = useAppTheme();

  const firstSocials = data?.[0];

  if (!firstSocials) {
    return null;
  }

  const readableName = getPreferredName(
    firstSocials,
    firstSocials.address ?? ""
  );
  const avatarUri = getPreferredAvatar(firstSocials);

  return (
    <HStack style={themed($memberContainer)}>
      <Pressable
        onPress={() => {
          navigate("Profile", {
            address: firstSocials.address ?? "",
          });
        }}
        style={themed($pressableContent)}
      >
        <Avatar
          size={theme.avatarSize.xs}
          uri={avatarUri}
          name={readableName}
        />
        <ChatGroupUpdateText weight="bold">{readableName} </ChatGroupUpdateText>
      </Pressable>
      <ChatGroupUpdateText>{translate(translationKey)}</ChatGroupUpdateText>
    </HStack>
  );
}

Update the usage in your main component:

{/* Member additions */}
{content.membersAdded.map((member) => (
- <ChatGroupMemberJoined
+ <ChatGroupMemberUpdate
    key={`joined-${member.inboxId}`}
    inboxId={member.inboxId as InboxId}
+   translationKey="group_member_joined"
  />
))}

{/* Member removals */}
{content.membersRemoved.map((member) => (
- <ChatGroupMemberLeft
+ <ChatGroupMemberUpdate
    key={`left-${member.inboxId}`}
    inboxId={member.inboxId as InboxId}
+   translationKey="group_member_left"
  />
))}
components/Chat/Message/message-content-types/message-reply.tsx (8)

40-41: Reminder: Address the TODO comment.

There's a // TODO comment indicating that this case needs handling. Consider implementing the appropriate logic to handle when replyMessageContent is falsy.

Would you like assistance in implementing this logic or opening a GitHub issue to track this task?


157-159: Consider fallback UI for missing replyMessageReference.

If replyMessageReference is not available, the component doesn't render MessageReplyReferenceContent. Consider providing a fallback UI or placeholder to inform the user that the original message is unavailable.


205-207: Clarify error tracking message.

The error message in sentryTrackError could be more descriptive. Consider including additional context to aid in debugging.

You might update it as follows:

sentryTrackError(
-  `Reply message reference message content is not handled with default text ${replyMessageSafeText}`
+  `Unhandled reply message content type. Fallback text: ${replyMessageSafeText}`
);

233-248: Avoid using non-serializable values in query keys.

In the useQuery hook, ensure that the query keys are serializable to prevent potential issues with caching and re-fetching.

Consider modifying the query options:

useQuery({
  ...getConversationMessageQueryOptions({
    account: currentAccount,
-   messageId,
+   messageId: messageId.toString(),
  }),
  enabled: !cachedReplyMessage,
});

174-183: Handle cases where content is neither string nor contains remoteAttachment.

If content doesn't match the expected types, ensure that the component handles it gracefully to prevent rendering issues.

Consider adding an else clause to handle unexpected content types.


67-91: Optimize conditional rendering of RemoteAttachmentImage.

The current check uses double negation (!!replyMessageContent.content.remoteAttachment). You can simplify the condition for better readability.

Update the condition as:

- {!!replyMessageContent.content.remoteAttachment && (
+ {replyMessageContent.content.remoteAttachment && (

93-97: Optimize conditional rendering of MessageText.

Similarly, simplify the condition when rendering MessageText.

Update the condition as:

- {!!replyMessageContent.content.text && (
+ {replyMessageContent.content.text && (

216-227: Avoid redundant error tracking in getReplyMessageSafeText.

Since errors are already caught and a fallback is returned, consider whether additional error logging is necessary.

features/conversation/dm-conversation.screen.tsx (3)

77-77: Simplify conditional rendering by removing redundant double negation

The use of !! in {!!conversation ? (...) : (...)} is unnecessary because the value will already be coerced to a boolean in the conditional expression. Removing the double negation simplifies the code without altering its functionality.

Apply this diff to remove the redundant double negation:

- {!!conversation ? (
+ {conversation ? (
    <ExistingDmConversation conversation={conversation} />
  ) : (
    <NewDmConversation peerAddress={peerAddress} />
  )}
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


216-216: Address TODO comments to implement empty states

There are TODO comments indicating that empty states need to be added for better user experience when there are no messages or conversations to display. Implementing these empty states will enhance usability and provide appropriate feedback to users.

Would you like assistance in designing and implementing the empty state components? I can help create a consistent and user-friendly interface.

Also applies to: 252-252


116-125: Implement optimistic updates for improved user experience

The commented-out code suggests adding optimistic updates to provide faster feedback to the user. Implementing this will make the application feel more responsive by updating the UI immediately after a user action, without waiting for the server response.

Would you like assistance in implementing these optimistic updates? I can help refactor the code to include this feature while ensuring data consistency.

Also applies to: 169-181

features/conversation/composer/composer.tsx (2)

67-71: Ensure onSend is not called with an empty object

All properties in IComposerSendArgs are optional, which could allow onSend to be called with an empty object. If at least one of text, remoteAttachment, or referencedMessageId is required for onSend to function correctly, consider enforcing this through type definitions or runtime validation.


13-13: Ensure consistent import path aliasing

The import path for SendAttachmentPreview is using an absolute path with @/features/.... Confirm that this aligns with your project's aliasing strategy and update it for consistency if necessary.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ef0e8d4 and 512abb5.

⛔ Files ignored due to path filters (1)
  • ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (81)
  • components/ActivityIndicator/ActivityIndicator.tsx (1 hunks)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx (0 hunks)
  • components/Chat/Attachment/attachment-container.tsx (1 hunks)
  • components/Chat/Attachment/attachment-loading.tsx (1 hunks)
  • components/Chat/Attachment/remote-attachment-image.tsx (1 hunks)
  • components/Chat/ChatGroupUpdatedMessage.tsx (2 hunks)
  • components/Chat/ConsentPopup/ConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx (1 hunks)
  • components/Chat/ConsentPopup/dm-consent-popup.tsx (1 hunks)
  • components/Chat/ConsentPopup/group-consent-popup.tsx (1 hunks)
  • components/Chat/Message/MessageTail.tsx (0 hunks)
  • components/Chat/Message/MessageTimestamp.tsx (0 hunks)
  • components/Chat/Message/V3Message.tsx (3 hunks)
  • components/Chat/Message/components/message-container.tsx (1 hunks)
  • components/Chat/Message/components/message-content-container.tsx (1 hunks)
  • components/Chat/Message/components/message-layout.tsx (1 hunks)
  • components/Chat/Message/components/message-repliable.tsx (1 hunks)
  • components/Chat/Message/components/message-space-between-messages.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-reply.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-simple-text.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-static-attachment.tsx (1 hunks)
  • components/Chat/Message/message-date-change.tsx (1 hunks)
  • components/Chat/Message/message-timestamp.tsx (1 hunks)
  • components/Chat/Message/message-utils.tsx (1 hunks)
  • components/Chat/Message/stores/message-store.tsx (0 hunks)
  • components/Conversation/V3Conversation.tsx (6 hunks)
  • components/GroupAvatar.tsx (2 hunks)
  • containers/GroupScreenMembersTable.tsx (2 hunks)
  • custom-eslint-plugin/padding-before-react-hooks.js (1 hunks)
  • design-system/loader.tsx (1 hunks)
  • features/conversation-list/hooks/useMessageText.ts (1 hunks)
  • features/conversation-list/useV3ConversationItems.ts (1 hunks)
  • features/conversation/composer/add-attachment-button.tsx (2 hunks)
  • features/conversation/composer/composer.tsx (9 hunks)
  • features/conversation/composer/send-attachment-preview.tsx (2 hunks)
  • features/conversation/conversation-context.tsx (6 hunks)
  • features/conversation/conversation-persisted-stores.ts (2 hunks)
  • features/conversation/conversation-service.ts (4 hunks)
  • features/conversation/dm-conversation.screen.tsx (1 hunks)
  • features/conversations/components/V3ConversationFromPeer.tsx (1 hunks)
  • features/conversations/utils/messageIsFromCurrentUser.ts (1 hunks)
  • features/search/components/NavigationChatButton.tsx (2 hunks)
  • hooks/useGroupConsent.ts (2 hunks)
  • hooks/useGroupCreator.ts (1 hunks)
  • hooks/useGroupMembers.ts (1 hunks)
  • navigation/useNavigation.tsx (1 hunks)
  • queries/MutationKeys.ts (0 hunks)
  • queries/QueryKeys.ts (2 hunks)
  • queries/queryClient.ts (1 hunks)
  • queries/useAddToGroupMutation.ts (1 hunks)
  • queries/useAllowGroupMutation.ts (0 hunks)
  • queries/useBlockGroupMutation.ts (1 hunks)
  • queries/useConversationQuery.ts (2 hunks)
  • queries/useConversationWithPeerQuery.ts (1 hunks)
  • queries/useDmConstentStateQuery.ts (1 hunks)
  • queries/useDmPeerInbox.ts (1 hunks)
  • queries/useGroupConsentQuery.ts (2 hunks)
  • queries/useGroupDescriptionMutation.ts (1 hunks)
  • queries/useGroupMembersQuery.ts (1 hunks)
  • queries/useGroupNameQuery.ts (1 hunks)
  • queries/useGroupPermissionPolicyQuery.ts (1 hunks)
  • queries/useGroupPinnedFrameQuery.ts (1 hunks)
  • queries/useGroupQuery.ts (1 hunks)
  • queries/usePromoteToAdminMutation.ts (1 hunks)
  • queries/usePromoteToSuperAdminMutation.ts (1 hunks)
  • queries/useRemoveFromGroupMutation.ts (1 hunks)
  • queries/useRevokeAdminMutation.ts (1 hunks)
  • queries/useRevokeSuperAdminMutation.ts (1 hunks)
  • queries/useV3ConversationListQuery.ts (1 hunks)
  • screens/ConversationReadOnly.tsx (1 hunks)
  • screens/GroupInvite.tsx (1 hunks)
  • screens/Navigation/Navigation.tsx (3 hunks)
  • screens/NewConversation/NewConversation.tsx (1 hunks)
  • screens/Profile.tsx (2 hunks)
  • utils/attachment/handleStaticAttachment.ts (0 hunks)
  • utils/capture-error.ts (1 hunks)
  • utils/groupUtils/groupActionHandlers.ts (2 hunks)
  • utils/xmtpRN/contacts.ts (2 hunks)
  • utils/xmtpRN/conversations.ts (3 hunks)
💤 Files with no reviewable changes (9)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx
  • components/Chat/ConsentPopup/ConsentPopup.tsx
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx
  • components/Chat/Message/MessageTail.tsx
  • components/Chat/Message/MessageTimestamp.tsx
  • components/Chat/Message/stores/message-store.tsx
  • queries/MutationKeys.ts
  • queries/useAllowGroupMutation.ts
  • utils/attachment/handleStaticAttachment.ts
✅ Files skipped from review due to trivial changes (4)
  • components/ActivityIndicator/ActivityIndicator.tsx
  • features/conversation-list/hooks/useMessageText.ts
  • features/conversations/components/V3ConversationFromPeer.tsx
  • features/conversations/utils/messageIsFromCurrentUser.ts
🧰 Additional context used
🪛 Biome (1.9.4)
features/conversation/dm-conversation.screen.tsx

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🔇 Additional comments (105)
components/Chat/Attachment/attachment-loading.tsx (1)

1-9: LGTM! Clean and focused implementation.

The component is well-structured with proper memoization and theme integration. The implementation follows React best practices and maintains a single responsibility.

components/Chat/Message/components/message-space-between-messages.tsx (1)

5-10: Add TypeScript types and documentation

While the implementation is clean, it would benefit from better typing and documentation.

queries/queryClient.ts (1)

10-10: Verify libXmtp's retry mechanism implementation.

Before globally disabling React Query's retry mechanism, we should verify that libXmtp's retry implementation adequately handles all failure scenarios.

✅ Verification successful

Based on the search results, I can now generate the final response as I have sufficient evidence about libXmtp's retry mechanism implementation:

Disabling React Query's retry mechanism is appropriate as libXmtp has comprehensive retry handling

The codebase shows a robust retry implementation through:

  • A dedicated retryWithBackoff utility that handles retries with exponential backoff
  • Default configuration of 5 retries with delays from 1s to 30s
  • Implementation in critical XMTP operations:
    • Conversation streaming
    • Message streaming
    • Client reconnection
    • Database reconnection
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for libXmtp retry configuration and implementation
rg -A 5 "retry|backoff|reconnect" --type ts

Length of output: 19899

queries/useGroupPinnedFrameQuery.ts (1)

Line range hint 18-19: Implementation appears incomplete

The function returns an empty string and has a commented-out implementation. This suggests the feature might not be fully implemented or tested.

Let's check if this is implemented elsewhere or if there are related issues:

hooks/useGroupCreator.ts (2)

3-4: LGTM! Clean import organization.

The import statements are well-organized, with types imported separately.


6-6: Verify the impact of making topic parameter required.

The change from topic?: ConversationTopic | undefined to topic: ConversationTopic is a breaking change that requires all callers to provide a defined topic. While this improves type safety and aligns with the query's enabled condition (!!topic), we should verify that all call sites are prepared for this change.

✅ Verification successful

Making topic parameter required is safe

The change is safe because:

  1. The only usage of useGroupCreator is in useGroupConsent.ts where it's called with a topic parameter that comes from the component's parameters
  2. The hook's implementation already assumes topic is required as shown by the enabled: !!topic condition in the query configuration
  3. The type change aligns with the actual usage pattern in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of useGroupCreator to verify they handle the breaking change

# Search for direct usage of the hook
echo "Direct usages of useGroupCreator:"
rg -A 2 "useGroupCreator\(" --type ts --type tsx

# Search for potential undefined topics being passed
echo -e "\nPotential undefined topics being passed:"
ast-grep --pattern 'useGroupCreator($topic)' | rg -B 2 -A 2 'useGroupCreator'

Length of output: 330


Script:

#!/bin/bash
# Let's try a different approach to find usages of useGroupCreator

# First, let's find all TypeScript files
echo "Finding TypeScript files that import or use useGroupCreator:"
rg -l "useGroupCreator" --type typescript

# Then, let's look at the actual usage contexts with more lines of context
echo -e "\nUsage contexts of useGroupCreator:"
rg -B 3 -A 3 "useGroupCreator" --type typescript

# Let's also check for any potential imports of this hook
echo -e "\nImport statements for useGroupCreator:"
rg "import.*useGroupCreator" --type typescript

Length of output: 553


Script:

#!/bin/bash
# Let's try without specifying the file type and use file extensions directly

# Find all files containing useGroupCreator
echo "Files containing useGroupCreator:"
rg -l "useGroupCreator" "*.ts" "*.tsx" || true

# Look at usage contexts
echo -e "\nUsage contexts of useGroupCreator:"
rg -B 3 -A 3 "useGroupCreator" || true

# Check for imports
echo -e "\nImport statements for useGroupCreator:"
rg "import.*useGroupCreator" || true

# Also check the hook definition file itself
echo -e "\nHook definition:"
cat hooks/useGroupCreator.ts || true

Length of output: 4083

components/Chat/Message/components/message-container.tsx (3)

1-3: Remove unused theme import.

The useAppTheme hook is imported but its returned theme object is not used in the component.


5-27: Well-structured message container implementation!

The component is well-implemented with proper TypeScript types, memoization for performance, and correct flex layout logic for message alignment. The code is clean and maintainable.


9-11: 🛠️ Refactor suggestion

Remove unused hook call.

The useAppTheme hook is called but its returned value is never used, which could cause unnecessary re-renders.

 export const MessageContainer = memo(function MessageContainer(props: {
   children: React.ReactNode;
   fromMe: boolean;
 }) {
   const { children, fromMe } = props;
-
-  const { theme } = useAppTheme();

Likely invalid or redundant comment.

queries/useGroupQuery.ts (2)

3-3: LGTM!

The import statement is clean and properly organized.


9-9: LGTM!

Good change to make topic required. This improves type safety and makes the function's requirements more explicit.

components/Chat/Attachment/attachment-container.tsx (2)

8-10: LGTM!

Clean implementation of props destructuring and theme usage.


15-24: Consider consistent aspect ratio handling and review tertiary fill usage

The aspect ratio is hardcoded but commented as "default". Consider making it configurable through props for better flexibility.

utils/capture-error.ts (1)

1-4: LGTM! Clean and well-organized imports

The imports are well-structured and each has a clear purpose in the error handling implementation.

components/Chat/Message/message-content-types/message-remote-attachment.tsx (2)

1-8: LGTM! Well-structured imports and type definitions

The imports are specific and the type definition properly leverages the XMTP SDK types.


15-18: ⚠️ Potential issue

Empty TODO needs clarification or implementation

The string content handling returns null without explanation, and the TODO comment lacks context. This could lead to confusion or missed edge cases.

Let's check if string content is a valid use case:

#!/bin/bash
# Search for any string-based message content handling in the codebase
rg -A 5 "content\(\) as string" || rg -A 5 "typeof content === \"string\""
components/Chat/Message/components/message-content-container.tsx (2)

5-31: Well-structured component with good performance optimization

The component is well-implemented with:

  • Proper use of memo for performance optimization
  • Clear conditional styling logic
  • Consistent theme usage

21-21: Verify maxWidth constraint across different screen sizes

The fixed maxWidth of 75% might not provide optimal layout on all screen sizes, especially on mobile devices.

✅ Verification successful

Fixed maxWidth is appropriate for chat message bubbles

The 75% maxWidth constraint is a common and appropriate design pattern for chat message bubbles. The codebase shows consistent width handling where:

  • Message containers use percentage-based widths for flexibility
  • Other components (reactions, avatars, etc.) use theme-based fixed widths for consistency
  • No media queries are needed as the percentage-based width naturally adapts to screen sizes

The 75% maxWidth ensures:

  • Messages remain readable on all devices
  • Leaves space for avatars and reactions
  • Maintains a chat-like appearance with bubbles not spanning the full width
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for responsive width adjustments in other message components
rg -l "maxWidth.*%" --type tsx components/Chat/

Length of output: 77


Script:

#!/bin/bash
# Search for responsive width adjustments in message components using ripgrep without file type restriction
rg "maxWidth.*%" components/Chat/

# Also check for any media queries or responsive design patterns
rg "@media" components/Chat/

# Look for any style configurations related to width in the message components
rg "width" components/Chat/Message/

Length of output: 2761

queries/useDmPeerInbox.ts (1)

9-12: LGTM! Improved naming clarity

The renaming from dmPeerInboxQueryKey to dmPeerInboxIdQueryKey better reflects the purpose of the query key. The structure follows React Query best practices and maintains type safety.

design-system/loader.tsx (1)

1-2: Standardize ActivityIndicator imports across the codebase

While the imports here are clean, there are inconsistencies in how ActivityIndicator is imported across the codebase (from 'react-native', 'react-native-paper', and '@components/ActivityIndicator').

Let's verify the current state of ActivityIndicator imports:

#!/bin/bash
# Search for all ActivityIndicator imports
echo "ActivityIndicator import patterns:"
rg "import.*ActivityIndicator.*from\s+['\"](@.*|react-native.*|.*paper.*)['\"]" --type tsx --type ts

# Search for direct ActivityIndicator usage
echo -e "\nActivityIndicator component usage:"
rg "<ActivityIndicator" --type tsx --type ts
components/Chat/Message/message-content-types/message-simple-text.tsx (5)

1-10: LGTM! Imports are well-organized.

The imports are properly structured with clear separation between internal components, hooks, and external dependencies.


12-14: LGTM! Component is properly memoized with typed props.

The use of memo is appropriate for optimizing chat message rendering performance, and the props are well-typed using the XMTP SDK types.


15-17: Add error handling for message content extraction.

The current implementation assumes message content will always be available. Consider adding error handling to gracefully handle cases where content might be undefined or invalid.


19-21: LGTM! Efficient state management implementation.

The use of useSelect with specific field selection optimizes performance by preventing unnecessary re-renders when other context values change.


23-34: Enhance accessibility for chat messages.

While the UI structure is solid, consider adding accessibility attributes to improve the experience for screen reader users.

features/conversation-list/useV3ConversationItems.ts (2)

22-37: Excellent refactoring for clarity and performance!

The new implementation shows several improvements:

  • Using Set for efficient lookups of pinned and deleted topics
  • Breaking down conditions into well-named variables for better readability
  • Using functional programming patterns with Object.entries and filter
  • Proper dependency tracking in useMemo

39-39: LGTM - Maintains backwards compatibility

The return statement preserves the expected interface by returning filtered data under the data property along with other query properties.

queries/useAddToGroupMutation.ts (2)

Line range hint 10-10: Verify if group refresh functionality is needed

There's commented out code related to group refreshing:

  • Commented import: import { refreshGroup } from "../utils/xmtpRN/conversations"
  • Commented usage: refreshGroup(account, topic)

This might indicate missing functionality or technical debt.

Let's check if this functionality is implemented elsewhere:

Also applies to: 46-46


Line range hint 35-38: Verify error tracking consistency

The error handling uses both logger.warn and sentryTrackError. Let's verify this pattern is consistent across other mutations.

✅ Verification successful

Let me gather more specific information about the error handling patterns.


Let me try a different approach to check the error handling patterns.


Error handling pattern is consistent across mutations

The error handling implementation in useAddToGroupMutation.ts follows the same pattern used consistently across other mutation hooks in the codebase. All examined mutations:

  • Import and use both logger and sentryTrackError
  • Call logger.warn with a mutation-specific message
  • Call sentryTrackError with the error object
  • Follow the same order of operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check error handling patterns in other mutation hooks
echo "Searching for error handling patterns in mutations..."
rg "onError.*sentryTrackError" -t ts

echo "Checking logger usage in mutations..."
rg "logger\.(warn|error).*Mutation" -t ts

Length of output: 1193


Script:

#!/bin/bash
# Check complete error handling implementation in mutation files
echo "Checking complete error handling in GroupDescriptionMutation..."
rg "onError.*\{[\s\S]*?\}" queries/useGroupDescriptionMutation.ts -A 5

echo "Checking complete error handling in RemoveFromGroupMutation..."
rg "onError.*\{[\s\S]*?\}" queries/useRemoveFromGroupMutation.ts -A 5

echo "Checking complete error handling in RevokeSuperAdminMutation..."
rg "onError.*\{[\s\S]*?\}" queries/useRevokeSuperAdminMutation.ts -A 5

echo "Checking complete error handling in PromoteToAdminMutation..."
rg "onError.*\{[\s\S]*?\}" queries/usePromoteToAdminMutation.ts -A 5

Length of output: 830


Script:

#!/bin/bash
# Check for both logger.warn and sentryTrackError usage in mutation files
echo "Checking error handling in GroupDescriptionMutation..."
rg "(logger\.warn|sentryTrackError)" queries/useGroupDescriptionMutation.ts -B 2 -A 2

echo "Checking error handling in RemoveFromGroupMutation..."
rg "(logger\.warn|sentryTrackError)" queries/useRemoveFromGroupMutation.ts -B 2 -A 2

echo "Checking error handling in RevokeSuperAdminMutation..."
rg "(logger\.warn|sentryTrackError)" queries/useRevokeSuperAdminMutation.ts -B 2 -A 2

echo "Checking error handling in PromoteToAdminMutation..."
rg "(logger\.warn|sentryTrackError)" queries/usePromoteToAdminMutation.ts -B 2 -A 2

Length of output: 2529

screens/ConversationReadOnly.tsx (1)

53-53: LGTM! Prop name change improves clarity and adds null safety.

The change from data={messages?.ids} to messageIds={messages?.ids ?? []} is a good improvement as it:

  • Uses a more descriptive prop name
  • Provides a safe fallback with empty array
queries/useBlockGroupMutation.ts (2)

Line range hint 32-49: Well-implemented mutation with proper optimistic updates and error handling

The mutation implementation follows React Query best practices with:

  • Proper optimistic updates using onMutate
  • Error handling with state rollback
  • Appropriate error tracking and logging

25-29: Verify topic conversion and consent value

The implementation looks correct, but please verify:

  1. That getV3IdFromTopic handles all possible topic formats correctly
  2. The hardcoded "deny" consent value matches the protocol specifications

Let's verify the implementation:

✅ Verification successful

Let's verify the implementation of getV3IdFromTopic function:


Let's check the implementation of getV3IdFromTopic and the consent type definition:


Implementation verified and looks correct

The verification confirms:

  1. getV3IdFromTopic correctly handles both formats:
    • Full topic format: /xmtp/mls/1/g-<id>/proto
    • Direct ID format: <id>
      As evidenced by the implementation and test cases.
  2. The "deny" consent value is valid as the type definition in ConsentToGroupsOnProtocolParams explicitly allows "allow" | "deny" values.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for all usages of getV3IdFromTopic to understand supported formats
ast-grep --pattern 'getV3IdFromTopic($_)'

# Check for all consent values used in the codebase
rg -B 2 -A 2 'consent: "(allow|deny)"'

Length of output: 6849


Script:

#!/bin/bash
# Check the implementation of getV3IdFromTopic
ast-grep --pattern 'function getV3IdFromTopic($_) {
  $$$
}'

# Also check for any test cases
cat utils/groupUtils/groupId.test.ts

Length of output: 1764


Script:

#!/bin/bash
# Find the implementation of getV3IdFromTopic
fd groupId.ts --exec cat {}

# Check the type definition for consent
ast-grep --pattern 'type $_ = {
  $$$
  consent: $_
  $$$
}'

Length of output: 1972

queries/useGroupNameQuery.ts (3)

Line range hint 13-24: LGTM! Query implementation looks robust.

The query implementation correctly:

  • Handles the case when conversation is undefined
  • Checks for the correct conversation version
  • Only enables the query when all required conditions are met

Line range hint 26-57: LGTM! Helper functions are well-structured.

The utility functions for managing query data (getGroupNameQueryData, setGroupNameQueryData, cancelGroupNameQuery, invalidateGroupNameQuery) are well-implemented with proper typing and consistent usage of the query client.


4-5: Verify the behavior consistency after query function change.

The switch from useConversationScreenQuery to useConversationQuery appears to be part of a larger refactoring effort. While the change looks straightforward, we should ensure the new query function maintains the same contract, especially regarding the conversation data structure and version handling.

Let's verify the implementation of both query functions:

Also applies to: 11-11

✅ Verification successful

Import change is safe and maintains behavior consistency

The verification shows that:

  1. The useConversationQuery function maintains a compatible interface, accepting account and topic parameters
  2. The usage in useGroupNameQuery correctly utilizes the new query function to fetch conversation data
  3. The data structure remains consistent, as evidenced by the type checking on conversation.version and conversation.groupName()
  4. No other files in the codebase are using the old useConversationScreenQuery, indicating a clean migration
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Compare the implementations of both query functions to ensure compatibility

# Search for the old implementation
echo "Old implementation (useConversationScreenQuery):"
rg -A 10 "useConversationScreenQuery.*=.*" 

# Search for the new implementation
echo "New implementation (useConversationQuery):"
rg -A 10 "useConversationQuery.*=.*"

# Check for any other files that might be affected by this change
echo "Other potential usage sites:"
rg "useConversationScreenQuery"

Length of output: 1089


Script:

#!/bin/bash
# Let's check the usage of useConversationQuery in useGroupNameQuery to understand the impact
echo "Usage in useGroupNameQuery:"
rg -A 10 "useConversationQuery" queries/useGroupNameQuery.ts

# Let's also check the git history for the specific file to see the change
echo "Recent changes:"
git log -n 1 --patch queries/useGroupNameQuery.ts

Length of output: 683

components/Chat/ConsentPopup/consent-popup.design-system.tsx (1)

14-16: LGTM!

Clean implementation that correctly forwards all props to the Text component.

queries/useGroupConsentQuery.ts (4)

1-7: LGTM! Good type abstraction

The introduction of GroupConsentQueryData type alias provides a clean abstraction layer over the SDK's ConsentState, making future type changes more maintainable.


9-13: LGTM! Improved type safety

Making topic required and updating queryOptions to use ConsentState improves type safety and makes the API contract clearer.


27-43: LGTM! Consistent type usage

The utility functions are well-typed and maintain consistency with the GroupConsentQueryData type alias throughout the codebase.


Line range hint 1-43: Verify impact on existing group messaging functionality

Since this file handles critical group consent functionality and the PR involves messaging improvements, let's verify that existing group messaging features aren't affected by these type changes.

✅ Verification successful

Based on the search results, I can now make an informed decision about the impact of the group consent changes. The results show:

  1. The group consent query functions are used in several critical files:

    • features/conversation/conversation-context.tsx
    • queries/useGroupConsentQuery.ts
    • queries/useBlockGroupMutation.ts
    • hooks/useGroupConsent.ts
  2. No TODO/FIXME comments related to group messaging functionality issues were found in the codebase.

  3. The group consent functionality is used in a well-structured way across the application, with proper separation of concerns.

  4. The changes in the review are type-related changes that maintain the existing functionality while improving type safety.

Given this evidence, I can provide the final response:

Group consent changes maintain existing functionality while improving type safety

The changes to the group consent query functions are type-related improvements that don't affect the runtime behavior. The usage across the codebase shows proper integration with no known issues or breaking changes to existing group messaging functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential breaking changes in group messaging functionality

# Look for existing uses of group consent queries that might be affected
echo "Checking for affected group consent query usage..."
rg -l "useGroupConsentQuery|getGroupConsentQueryData|setGroupConsentQueryData" --type typescript

# Check for any TODO or FIXME comments related to group messaging
echo "Checking for known issues in group messaging..."
rg -i "TODO|FIXME|XXX" -C 2 "group.*message|message.*group" --type typescript

Length of output: 464


Script:

#!/bin/bash
# Let's try without typescript file type specification
# Look for existing uses of group consent queries that might be affected
echo "Checking for affected group consent query usage..."
rg -l "useGroupConsentQuery|getGroupConsentQueryData|setGroupConsentQueryData"

# Check for any references to group messaging functionality
echo "Checking group messaging related code..."
rg "group.*message|message.*group" -C 2

# Check for any TODO or FIXME comments
echo "Checking for known issues..."
rg -i "TODO|FIXME|XXX" -C 2

Length of output: 115967

queries/useDmConstentStateQuery.ts (1)

1-54: Verify integration with conversation context

Let's verify the integration with the conversation context as mentioned in the AI summary.

✅ Verification successful

Integration with conversation context is properly implemented

The verification shows that the useDmConsentQuery hook is correctly integrated within the conversation context:

  • It's properly imported and used in features/conversation/conversation-context.tsx
  • The loading and consent states are correctly propagated through the context
  • The consent states are properly handled in the UI components with appropriate loading states and consent popups
  • Error states and edge cases are handled through the isAllowedConversation and isLoadingConversationConsent flags
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check how this hook is used in the conversation context
ast-grep --pattern 'useDmConsentQuery' 

# Check for proper error handling in the conversation context
rg -A 5 'isLoadingConversationConsent|isAllowedConversation'

Length of output: 5846

hooks/useGroupMembers.ts (2)

Line range hint 1-56: LGTM: Well-structured hook implementation

The hook is well-organized with clear separation of concerns:

  • Proper use of React Query patterns
  • Clean mutation management
  • Consistent parameter passing
  • Good type safety with TypeScript

17-17: Breaking change: topic parameter is now required

The change improves type safety by requiring a valid ConversationTopic. However, this is a breaking change that requires all consumers of this hook to provide a topic.

Let's verify the impact of this breaking change:

✅ Verification successful

Based on the search results, I can now generate the final response:

All usages of useGroupMembers already provide the required topic parameter

The codebase scan shows that all 9 instances where useGroupMembers is used already provide the topic parameter, either directly or through non-null assertions (!):

  • screens/Profile.tsx: uses groupTopic!
  • screens/NewConversation/NewConversation.tsx: uses route.params?.addingToGroupTopic!
  • hooks/useGroupPendingRequests.ts: uses topic
  • containers/GroupScreenName.tsx: uses topic
  • components/GroupAvatar.tsx: uses topic!
  • containers/GroupScreenImage.tsx: uses topic
  • containers/GroupScreenDescription.tsx: uses topic
  • containers/GroupScreenAddition.tsx: uses topic
  • containers/GroupScreenMembersTable.tsx: uses (topic ?? group?.topic)!

Making the parameter required is safe as all consumers are already treating it as required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of useGroupMembers to ensure they provide the required topic

# Search for direct hook usage
echo "Searching for useGroupMembers usage patterns..."
rg -A 3 "useGroupMembers\(" 

# Search for potential destructuring patterns
echo "Searching for destructured hook usage..."
ast-grep --pattern 'const { $$$$ } = useGroupMembers($$$)'

Length of output: 2901

queries/useConversationQuery.ts (3)

7-7: LGTM! Well-defined type using ReturnType

The ConversationQueryData type definition ensures type safety by deriving from the return type of getConversation.


Line range hint 30-65: LGTM! Well-structured query cache management

The utility functions are well-organized, maintain type safety, and follow React Query best practices. Each function has a clear, single responsibility:

  • invalidateConversationQuery: Invalidates specific conversation cache
  • setConversationQueryData: Updates cache with new data
  • refetchConversationQuery: Triggers a fresh fetch
  • getConversationQueryData: Retrieves cached data

9-15: Verify the impact of includeSync: false

The function looks well-structured, but let's verify the implications of hardcoding includeSync: false.

✅ Verification successful

Based on the search results, I can now generate the final response as I have sufficient information about the includeSync parameter and its usage across the codebase:

Setting includeSync: false is appropriate for this query

The includeSync parameter controls whether to perform an immediate sync operation on conversations. Setting it to false in this context is correct because:

  • This function serves as a base query to fetch conversation data, where immediate sync isn't necessary
  • Other specialized queries that need sync (like useConversationWithPeerQuery) explicitly set includeSync: true
  • The default value across most conversation retrieval functions is false, maintaining consistency
  • The V3 conversation list handles bulk syncing separately with syncAllConversations()
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of includeSync to understand its purpose and impact
rg -A 3 "includeSync" 

# Search for any documentation or comments about sync behavior
ast-grep --pattern 'getConversationByTopicByAccount' 

Length of output: 9711

custom-eslint-plugin/padding-before-react-hooks.js (2)

47-54: Tests needed for new ESLint rule functionality

The rule still lacks test coverage, especially for the new first-statement-in-function behavior.


Line range hint 1-70: Verify PR scope alignment

This ESLint rule modification appears unrelated to the PR's stated objectives of "messaging attachments + new dm convo improvements". Consider moving this change to a separate PR for better change isolation and review focus.

Let's verify if this change is referenced in any messaging-related files:

queries/useRevokeAdminMutation.ts (1)

17-17: LGTM! Type safety improvement

The change to make topic required enhances type safety by ensuring a valid ConversationTopic is always provided.

navigation/useNavigation.tsx (1)

25-27: Verify the migration progress to useRouteParams

Previous review identified the need to migrate direct params access to this new hook. Let's check the current adoption status.

#!/bin/bash
# Description: Check the adoption status of useRouteParams vs direct params access

# Check for direct useRoute().params usage that could be migrated
echo "Files still using direct params access:"
rg "useRoute\(\)\.params" --type tsx

# Check current adoption of useRouteParams
echo -e "\nFiles already using useRouteParams:"
rg "useRouteParams" --type tsx

# Focus on messaging-related files
echo -e "\nMessaging-related files params usage:"
fd -e tsx | grep -i "message\|conversation\|chat" | xargs rg "\.params"
queries/useRevokeSuperAdminMutation.ts (1)

18-18: LGTM: Type signature change improves type safety

The change to make topic a required parameter is a good improvement that enforces better type safety at compile time.

queries/usePromoteToSuperAdminMutation.ts (1)

17-19: Verify all hook usages for the breaking change

The topic parameter is now required instead of optional. This is a breaking change that needs verification across all usages of this hook.

queries/useRemoveFromGroupMutation.ts (1)

Line range hint 10-10: Verify the removal of group refresh logic

The commented out refreshGroup import and function call suggest that group state synchronization might have changed. Let's verify if this was intentional and if there are any potential race conditions.

Also applies to: 61-61

utils/groupUtils/groupActionHandlers.ts (1)

4-4: Verify the breaking change from Consent to ConsentState.

The type change from Consent to ConsentState is a breaking change that needs verification:

  1. Ensure all callers of groupRemoveRestoreHandler are updated to use the new type
  2. Confirm that ConsentState from XMTP SDK includes "denied" as a valid state

Let's verify the impact:

Also applies to: 15-15

✅ Verification successful

Based on the shell script results, I can now generate the final response:

The type change from Consent to ConsentState is correctly implemented

The verification shows:

  1. All callers of groupRemoveRestoreHandler are already using the new ConsentState type:
    • components/Chat/ConsentPopup/group-consent-popup.tsx
    • containers/GroupScreenConsentTable.tsx
  2. ConsentState from XMTP SDK includes "denied" as a valid state, confirmed by multiple usages in the codebase where ConsentState.DENIED is used for consent checks.
  3. The old Consent type was not a direct type but rather a collection of consent-related type definitions, making this a clean migration to the SDK's type.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of groupRemoveRestoreHandler and ConsentState type

# Find all usages of groupRemoveRestoreHandler
echo "Checking groupRemoveRestoreHandler usage:"
rg -A 3 "groupRemoveRestoreHandler"

# Find Consent type definition and usage
echo -e "\nChecking old Consent type usage:"
rg "type.*Consent.*=|interface.*Consent"

# Verify ConsentState type from SDK
echo -e "\nChecking ConsentState definition:"
rg -A 5 "ConsentState"

Length of output: 10591

components/Chat/Message/message-date-change.tsx (2)

1-15: LGTM! Well-organized imports

The imports are properly structured with clear separation between internal components, design system, and external dependencies.


48-72: Remove commented debug code

The component's rendering logic is well-structured, but contains commented debug code that should be removed.

   <AnimatedHStack
     layout={theme.animation.reanimatedSpringLayoutTransition}
     style={{
-      // ...debugBorder("red"),
       alignSelf: "center",
       alignItems: "center",

Also, verify that all theme values being used are properly typed and available in the theme object.

✅ Verification successful

Remove commented debug code

The component's rendering logic is well-structured, but contains commented debug code that should be removed.

   <AnimatedHStack
     layout={theme.animation.reanimatedSpringLayoutTransition}
     style={{
-      // ...debugBorder("red"),
       alignSelf: "center",
       alignItems: "center",

The theme spacing values "4xs" and "sm" used in the component are properly defined in the theme system (theme/spacing.ts).

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify theme values used in the component
# Search for theme spacing definitions to ensure "4xs" and "sm" exist

rg -A 5 "spacing.*=.*{" 

Length of output: 115


Script:

#!/bin/bash
# Search for theme spacing definitions with proper regex
rg -A 10 'spacing\s*[:=]' 

# Also search for specific spacing values
rg '"4xs"|"sm"' 

Length of output: 3521

queries/usePromoteToAdminMutation.ts (1)

Line range hint 11-11: Verify the impact of removing group refresh functionality.

The commented out refreshGroup import and usage suggests a potential change in how group data is refreshed. Please verify that:

  1. Group data remains consistent after promoting admins
  2. The UI updates correctly without manual refresh
  3. There are no race conditions with the optimistic updates

Let's check for alternative refresh mechanisms:

Also applies to: 67-67

components/Chat/Message/components/message-layout.tsx (1)

30-32: 🛠️ Refactor suggestion

Add error handling for the reply action.

The setCurrentConversationReplyToMessageId call should be wrapped in a try-catch block to handle potential failures gracefully.

 const handleReply = useCallback(() => {
-    setCurrentConversationReplyToMessageId(messageId);
+    try {
+      setCurrentConversationReplyToMessageId(messageId);
+    } catch (error) {
+      console.error('Failed to set reply message:', error);
+      // Consider showing a user-friendly error message
+    }
 }, [messageId]);

Likely invalid or redundant comment.

features/conversation/composer/send-attachment-preview.tsx (1)

1-11: LGTM: Clean import organization and proper design system adoption

The replacement of ActivityIndicator with the new Loader component and the organization of imports look good. The changes align well with the design system adoption.

features/search/components/NavigationChatButton.tsx (1)

41-43: ⚠️ Potential issue

Update navigation to use the new DmConversation screen

This component is still using the old "Conversation" screen while other parts of the codebase have moved to "DmConversation". This inconsistency should be addressed as part of the DM conversation improvements.

Apply this change to align with the new navigation pattern:

-    navigate("Conversation", {
-      mainConversationWithPeer: address,
-    });
+    navigate("DmConversation", {
+      peerAddress: address,
+    });
components/Chat/Message/message-timestamp.tsx (2)

19-28: Well-structured component with proper optimization

The component is correctly memoized and efficiently uses context hooks for state management.


73-89: Clean and efficient rendering logic

The component's rendering logic is well-structured with proper conditional rendering and component composition.

queries/useGroupMembersQuery.ts (1)

Line range hint 35-36: Address TODO for multiple addresses support

The current implementation only supports a single address. Let's verify if there are any requirements or related issues for multiple address support.

Also applies to: 61-62

components/Chat/Message/components/message-repliable.tsx (2)

Line range hint 19-23: LGTM! Component structure and naming are well-organized.

The renamed component follows better naming conventions, and the memoization with proper TypeScript types will help with performance in message lists.


Line range hint 41-67: LGTM! Well-implemented animations with proper interpolation.

The animation configuration provides smooth transitions with:

  • Proper opacity fading (0 -> 0 -> 1)
  • Natural scaling effect (0 -> 0 -> 1)
  • Constrained translation using clamp
features/conversation/conversation-persisted-stores.ts (2)

10-10: LGTM! Improved type safety with MessageId

The change from string to MessageId type enhances type safety and better integrates with the XMTP SDK's type system.

Also applies to: 31-31


31-31: Verify the MessageId type compatibility across the codebase

The change from string to MessageId could be a breaking change. Let's verify that all consumers of this store handle the new type correctly.

✅ Verification successful

Let me generate additional verification scripts to check the MessageId type definition and its usage patterns.

Let's analyze the type definition and usage patterns to ensure type compatibility across the codebase.


Based on the analysis of the shell script results, I can now provide the final response:

The MessageId type change is safe and properly implemented

The codebase consistently uses the MessageId type from @xmtp/react-native-sdk, and all interactions with replyingToMessageId are type-safe:

  • The type is properly imported from the SDK in all relevant files
  • All consumers handle it correctly through the store selectors
  • The type is used consistently in message references and reply functionality
  • No string type assertions or unsafe comparisons were found
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for usage of replyingToMessageId to ensure type compatibility

# Find direct usage of replyingToMessageId
echo "Checking direct usage of replyingToMessageId:"
rg -A 2 "replyingToMessageId" --type ts

# Find store selector usage that might access this field
echo "Checking store selectors that might access replyingToMessageId:"
rg -A 5 "useConversationPersistedStoreState|useCurrentConversationPersistedStoreState" --type ts

# Find any string type assertions or comparisons with message IDs
echo "Checking for potential type mismatches:"
ast-grep --pattern 'replyingToMessageId === "$_"'

Length of output: 8398


Script:

#!/bin/bash
# Check MessageId type definition and its usage
echo "Checking MessageId type definition:"
rg -p "type.*MessageId" --type ts

echo "Checking MessageId imports and usage:"
rg "MessageId" --type ts -B 2 -A 2

echo "Checking message ID comparisons and assignments:"
ast-grep --pattern 'replyingToMessageId = $_'
ast-grep --pattern 'replyingToMessageId: $_'

Length of output: 36585

queries/QueryKeys.ts (2)

30-30: LGTM! Consistent enum addition

The new DM_CONSENT enum entry follows the established naming pattern and is appropriately placed within the consent-related section.


107-110: LGTM! Verify usage in related components

The dmConsentQueryKey function follows the established pattern perfectly. The implementation is consistent with other query key functions, particularly matching the structure of groupConsentQueryKey.

Let's verify the usage of this new query key:

✅ Verification successful

LGTM! The query key is properly integrated and used

The verification shows that dmConsentQueryKey is well integrated into the codebase:

  • Defined in queries/QueryKeys.ts
  • Properly used in useDmConstentStateQuery.ts for querying, getting, and setting DM consent state
  • Integrated in the conversation context (features/conversation/conversation-context.tsx)
  • Used with the DM consent popup component (components/Chat/ConsentPopup/dm-consent-popup.tsx)

The implementation follows the same pattern as other consent-related queries and is used consistently throughout the related components.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the usage of dmConsentQueryKey in the codebase
# Expected: Should find usage in DM consent related components/hooks

# Search for direct usage of the query key
echo "Checking direct usage of DM_CONSENT:"
rg "DM_CONSENT" --type ts --type tsx

# Search for function usage
echo -e "\nChecking usage of dmConsentQueryKey:"
rg "dmConsentQueryKey" --type ts --type tsx

# Search for related consent hooks/components
echo -e "\nChecking for related consent components:"
rg "DmConsent" --type ts --type tsx

Length of output: 469


Script:

#!/bin/bash
# Let's try without specifying file types since tsx isn't recognized
# Search for direct usage of the query key and related patterns

echo "Checking direct usage of DM_CONSENT:"
rg "DM_CONSENT"

echo -e "\nChecking usage of dmConsentQueryKey:"
rg "dmConsentQueryKey"

echo -e "\nChecking for related consent components:"
rg "DmConsent"

# Also search for any files that might contain these patterns
echo -e "\nFinding relevant files:"
fd -e ts -e tsx

Length of output: 25177

components/Chat/Message/message-utils.tsx (1)

Line range hint 19-30: Restore type safety by utilizing the commented-out type definition.

The commented-out DecodedMessageAllTypes type union should be uncommented and used instead of any in the type guard functions. This would improve type safety and make the code more maintainable.

Let's verify if this type is used elsewhere in the codebase:

features/conversation/composer/add-attachment-button.tsx (2)

8-14: LGTM! Well-organized imports for media handling and error tracking.

The new imports are properly organized and provide essential functionality for image compression, media selection, error tracking, and encryption.


Line range hint 38-42: Verify removal of exported type.

The SelectedAttachment type is no longer exported. Let's verify it's not used in other files.

✅ Verification successful

Safe to remove type from exports - no external usage found

The search results confirm that SelectedAttachment type is only used within add-attachment-button.tsx and has no external dependencies. The removal from exports is safe.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any usage of SelectedAttachment type in other files
rg "SelectedAttachment" --type typescript

Length of output: 76


Script:

#!/bin/bash
# Search for any usage of SelectedAttachment type using ripgrep
# Note: Using -t ts and -t tsx for TypeScript files
rg "SelectedAttachment" -t ts -t tsx

Length of output: 64


Script:

#!/bin/bash
# Search for any usage of SelectedAttachment in all files
# Using --type-add to define TypeScript patterns first
rg --type-add 'typescript:*.{ts,tsx}' --type typescript "SelectedAttachment"

Length of output: 161

queries/useV3ConversationListQuery.ts (1)

22-22: LGTM! Type export enhances code reusability.

The export of V3ConversationListType improves modularity by allowing other components to reuse this type definition, which is particularly useful for maintaining type consistency across the conversation-related features.

features/conversation/conversation-service.ts (3)

12-12: LGTM: MessageId import addition

The addition of MessageId import aligns with the type safety improvements across the codebase.


23-23: Verify logger implementation

The addition of the logger import is good for centralized error handling. Let's verify the logger implementation.

✅ Verification successful

Based on the search results, I can now generate the final response since I have enough information about the logger implementation and its usage across the codebase.

Logger implementation appears to be properly used

The logger import is correctly used throughout the codebase with consistent logging patterns:

  • Proper error logging with context using logger.error()
  • Debug information with logger.debug() for development tracking
  • Informational logs with logger.info() for important state changes
  • Warning logs with logger.warn() for non-critical issues

The logger is used extensively across the application for:

  • Transaction monitoring
  • XMTP client operations
  • File system operations
  • Authentication flows
  • Error capturing
  • Performance tracking
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check logger implementation and usage
rg -A 5 "logger\." "utils/"

Length of output: 44590


112-112: Verify consistent MessageId usage

The type change from string | null to MessageId | null improves type safety. Let's verify consistent usage across the codebase.

✅ Verification successful

Type change from string | null to MessageId | null is consistently applied

The verification shows that the MessageId type is consistently used across the codebase:

  • In persisted store definition (conversation-persisted-stores.ts): replyingToMessageId: MessageId | null
  • In service functions (conversation-service.ts): Parameter and return types use MessageId | null
  • In composer component (composer.tsx): Uses the typed hooks and functions
  • In message components: Properly handles MessageId type when dealing with replies

The type change improves type safety without introducing inconsistencies.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check MessageId type usage in conversation-related files
ast-grep --pattern 'replyingToMessageId: $type'

Length of output: 49


Script:

#!/bin/bash
# Search for MessageId type usage and imports
rg "MessageId" -A 2 -B 2

# Search for replyingToMessageId usage
rg "replyingToMessageId" -A 2 -B 2

Length of output: 50210

utils/xmtpRN/contacts.ts (2)

4-5: LGTM! Import organization looks good.

The imports are logically grouped and follow TypeScript best practices.


210-215: Good refactor to object parameters, but verify call sites.

The refactor to use an object parameter pattern improves maintainability and follows TypeScript best practices. However, this is a breaking change that requires updates to all calling code.

Let's verify that all call sites have been updated:

✅ Verification successful

All call sites have been properly updated to use object parameters

The verification shows that all three call sites of consentToGroupsOnProtocolByAccount are already using the new object parameter pattern:

  • queries/useBlockGroupMutation.ts
  • hooks/useGroupConsent.ts
  • components/Chat/ConsentPopup/dm-consent-popup.tsx

Each call site correctly passes parameters as an object with named fields, matching the new function signature.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct calls to consentToGroupsOnProtocolByAccount
# We're looking for any calls that might still use the old parameter style
rg -A 2 "consentToGroupsOnProtocolByAccount\(" --type ts

Length of output: 734

containers/GroupScreenMembersTable.tsx (2)

18-22: LGTM: Type definitions and imports are well-structured

The new imports and type definitions properly support the addition of the group parameter while maintaining type safety.


45-49: Verify permission handling across the application

The changes to topic handling and group permissions are security-critical. Let's verify the usage of these permissions across the codebase.

✅ Verification successful

Based on the shell script results, I can now generate the final response:

Permission handling implementation is consistent across the codebase

The changes to topic handling and group permissions are safely implemented:

  • useGroupMembers hook is consistently used across components with proper topic handling
  • Permission policy checks are centralized through useGroupPermissionPolicyQuery and userCanDoGroupActions utility
  • The topic fallback pattern topic ?? group?.topic is only used in this component, making it an isolated change
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other components that might be affected by the permission changes

# Check for other uses of useGroupMembers hook
echo "Checking useGroupMembers usage:"
rg "useGroupMembers\(" -A 3

# Check for other components handling group permissions
echo "Checking group permission handling:"
rg "groupPermissionPolicy" -A 3

# Check for other components using topic/group?.topic pattern
echo "Checking topic fallback pattern usage:"
rg "topic \?\? group\?.topic"

Length of output: 5956

screens/Navigation/Navigation.tsx (2)

1-1: LGTM: Import statements are well-organized

The new imports are properly structured and all appear to be used within the navigation configuration.

Also applies to: 13-25


153-156: Add standard navigation options for consistency

The DmConversation screen is missing standard navigation options that are present in other conversation screens.

As noted in previous reviews, consider adding:

  • headerTitle
  • headerTitleStyle: headerTitleStyle(colorScheme)
  • animation: navigationAnimation
  • headerTintColor configuration

This will maintain consistency with other conversation screens in the application.

utils/xmtpRN/conversations.ts (3)

429-435: ⚠️ Potential issue

Mask peer address in debug logs

The peer address should be masked in the debug logs to prevent potential PII exposure.

Apply this diff:

+ const maskAddress = (address: string) => 
+   `${address.slice(0, 6)}...${address.slice(-4)}`;

- logger.debug(`[XMTPRN Conversations] Finding DM by address: ${peer}`);
+ logger.debug(`[XMTPRN Conversations] Finding DM by address: ${maskAddress(peer)}`);

450-458: ⚠️ Potential issue

Mask peer address in retry logs

The peer address should also be masked in the retry debug logs.

Apply this diff:

- logger.debug(`[XMTPRN Conversations] Retrying find DM by address: ${peer}`);
+ logger.debug(`[XMTPRN Conversations] Retrying find DM by address: ${maskAddress(peer)}`);

Likely invalid or redundant comment.


462-464: 🛠️ Refactor suggestion

Enhance error logging and mask peer address

  1. The peer address should be masked in the error logs.
  2. Consider adding more context about potential causes or recovery steps in the error message.

Apply this diff:

  logger.error(
-   `[XMTPRN Conversations] Conversation with peer ${peer} not found after sync`
+   `[XMTPRN Conversations] Conversation with peer ${maskAddress(peer)} not found after sync. ` +
+   'This might occur if the peer has not initialized their XMTP identity or if there are network connectivity issues.'
  );

Likely invalid or redundant comment.

components/Chat/Message/message-content-types/message-static-attachment.tsx (3)

29-32: Address the TODO and clarify content type handling

The // TODO comment indicates a pending implementation. Since message.content() should return StaticAttachmentContent, the check for typeof content === "string" may be unnecessary. Please confirm if this check is needed or consider removing it.


61-64: Sanitize staticAttachment.filename to prevent path traversal vulnerabilities

Ensure that staticAttachment.filename is properly sanitized to prevent path traversal or injection attacks when constructing attachmentPath. This helps prevent unauthorized file access or overwriting critical files.

Consider adding validation to staticAttachment.filename before using it in file paths.


92-94: Prevent division by zero in aspect ratio calculation

When calculating aspectRatio, ensure that attachment.imageSize.height is not zero to prevent division by zero errors.

Apply this diff to add a safety check:

-  const aspectRatio = attachment.imageSize
-    ? attachment.imageSize.width / attachment.imageSize.height
-    : undefined;
+  const aspectRatio = 
+    attachment.imageSize && attachment.imageSize.height !== 0
+      ? attachment.imageSize.width / attachment.imageSize.height
+      : undefined;
components/Chat/ConsentPopup/dm-consent-popup.tsx (1)

51-55: Verify the necessity of consenting to groups in a direct message context.

The consentToGroupsOnProtocolByAccount function is called with groupIds: [conversationId]. Since this is a direct message conversation, verify if consenting to groups is required, or if this call can be omitted to avoid unnecessary operations.

components/Chat/Message/V3Message.tsx (2)

34-34: ⚠️ Potential issue

Avoid using non-null assertion with getCurrentConversationMessages

Using the non-null assertion operator ! assumes that getCurrentConversationMessages() will always return a value. If it returns null or undefined, it could lead to a runtime error. Consider adding a check to handle cases when messages are unavailable.

Apply this diff to handle the potential null value:

-    const messages = getCurrentConversationMessages()!;
+    const messages = getCurrentConversationMessages();
+    if (!messages) {
+      // Handle the null or undefined case appropriately
+      return null; // or provide a fallback UI
+    }

101-111: ⚠️ Potential issue

Ensure only one message component is rendered per message

Multiple message type conditions may be true simultaneously, causing multiple components to render for a single message. For example, a message could be both a reply and a text message. If the intent is to render only one component per message, consider making the conditions mutually exclusive.

Apply this diff to render only one component based on priority:

-            {isGroupUpdatedMessage(message) && (
-              <ChatGroupUpdatedMessage message={message} />
-            )}
-            {isReplyMessage(message) && <MessageReply message={message} />}
-            {isTextMessage(message) && <MessageSimpleText message={message} />}
-            {isRemoteAttachmentMessage(message) && (
-              <MessageRemoteAttachment message={message} />
-            )}
-            {isStaticAttachmentMessage(message) && (
-              <MessageStaticAttachment message={message} />
-            )}
+            {isGroupUpdatedMessage(message) ? (
+              <ChatGroupUpdatedMessage message={message} />
+            ) : isReplyMessage(message) ? (
+              <MessageReply message={message} />
+            ) : isTextMessage(message) ? (
+              <MessageSimpleText message={message} />
+            ) : isRemoteAttachmentMessage(message) ? (
+              <MessageRemoteAttachment message={message} />
+            ) : isStaticAttachmentMessage(message) ? (
+              <MessageStaticAttachment message={message} />
+            ) : null}
components/Chat/Attachment/remote-attachment-image.tsx (3)

78-80: Implement the 'openInWebview' functionality

The onPress handler for unsupported media types is currently empty, preventing users from opening the attachment in a web view.


95-98: Prevent division by zero when calculating aspect ratio

Ensure that attachment.imageSize.height is not zero before performing division to avoid potential runtime errors.

Apply this diff to add a check:

 const aspectRatio =
-  fitAspectRatio && attachment.imageSize
-    ? attachment.imageSize.width / attachment.imageSize.height
+  fitAspectRatio && attachment.imageSize && attachment.imageSize.height !== 0
+    ? attachment.imageSize.width / attachment.imageSize.height
     : undefined;

157-157: Handle potential null value from getCurrentAccount()

Using the non-null assertion operator ! after getCurrentAccount() can lead to runtime errors if the account is null or undefined. Consider handling the case when no current account is available.

Apply this diff to handle the potential null value:

 const decryptedLocalAttachment = await fetchAndDecodeRemoteAttachment({
-  account: getCurrentAccount()!,
+  const account = getCurrentAccount();
+  if (!account) {
+    throw new Error("Current account is not available.");
+  }
+  account: account,
   messageId: messageId,
   remoteAttachmentContent: content,
 });
features/conversation/conversation-context.tsx (2)

145-156: Review the logic in isAllowedConversation computation

The isAllowedConversation variable relies on groupConsent and dmConsent values. Ensure that these consent values can only be "allowed" or other expected strings. Additionally, consider handling any unexpected consent states to prevent potential issues.

Run the following script to verify possible values of groupConsent and dmConsent across the codebase:

✅ Verification successful

Let me gather more information about the possible values of ConsentState.


Let me fix the regex and try again.


Based on the search results, I can now provide a final response:

The isAllowedConversation logic is correctly implemented

The codebase shows consistent handling of consent states across different components:

  • The consent state values are properly managed through the XMTP SDK's ConsentState type
  • The codebase consistently uses "allowed" for allowed conversations and "denied" for blocked ones
  • The implementation aligns with the SDK's consent management system, as seen in multiple files like utils/conversation.ts and utils/notifications.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all assignments and usages of `groupConsent` and `dmConsent` to verify their possible values.

rg -A2 -B2 'groupConsent|dmConsent'

Length of output: 6574


Script:

#!/bin/bash
# Search for ConsentState type definition and its usage
ast-grep --pattern 'type ConsentState = $_'

# Also search for any direct string assignments or comparisons with "allowed" string
rg -B2 -A2 '(consent.*=.*["\'']|===.*["\'']allowed["\''])'

Length of output: 235


Script:

#!/bin/bash
# Search for ConsentState type definition and its usage
ast-grep --pattern 'type ConsentState = $_'

# Search for any direct string assignments or comparisons with "allowed" string
rg -B2 -A2 "(consent.*=.*['\"]|===.*['\"]allowed['\"])"

# Search for any imports of ConsentState
rg -B2 -A2 "import.*ConsentState"

Length of output: 7414


25-25: Ensure all usages of referencedMessageId reflect the updated type

The referencedMessageId parameter type has been changed from string to MessageId. Ensure that all instances where ISendMessageParams is used, and any function calls utilizing referencedMessageId, are updated to reflect this change to maintain type consistency.

Run the following script to verify that all usages of referencedMessageId are updated:

✅ Verification successful

Let me gather more context about the MessageId type and its usage.


Let me try a different approach to find the type definition and its usage.


Let me check the actual usage context in the TypeScript files, particularly focusing on the conversation and composer components where the type is being used.


Based on the code analysis, I can now provide the final response:

The type change is correctly implemented across the codebase

The referencedMessageId is consistently used with the MessageId type imported from @xmtp/react-native-sdk in both the conversation context and composer component. The type is properly propagated through the ISendMessageParams interface and all usage points handle it correctly. The mobile platform implementations (iOS/Android) maintain their own typing as String which is appropriate for their respective languages.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all occurrences of `referencedMessageId` assignments and ensure they use `MessageId`.

rg 'referencedMessageId[\s]*[:=]'

Length of output: 2390


Script:

#!/bin/bash
# Find the definition of MessageId type and where it's used
rg "type\s+MessageId" -A 2

# Find imports or usage of MessageId
rg "MessageId" --type typescript

Length of output: 96


Script:

#!/bin/bash
# Find any files containing MessageId
rg -l "MessageId"

# Look for type definitions or interfaces
ast-grep --pattern 'type MessageId = $_'
ast-grep --pattern 'interface MessageId $_'

Length of output: 1270


Script:

#!/bin/bash
# Check the conversation context file for MessageId type definition and usage
cat features/conversation/conversation-context.tsx

# Check the composer component where referencedMessageId is used
cat features/conversation/composer/composer.tsx

Length of output: 22892

components/Chat/ChatGroupUpdatedMessage.tsx (4)

33-36: Implement proper error handling for string content

As previously noted, returning null when content is a string could lead to a poor user experience. Consider displaying an error message to inform the user about the issue.

Apply this diff to handle the error gracefully:

  if (typeof content === "string") {
-   // TODO
-   return null;
+   return (
+     <VStack style={themed($container)}>
+       <Text color="error" preset="smaller">
+         {translate("message_parse_error")}
+       </Text>
+     </VStack>
+   );
  }

58-59: ⚠️ Potential issue

Incorrect translation key in 'ChatGroupMemberLeft' component

In the ChatGroupMemberLeft component, the translation key used is "group_member_joined", which is incorrect for a member leaving the group. It should be "group_member_left" to accurately reflect the action.

Apply this diff to correct the translation key:

      <ChatGroupUpdateText>
-       {translate("group_member_joined")}
+       {translate("group_member_left")}
      </ChatGroupUpdateText>

Likely invalid or redundant comment.


15-15: ⚠️ Potential issue

Typo in import: 'GroupUpdatedMetadatEntry' should be 'GroupUpdatedMetadataEntry'

There's a typo in the imported type GroupUpdatedMetadatEntry. It should be GroupUpdatedMetadataEntry. This correction ensures proper type checking and avoids potential runtime errors.

Apply this diff to fix the typo:

-  GroupUpdatedMetadatEntry,
+  GroupUpdatedMetadataEntry,

Likely invalid or redundant comment.


67-67: Ensure 'initiatedByInboxId' is defined

There is a potential for content.initiatedByInboxId to be undefined. Consider adding a null check to prevent possible runtime errors.

Run the following script to search for instances where initiatedByInboxId might be undefined:

If the script does not return any matches, it indicates that initiatedByInboxId might not be guaranteed to be defined. In that case, add a null check:

+ if (content.initiatedByInboxId) {
    <ChatGroupMetadataUpdate
      key={`metadata-${index}`}
      metadataEntry={entry}
      initiatorInboxId={content.initiatedByInboxId as InboxId}
    />
+ }
✅ Verification successful

Based on the code analysis, I can now provide the final response:

Type assertion is appropriate here, no null check needed

The type assertion content.initiatedByInboxId as InboxId is actually correct in this context. The GroupUpdatedCodec is properly imported from the XMTP SDK and used as a type parameter in DecodedMessage<[GroupUpdatedCodec]>. The component is specifically designed to handle group update messages, and the type casting is safe because:

  1. The component only renders metadata changes when content.metadataFieldsChanged exists and has entries
  2. The SDK's GroupUpdatedCodec ensures initiatedByInboxId is present for group updates
  3. The type assertion is used in a child component that only renders when there are metadata changes
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if 'initiatedByInboxId' is always defined in 'GroupUpdatedCodec' content.

# Test: Search for definitions of 'GroupUpdatedCodec' to verify 'initiatedByInboxId' presence.
ast-grep --pattern $'interface GroupUpdatedCodec {
  initiatedByInboxId: $_;
  $$$
}' --lang typescript

Length of output: 104


Script:

#!/bin/bash
# Let's try a broader search to find the type definition and usage patterns

# Search for any interface or type containing initiatedByInboxId
ast-grep --pattern 'initiatedByInboxId: $_' --lang typescript

# Search for the actual usage of initiatedByInboxId in the codebase
rg "initiatedByInboxId" -A 2 -B 2

# Search specifically in the component file
cat "components/Chat/ChatGroupUpdatedMessage.tsx"

Length of output: 7324


Script:

#!/bin/bash
# Let's search for the GroupUpdatedCodec type definition and its usage
rg "GroupUpdatedCodec" -B 3 -A 3

# Also search for where this type might be defined in the SDK
fd -e ts -e tsx "GroupUpdatedCodec" --exec cat {}

Length of output: 3776

components/Chat/Message/message-content-types/message-reply.tsx (2)

116-121: Ensure readableProfile handles undefined replyMessageReference.

When replyMessageReference is undefined, readableProfile is set to null. Verify that this case is handled appropriately in the UI to prevent any unexpected behavior.


241-247: Confirm query enabled condition logic.

The enabled property is set to !cachedReplyMessage. Verify that this logic correctly determines when the query should be executed, ensuring that messages are fetched only when not already cached.

features/conversation/dm-conversation.screen.tsx (2)

225-233: ⚠️ Potential issue

Correct the typo in the parameter name peerAddresss

There's a typo in the function parameter peerAddresss; it should be peerAddress. This typo can cause bugs due to referencing an undefined variable.

Apply this diff to correct the parameter name and its usage:

-function useNewConversationHeader(peerAddresss: string) {
+function useNewConversationHeader(peerAddress: string) {
   const navigation = useRouter();

   useEffect(() => {
     navigation.setOptions({
-      headerTitle: () => <NewConversationTitle peerAddress={peerAddresss} />,
+      headerTitle: () => <NewConversationTitle peerAddress={peerAddress} />,
     });
-  }, [peerAddresss, navigation]);
+  }, [peerAddress, navigation]);
 }

156-157: ⚠️ Potential issue

Review the use of return; which may lead to unreachable code

The return; statement at line 156 causes the function to exit early, making the subsequent code unreachable when referencedMessageId is truthy. Ensure that this early return is intentional and that you do not intend to process additional logic afterward. If both text and remoteAttachment are provided without a referencedMessageId, the current logic may not handle that case as expected.

Consider refactoring the function to properly handle all combinations of text, remoteAttachment, and referencedMessageId without causing unintended early exits.

components/Conversation/V3Conversation.tsx (1)

235-238: Verify handling of undefined adjacent message IDs

When accessing messageIds[index - 1] and messageIds[index + 1], there's a possibility of obtaining undefined at the boundaries of the array. Ensure that the V3Message component can handle undefined values for nextMessageId and previousMessageId without errors.

Run the following script to search for usage of nextMessageId and previousMessageId in V3Message and check for handling of undefined values:

This script will help verify if the V3Message component safely handles cases when nextMessageId or previousMessageId are undefined.

✅ Verification successful

Undefined message IDs are properly handled

The code safely handles undefined message IDs through multiple layers:

  1. The component properly checks for undefined messages with !!previousMessage and !!nextMessage before using them in _hasPreviousMessageInSeries and _hasNextMessageInSeries calculations.
  2. There's a null check for the main message (if (!message) { return null; }).
  3. The boolean operations ensure that undefined values are properly coerced and don't cause runtime errors.

While the type definition shows these props as required strings, the runtime checks make the component resilient to undefined values, making it safe to use at array boundaries.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if V3Message component handles undefined nextMessageId and previousMessageId.

# Search for the V3Message component definition
fd 'V3Message.tsx' --exec cat {} \; | rg -A 10 -B 5 'function V3Message'

# Search for prop types and usage of nextMessageId and previousMessageId
rg -A 5 'nextMessageId' $(fd 'V3Message.tsx')

# Search for any default values or conditional checks for these props
rg 'nextMessageId.*==' $(fd 'V3Message.tsx')
rg 'previousMessageId.*==' $(fd 'V3Message.tsx')

Length of output: 815


Script:

#!/bin/bash
# Let's search for the complete V3Message component implementation to understand how these props are used
ast-grep --pattern 'export const V3Message = memo(({ $$$, previousMessageId, nextMessageId $$$}) => {
  $$$
})'

# Also search for any type definitions or interfaces related to V3Message
rg -A 10 'type V3MessageProps' components/Chat/Message/V3Message.tsx
rg -A 10 'interface V3MessageProps' components/Chat/Message/V3Message.tsx

# Let's also check for any utility functions that might handle these IDs
rg -A 5 'hasPreviousMessageInSeries' components/Chat/Message/V3Message.tsx

Length of output: 7628

@@ -5,7 +5,7 @@ import { useGroupQuery } from "./useGroupQuery";

export const useGroupPermissionPolicyQuery = (
account: string,
topic: ConversationTopic | undefined
topic: ConversationTopic
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Breaking change detected: Topic parameter mismatch

The component GroupScreenMembersTable accepts topic as optional (ConversationTopic | undefined), but uses it to call useGroupPermissionPolicyQuery which requires a non-optional ConversationTopic. This creates a type mismatch that needs to be addressed:

  • containers/GroupScreenMembersTable.tsx: The component uses (topic ?? group?.topic)! with a non-null assertion, but both topic and group?.topic could be undefined.
🔗 Analysis chain

Parameter type change looks good but needs verification.

The change to make topic required improves type safety. However, we should verify that all callers of this hook have been updated accordingly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of useGroupPermissionPolicyQuery to verify they provide the required topic parameter
# Expected: All calls should pass a defined topic parameter

# Search for function calls
rg -A 2 "useGroupPermissionPolicyQuery\(" 

# Search for potential undefined/optional topic values being passed
ast-grep --pattern 'useGroupPermissionPolicyQuery($account, $topic?)' 

Length of output: 360


Script:

#!/bin/bash
# Let's check the full context of the usage to understand how topic is handled
rg -B 5 -A 5 "useGroupPermissionPolicyQuery\(" containers/GroupScreenMembersTable.tsx

# Also check if topic is properly defined in the component
ast-grep --pattern 'const $_ = useGroupScreenMembersTable({$$$})'

Length of output: 485


Script:

#!/bin/bash
# Let's check how topic is defined in the component
rg -B 10 -A 2 "const.*GroupScreenMembersTable" containers/GroupScreenMembersTable.tsx

# Also check the props or parameters of the component
ast-grep --pattern 'interface $Props {
  $$$
}'

Length of output: 566

Comment on lines +14 to +27
export function captureErrorWithToast(
error: unknown,
options?: {
message?: string;
}
) {
const message =
options?.message || (error as Error)?.message || `Something went wrong`;
captureError(error);
showSnackbar({
message,
type: "error",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error type handling and toast configuration

The current implementation has some potential improvements:

  1. The type cast to Error is unsafe
  2. Toast configuration could be more flexible

Consider this enhanced implementation:

 export function captureErrorWithToast(
   error: unknown,
   options?: {
     message?: string;
+    duration?: number;
+    autoHide?: boolean;
   }
 ) {
+  // Safely extract error message
+  const errorMessage = error instanceof Error 
+    ? error.message 
+    : String(error);
+
   const message =
-    options?.message || (error as Error)?.message || `Something went wrong`;
+    options?.message || errorMessage || `Something went wrong`;
 
   captureError(error);
   showSnackbar({
     message,
     type: "error",
+    duration: options?.duration ?? 5000,
+    autoHide: options?.autoHide ?? true,
   });
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +33
import { showSnackbar } from "@/components/Snackbar/Snackbar.service";
import config from "@/config";
import logger from "@/utils/logger";
import { sentryTrackError } from "@/utils/sentry";

export function captureError(error: unknown) {
if (config.env === "dev") {
logger.error(error);
}

sentryTrackError(error);
}

export function captureErrorWithToast(
error: unknown,
options?: {
message?: string;
}
) {
const message =
options?.message || (error as Error)?.message || `Something went wrong`;
captureError(error);
showSnackbar({
message,
type: "error",
});
}

export function captureErrorWithFriendlyToast(error: unknown) {
captureErrorWithToast(error, {
message: "Something went wrong",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Error handling needs standardization across the codebase

Several instances of direct console.error usage were found that should be migrated to use the centralized error handling utilities:

  • theme/useAppTheme.ts
  • screens/NewConversation/NewConversation.tsx
  • containers/GroupScreenAddition.tsx
  • components/Chat/Message/message-content-types/message-reply.tsx
  • components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts

The Sentry configuration is properly set up in utils/sentry.ts. However, the direct console.error usage bypasses both error tracking and user notification features provided by the error capture utilities.

Note: Build scripts can retain console.error usage as they are development utilities.

🔗 Analysis chain

Verify error handling configuration and usage

Let's ensure consistent error handling across the codebase and proper Sentry setup.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other error handling patterns that might need migration
rg -g '!utils/capture-error.ts' "showSnackbar.*error" -A 2

# Check Sentry configuration
rg "Sentry\.init" -A 5

# Look for direct console.error usage that should use captureError
rg "console\.error"

Length of output: 1411

Comment on lines +1 to 8
import { useQuery } from "@tanstack/react-query";
import { getDmPeerInbox } from "@utils/xmtpRN/contacts";
import {
ConversationVersion,
type ConversationTopic,
} from "@xmtp/react-native-sdk";
import { useConversationScreenQuery } from "./useConversationQuery";
import { useQuery } from "@tanstack/react-query";
import { getDmPeerInbox } from "@utils/xmtpRN/contacts";
import { useConversationQuery } from "./useConversationQuery";

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Migration from useConversationScreenQuery is incomplete

The codebase still contains references to useGroupMembersConversationScreenQuery in multiple files:

  • features/conversations/hooks/useGroupMembersAvatarData.ts is actively using it
  • queries/useGroupMembersQuery.ts contains the implementation
  • features/conversation/conversation-group-context.tsx has a commented-out usage

The change from useConversationScreenQuery to useConversationQuery appears to be part of a larger refactoring effort that's not fully complete. The group members functionality still uses the old pattern.

🔗 Analysis chain

Verify the migration from useConversationScreenQuery

The change from useConversationScreenQuery to useConversationQuery looks good, but let's verify that all other components have been updated accordingly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usages of useConversationScreenQuery

# Search for any remaining references to the old query hook
rg "useConversationScreenQuery"

# Search for components still importing from the old location
ast-grep --pattern 'import { $$$useConversationScreenQuery$$$ } from $path'

Length of output: 109


Script:

#!/bin/bash
# Let's try a more comprehensive search to understand the conversation query hooks

# Search for any files containing conversation query hooks
rg -l "useConversation.*Query"

# Look for specific imports related to conversation queries
ast-grep --pattern 'import { $$$useConversation$$$ } from $path'

# Search for any references to ConversationScreenQuery
rg "ConversationScreenQuery"

# Check git history for recent changes related to conversation queries
git log --oneline -n 5 --grep="conversation.*query" --grep="useConversation"

Length of output: 1093


Script:

#!/bin/bash
# Let's check the specific files that still reference ConversationScreenQuery

# Check the content of the group members query file
cat queries/useGroupMembersQuery.ts

# Look for any imports of useConversationQuery
rg "import.*useConversationQuery" -A 2

# Check the implementation in conversation-group-context
cat features/conversation/conversation-group-context.tsx

Length of output: 7052

) => {
return useQuery({
...options,
queryKey: conversationWithPeerQueryKey(account, peer!),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unsafe non-null assertion on peer

The non-null assertion (!) on peer in the queryKey is unsafe as peer is explicitly typed as string | undefined. While the query is only enabled when peer exists, it's better to maintain type safety.

-queryKey: conversationWithPeerQueryKey(account, peer!),
+queryKey: conversationWithPeerQueryKey(account, peer),

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +153 to +155
console.log("isAllowedConversation:", isAllowedConversation);
console.log("isLoadingConversationConsent:", isLoadingConversationConsent);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove debug console.log statements

There are console.log statements present which may not be suitable for production code. Consider removing them or replacing them with an appropriate logging mechanism if needed.

Apply this diff to remove the debug statements:

Lines 153-155:

-  console.log("isAllowedConversation:", isAllowedConversation);
-  console.log("isLoadingConversationConsent:", isLoadingConversationConsent);

Lines 202-204:

-  console.log("isLoadingConversationConsent:", isLoadingConversationConsent);
-  console.log("isAllowedConversation:", isAllowedConversation);

Also applies to: 202-204

Comment on lines +106 to +108
async (args: IComposerSendArgs) => {
sendMessage(args);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Await the asynchronous sendMessage function

In the handleSend function, the sendMessage call is likely asynchronous but is not awaited. This could lead to unhandled promise rejections or unexpected behavior.

Apply this diff to fix the issue:

async (args: IComposerSendArgs) => {
-    sendMessage(args);
+    await sendMessage(args);
},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async (args: IComposerSendArgs) => {
sendMessage(args);
},
async (args: IComposerSendArgs) => {
await sendMessage(args);
},

Comment on lines +392 to +398
containerProps={{
style: {
height: theme.avatarSize.md,
width: theme.avatarSize.md,
borderRadius: theme.borderRadius.xs,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify that content is not undefined before usage

In the RemoteAttachmentImage component, passing content without checking if it's defined could result in runtime errors. Ensure that content is validated before use.

Apply this diff to add a null check:

+if (content) {
  <RemoteAttachmentImage
    messageId={messageTyped.id}
    remoteMessageContent={content}
    containerProps={{
      style: {
        height: theme.avatarSize.md,
        width: theme.avatarSize.md,
        borderRadius: theme.borderRadius.xs,
      },
    }}
  />
+} else {
+  // Handle the case where content is undefined, e.g., render a placeholder or return null
+  return null;
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 102 to 103
await onSend({
remoteAttachment: uploadedRemoteAttachment,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid using the non-null assertion operator !

Using getUploadedRemoteAttachment()! assumes that the function will always return a value, which could lead to runtime errors if it returns undefined. Instead, add a null check to handle cases where no attachment is uploaded.

Apply this diff to add a null check:

-const uploadedRemoteAttachment = getUploadedRemoteAttachment()!;
+const uploadedRemoteAttachment = getUploadedRemoteAttachment();
+if (!uploadedRemoteAttachment) {
+  // Handle the error case appropriately, e.g., show an error message or return early
+  return;
+}

await onSend({
  remoteAttachment: uploadedRemoteAttachment,
  ...(replyingToMessageId && {
    referencedMessageId: replyingToMessageId,
  }),
});

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +365 to +371
containerProps={{
style: {
height: theme.avatarSize.md,
width: theme.avatarSize.md,
borderRadius: theme.borderRadius.xs,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null checks for content.content.remoteAttachment

When accessing content.content.remoteAttachment, there is a risk of encountering undefined if content or content.content does not contain remoteAttachment. This could lead to runtime errors when rendering the RemoteAttachmentImage component.

Apply this diff to add a null check:

+if (content.content.remoteAttachment) {
  <RemoteAttachmentImage
    messageId={content.reference}
    remoteMessageContent={content.content.remoteAttachment}
    containerProps={{
      style: {
        height: theme.avatarSize.md,
        width: theme.avatarSize.md,
        borderRadius: theme.borderRadius.xs,
      },
    }}
  />
+} else {
+  // Handle the case where remoteAttachment is missing, e.g., render a placeholder or return null
+  return null;
+}

Committable suggestion skipped: line range outside the PR's diff.

@alexrisch alexrisch self-requested a review December 2, 2024 14:53
@alexrisch alexrisch self-assigned this Dec 2, 2024
Copy link
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: 0

🧹 Outside diff range and nitpick comments (8)
features/conversation/composer/composer.tsx (3)

68-68: Consider removing unnecessary type alias

The type alias IComposerSendArgs = ISendMessageParams adds an extra layer of indirection without providing additional value. Consider using ISendMessageParams directly for better code clarity.

-export type IComposerSendArgs = ISendMessageParams;

Line range hint 78-133: Consider splitting the send function for better maintainability

The send function handles both media and text message sending, making it complex and harder to maintain. Consider splitting it into separate functions for better separation of concerns.

-const send = useCallback(async () => {
+const sendMediaMessage = useCallback(async (replyingToMessageId: string | null) => {
+  if (mediaPreview?.status === "uploading") {
+    await waitUntilMediaPreviewIsUploaded();
+  }
+
+  setComposerMediaPreviewStatus("sending");
+
+  try {
+    await saveAttachmentLocally();
+  } catch (error) {
+    sentryTrackError(error);
+  }
+
+  const uploadedRemoteAttachment = getUploadedRemoteAttachment();
+  if (!uploadedRemoteAttachment) {
+    throw new Error("No attachment found");
+  }
+
+  await onSend({
+    content: {
+      remoteAttachment: uploadedRemoteAttachment,
+    },
+    ...(replyingToMessageId && {
+      referencedMessageId: replyingToMessageId,
+    }),
+  });
+
+  resetUploadedRemoteAttachment();
+  resetComposerMediaPreview();
+}, [onSend]);
+
+const sendTextMessage = useCallback(async (replyingToMessageId: string | null) => {
+  const inputValue = getCurrentConversationInputValue();
+  if (inputValue.length === 0) return;
+
+  await onSend({
+    content: {
+      text: inputValue,
+    },
+    ...(replyingToMessageId && {
+      referencedMessageId: replyingToMessageId,
+    }),
+  });
+
+  setCurrentConversationInputValue("");
+}, [onSend]);
+
+const send = useCallback(async () => {
+  const mediaPreview = getComposerMediaPreview();
+  const replyingToMessageId = getCurrentConversationReplyToMessageId();
+
+  try {
+    if (mediaPreview) {
+      await sendMediaMessage(replyingToMessageId);
+    }
+    await sendTextMessage(replyingToMessageId);
+    
+    setCurrentConversationReplyToMessageId(null);
+  } catch (error) {
+    sentryTrackError(error);
+    // TODO: Show error to user
+  }
+}, [sendMediaMessage, sendTextMessage]);

Line range hint 363-398: Extract duplicate styles to a shared constant

The containerProps styles for RemoteAttachmentImage are duplicated. Consider extracting them to a shared constant for better maintainability.

+const attachmentImageStyles = (theme) => ({
+  style: {
+    height: theme.avatarSize.md,
+    width: theme.avatarSize.md,
+    borderRadius: theme.borderRadius.xs,
+  },
+});

// Then use it in both places:
<RemoteAttachmentImage
  messageId={content.reference}
  remoteMessageContent={content.content.remoteAttachment}
-  containerProps={{
-    style: {
-      height: theme.avatarSize.md,
-      width: theme.avatarSize.md,
-      borderRadius: theme.borderRadius.xs,
-    },
-  }}
+  containerProps={attachmentImageStyles(theme)}
/>
features/conversation/conversation-context.tsx (5)

23-32: Consider using discriminated union for message content

The current type definition could be improved for better type safety and clarity.

Consider this alternative:

type BaseMessageContent = {
  referencedMessageId?: MessageId;
};

type TextMessageContent = BaseMessageContent & {
  type: 'text';
  content: {
    text: string;
  };
};

type AttachmentMessageContent = BaseMessageContent & {
  type: 'attachment';
  content: {
    remoteAttachment: RemoteAttachmentContent;
  };
};

type MixedMessageContent = BaseMessageContent & {
  type: 'mixed';
  content: {
    text: string;
    remoteAttachment: RemoteAttachmentContent;
  };
};

export type ISendMessageParams = TextMessageContent | AttachmentMessageContent | MixedMessageContent;

112-112: Remove console.log statement

Debug statements should not be committed to production code.

-console.log("payload:", payload);

Line range hint 92-144: Refactor message sending logic to reduce complexity

The current implementation has duplicate logic for handling different message types and could be simplified.

Consider this approach:

const sendMessage = useCallback(
  async ({ referencedMessageId, content }: ISendMessageParams) => {
    if (!conversation && !peerAddress) {
      return;
    }

    const createPayload = (content: any, reference?: MessageId) => {
      if (!reference) return content;
      return {
        reply: {
          reference,
          content
        }
      };
    };

    const targetConversation = conversation || await createNewConversation();
    
    if (content.remoteAttachment) {
      await targetConversation.send(
        createPayload({ remoteAttachment: content.remoteAttachment }, referencedMessageId)
      );
    }
    
    if (content.text) {
      await targetConversation.send(
        createPayload({ text: content.text }, referencedMessageId)
      );
    }
  },
  [conversation, currentAccount, peerAddress]
);

174-174: Implement proper blocked conversation state handling

The current implementation has a TODO comment and may not correctly reflect the conversation state.

Would you like me to help implement proper blocked conversation state handling? I can help create a new issue to track this task.


Line range hint 92-146: Add proper error handling for message sending

The message sending logic lacks proper error handling and loading states.

Consider adding try-catch blocks and error states:

const [isSending, setIsSending] = useState(false);
const [sendError, setSendError] = useState<Error | null>(null);

const sendMessage = useCallback(
  async ({ referencedMessageId, content }: ISendMessageParams) => {
    setIsSending(true);
    setSendError(null);
    
    try {
      // existing send logic
    } catch (error) {
      setSendError(error as Error);
      // Consider adding error reporting
      throw error;
    } finally {
      setIsSending(false);
    }
  },
  [conversation, currentAccount, peerAddress]
);

Also, consider adding these states to the context value for UI feedback.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 512abb5 and 9d31a49.

📒 Files selected for processing (2)
  • features/conversation/composer/composer.tsx (9 hunks)
  • features/conversation/conversation-context.tsx (5 hunks)
🔇 Additional comments (3)
features/conversation/composer/composer.tsx (1)

99-102: Remove non-null assertion operator

The use of ! with getUploadedRemoteAttachment() could lead to runtime errors if the attachment is undefined.

features/conversation/conversation-context.tsx (2)

1-1: Fix typo in import path for useDmConsentQuery

The import path contains a typo: "useDmConstentStateQuery" should be "useDmConsentStateQuery".


67-73: Handle potential undefined topic safely

The topic variable is being used with a non-null assertion in useGroupConsentQuery and passed directly to useDmConsentQuery.

@@ -0,0 +1,281 @@
/**
*
* WORK IN PROGRESS!
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it makes sense to decouple new DM experience, I'm not 100% sure about decoupling DMs and Groups

I think there is value in accepting a topic and it "just working"
I think there is a lot of value to be had in accepting simpler links for devs to be able to make things on

@alexrisch alexrisch changed the base branch from feature/v3-split to release/3.0.0 December 2, 2024 23:23
@thierryskoda thierryskoda force-pushed the ts/message-refactoring-attachments branch from 9d31a49 to 47e992c Compare December 2, 2024 23:58
Copy link
Contributor

github-actions bot commented Dec 3, 2024

Performance Comparison Report

  • Current: e741a14 - 2024-12-05 14:57:31Z
  • Baseline: release/3.0.0 (4e4764c) - 2024-12-05 14:56:09Z

Significant Changes To Duration

There are no entries

Meaningless Changes To Duration

Show entries
Name Type Duration Count
Avatar Image 10 runs render 1.3 ms → 1.0 ms (-0.3 ms, -23.1%) 🟢 1 → 1
Avatar Image 50 runs render 1.1 ms → 1.0 ms (-0.1 ms, -8.8%) 1 → 1
Empty Avatar 10 runs render 1.0 ms → 0.8 ms (-0.2 ms, -20.0%) 🟢 1 → 1
Empty Avatar 50 runs render 0.8 ms → 0.8 ms (-0.0 ms, -2.6%) 1 → 1
Text Component with color prop - 10 runs render 0.0 ms → 0.3 ms (+0.3 ms, +Infinity%) 🔴 1 → 1
Text Component with default props - 10 runs render 0.3 ms → 0.4 ms (+0.1 ms, +33.3%) 🔴 1 → 1
Text Component with translation key - 10 runs render 0.4 ms → 0.4 ms 1 → 1
Text Component with weight and size - 10 runs render 0.5 ms → 0.3 ms (-0.2 ms, -40.0%) 🟢 1 → 1
Show details
Name Type Duration Count
Avatar Image 10 runs render Baseline
Mean: 1.3 ms
Stdev: 0.5 ms (37.2%)
Runs: 2 1 2 1 2 1 1 1 1 1
Warmup runs: 3

Current
Mean: 1.0 ms
Stdev: 0.0 ms (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Warmup runs: 2
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Avatar Image 50 runs render Baseline
Mean: 1.1 ms
Stdev: 0.4 ms (35.5%)
Runs: 1 1 1 1 1 1 1 0 2 1 1 1 1 1 2 1 1 1 1 2 1 1 1 1 2 1 1 1 1 1 2 1 1 1 1 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1
Warmup runs: 1

Current
Mean: 1.0 ms
Stdev: 0.2 ms (19.0%)
Runs: 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Warmup runs: 2
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 10 runs render Baseline
Mean: 1.0 ms
Stdev: 0.0 ms (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Warmup runs: 3

Current
Mean: 0.8 ms
Stdev: 0.4 ms (52.7%)
Runs: 1 0 1 1 1 1 1 0 1 1
Warmup runs: 2
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 50 runs render Baseline
Mean: 0.8 ms
Stdev: 0.4 ms (53.6%)
Runs: 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 1 0 1 1 0 1 1 1 1 0 0 1 1 1 1 0 1 0 1 1 0 1 1 1 1 1 1 1
Warmup runs: 1

Current
Mean: 0.8 ms
Stdev: 0.4 ms (56.8%)
Runs: 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 0 1 1 1 1 1 1 0 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 0 0 0 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with color prop - 10 runs render Baseline
Mean: 0.0 ms
Stdev: 0.0 ms (NaN%)
Runs: 0 0 0 0 0 0 0 0 0 0
Warmup runs: 0

Current
Mean: 0.3 ms
Stdev: 0.5 ms (161.0%)
Runs: 0 0 1 0 0 1 0 0 1 0
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with default props - 10 runs render Baseline
Mean: 0.3 ms
Stdev: 0.5 ms (161.0%)
Runs: 0 1 0 0 1 0 0 1 0 0
Warmup runs: 0

Current
Mean: 0.4 ms
Stdev: 0.5 ms (129.1%)
Runs: 1 0 0 0 1 0 0 0 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with translation key - 10 runs render Baseline
Mean: 0.4 ms
Stdev: 0.5 ms (129.1%)
Runs: 0 1 1 0 0 1 1 0 0 0
Warmup runs: 0

Current
Mean: 0.4 ms
Stdev: 0.5 ms (129.1%)
Runs: 0 0 1 1 0 1 0 1 0 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with weight and size - 10 runs render Baseline
Mean: 0.5 ms
Stdev: 0.5 ms (105.4%)
Runs: 1 0 0 0 0 1 1 0 1 1
Warmup runs: 0

Current
Mean: 0.3 ms
Stdev: 0.5 ms (161.0%)
Runs: 0 0 0 0 1 1 0 1 0 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Render Count Changes

There are no entries

Render Issues

There are no entries

Added Scenarios

There are no entries

Removed Scenarios

There are no entries

Generated by 🚫 dangerJS against 828442b

Copy link
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: 10

🧹 Outside diff range and nitpick comments (30)
features/conversation-list/useV3ConversationItems.ts (1)

22-37: Consider extracting the filter callback for reusability

The filtering logic could be made more reusable by extracting it into a separate function.

+ const isConversationVisible = (
+   conversation: ConversationType,
+   pinnedTopics: Set<string>,
+   deletedTopics: Set<string>
+ ) => {
+   const isAllowed = conversation.state === "allowed";
+   const isNotPinned = !pinnedTopics.has(conversation.topic);
+   const isNotDeleted = !deletedTopics.has(conversation.topic);
+   return isAllowed && isNotPinned && isNotDeleted;
+ };

  const conversationsFiltered = useMemo(() => {
    const pinnedTopics = new Set(pinnedConversationTopics);
    const deletedTopics = new Set(
      Object.entries(topicsData)
        .filter(([_, data]) => data?.status === "deleted")
        .map(([topic]) => topic)
    );

-   return conversations?.filter((conversation) => {
-     const isAllowed = conversation.state === "allowed";
-     const isNotPinned = !pinnedTopics.has(conversation.topic);
-     const isNotDeleted = !deletedTopics.has(conversation.topic);
-
-     return isAllowed && isNotPinned && isNotDeleted;
-   });
+   return conversations?.filter((conversation) =>
+     isConversationVisible(conversation, pinnedTopics, deletedTopics)
+   );
  }, [conversations, pinnedConversationTopics, topicsData]);
queries/useDmPeerInbox.ts (1)

Line range hint 14-27: Consider enhancing error handling for getDmPeerInbox

While the implementation looks good overall, consider adding error handling for the getDmPeerInbox call to provide more specific error messages and better error recovery.

 export const useDmPeerInboxId = (account: string, topic: ConversationTopic) => {
   const { data: conversation } = useConversationQuery(account, topic);
   return useQuery({
     queryKey: dmPeerInboxIdQueryKey(account, topic),
     queryFn: () => {
       if (!conversation) {
         throw new Error("Conversation not found");
       }
       if (conversation.version !== ConversationVersion.DM) {
         throw new Error("Conversation is not a DM");
       }
-      return getDmPeerInbox(conversation);
+      try {
+        return getDmPeerInbox(conversation);
+      } catch (error) {
+        throw new Error(`Failed to get DM peer inbox: ${error.message}`);
+      }
     },
     enabled: !!conversation && conversation.version === ConversationVersion.DM,
   });
 };
queries/useRemoveFromGroupMutation.ts (2)

Line range hint 24-30: Clean up redundant null check in mutationFn

Since topic is now a required parameter, the null check is redundant. Only group and account checks are needed.

 mutationFn: async (inboxIds: InboxId[]) => {
-  if (!group || !account || !topic) {
+  if (!group || !account) {
     return;
   }
   await group.removeMembersByInboxId(inboxIds);
   return inboxIds;
 },

Line range hint 31-54: Remove redundant topic checks in callbacks

Since topic is now a required parameter, the null checks in onMutate and onError are unnecessary.

 onMutate: async (inboxIds: InboxId[]) => {
-  if (!topic) {
-    return;
-  }
   await cancelGroupMembersQuery(account, topic);
   // ... rest of the implementation

 onError: (error, _variables, context) => {
   logger.warn("onError useRemoveFromGroupMutation");
   sentryTrackError(error);
   if (context?.previousGroupMembers === undefined) {
     return;
   }
-  if (!topic) {
-    return;
-  }
   setGroupMembersQueryData(account, topic, context.previousGroupMembers);
 },
features/conversation/composer/add-attachment-button.tsx (3)

Line range hint 119-137: Add error handling for image compression failures

The compressAndResizeImage operation could fail, especially with large images or memory constraints, but the error is only tracked without user feedback.

Add explicit error handling and user feedback:

 const resizedImage = await compressAndResizeImage(asset.uri);
+if (!resizedImage) {
+  setComposerMediaPreviewStatus("error");
+  Alert.alert(
+    "Error",
+    "Failed to process image. Please try a different image."
+  );
+  return;
+}

Line range hint 142-151: Consider adding upload progress feedback

The upload process might take time for large files, but there's no progress indication to the user.

Consider adding upload progress tracking:

 const uploadedAttachment = await uploadRemoteAttachment(
   currentAccount,
-  encryptedAttachment
+  encryptedAttachment,
+  (progress) => {
+    setComposerMediaPreviewStatus(`uploading ${Math.round(progress * 100)}%`);
+  }
 );

Line range hint 152-156: Improve error recovery mechanism

When upload fails, the error is tracked but the UI state isn't reset, potentially leaving the user in an inconsistent state.

Add error state handling:

 } catch (error) {
   sentryTrackMessage("ATTACHMENT_UPLOAD_ERROR", { error });
+  setComposerMediaPreviewStatus("error");
+  Alert.alert(
+    "Upload Failed",
+    "Failed to upload attachment. Please try again."
+  );
 }
components/Chat/Message/V3Message.tsx (1)

119-138: Remove commented out code

The commented-out useConversationMessageById function should be removed if it's no longer needed.

Either remove the commented code or add a comment explaining why it's kept for future reference.

containers/GroupScreenMembersTable.tsx (1)

Line range hint 109-117: Enhance error messages for member actions

Generic "An error occurred" alerts don't provide enough context for users to understand what went wrong.

Consider providing more specific error messages:

 } catch (e) {
   logger.error(e);
-  Alert.alert("An error occurred");
+  Alert.alert(
+    "Action Failed",
+    `Failed to promote member to admin: ${e.message || 'Unknown error'}`
+  );
 }
components/Conversation/V3Conversation.tsx (1)

161-171: Simplify the consent popup condition

The condition for rendering the consent popup can be simplified for better readability.

-      ListHeaderComponent={
-        !isAllowedConversation &&
-        peerInboxId &&
-        !isLoadingConversationConsent ? (
-          <DmConsentPopup
-            peerInboxId={peerInboxId}
-            topic={topic}
-            conversationId={conversationId}
-          />
-        ) : undefined
-      }
+      ListHeaderComponent={
+        peerInboxId && !isAllowedConversation && !isLoadingConversationConsent
+          ? <DmConsentPopup
+              peerInboxId={peerInboxId}
+              topic={topic}
+              conversationId={conversationId}
+            />
+          : undefined
+      }
screens/Profile.tsx (1)

Line range hint 158-167: Improve the delay calculation precision

The delay calculation could be more precise by considering the actual refresh duration.

   const manuallyRefreshBalance = useCallback(async () => {
     setRefreshingBalance(true);
     const now = new Date().getTime();
+    const minDisplayTime = 1000; // 1 second
     await refreshBalanceForAccount(userAddress, 0);
     const after = new Date().getTime();
-    if (after - now < 1000) {
-      await new Promise((r) => setTimeout(r, 1000 - after + now));
+    const elapsed = after - now;
+    if (elapsed < minDisplayTime) {
+      await new Promise((r) => setTimeout(r, minDisplayTime - elapsed));
     }
     setRefreshingBalance(false);
   }, [userAddress]);
components/Chat/Attachment/remote-attachment-image.tsx (1)

54-60: Enhance error message with more details

Consider providing more specific error information to help users understand and troubleshoot the issue.

-        <Text>{translate("attachment_message_error_download")}</Text>
+        <Text>
+          {translate("attachment_message_error_download")}{" "}
+          {attachmentError instanceof Error ? attachmentError.message : ""}
+        </Text>
components/Chat/Message/message-content-types/message-reply.tsx (3)

39-42: Address TODO comment and implement proper error handling

The TODO comment should be addressed with proper error handling for invalid message content.

Would you like me to help implement proper error handling for invalid message content?


230-253: Improve type safety in the hook implementation

Consider the following improvements:

  1. Handle potential null case for currentAccount
  2. Use type guards instead of type assertions for better type safety
-  const currentAccount = useCurrentAccount()!;
+  const currentAccount = useCurrentAccount();
+  if (!currentAccount) {
+    return undefined;
+  }

-  const cachedReplyMessage = messages?.byId[messageId] as
-    | DecodedMessage<[ReplyCodec]>
-    | undefined;
+  const cachedReplyMessage = messages?.byId[messageId];
+  if (cachedReplyMessage && isReplyCodecMessage(cachedReplyMessage)) {
+    return cachedReplyMessage;
+  }

74-90: Simplify border radius calculations

Consider extracting the complex border radius calculation into a theme variable or utility function for better maintainability.

+ // In theme configuration
+ theme.borderRadius.nestedAttachment = theme.borderRadius.message.attachment - 
+   theme.spacing.message.replyMessage.horizontalPadding / 2;

  // In component
  containerProps={{
    style: {
      width: "100%",
-     borderRadius:
-       theme.borderRadius.message.attachment -
-       theme.spacing.message.replyMessage.horizontalPadding / 2,
+     borderRadius: theme.borderRadius.nestedAttachment,
    },
  }}
queries/useConversationWithPeerQuery.ts (1)

17-21: Improve error logging in queryFn

The current logging is too generic. Consider adding more context about the peer and account for better debugging.

-      logger.info("[Crash Debug] queryFn fetching conversation with peer");
+      logger.info("[Conversation] Fetching conversation", { peer, account });
       if (!peer) {
+        logger.info("[Conversation] No peer provided, returning null");
         return null;
       }
queries/useConversationQuery.ts (1)

50-57: Add return type annotation for refetchConversationQuery

The function's return type should be explicitly defined for better type safety.

-export function refetchConversationQuery(
+export function refetchConversationQuery(
   account: string,
   topic: ConversationTopic
-) {
+): Promise<void> {
   return queryClient.refetchQueries({
     queryKey: conversationQueryKey(account, topic),
   });
 }
features/conversation/conversation-context.tsx (1)

Line range hint 92-144: Improve error handling and logging in sendMessage

The sendMessage function lacks proper error handling and logging, which could make debugging issues difficult.

   async ({ referencedMessageId, content }: ISendMessageParams) => {
     const sendCallback = async (payload: any) => {
+      try {
         if (!conversation && !peerAddress) {
+          logger.error("[Conversation] Cannot send message: no conversation or peer address");
           return;
         }

         if (!conversation && peerAddress) {
+          logger.info("[Conversation] Creating new conversation", { peerAddress });
           const newConversation = await createConversationByAccount(
             currentAccount,
             peerAddress
           );
           updateNewConversation(newConversation.topic);
           await newConversation.send(payload);
           addConversationToConversationListQuery(
             currentAccount,
             newConversation
           );
           return;
         }

-        console.log("payload:", payload);
+        logger.debug("[Conversation] Sending message", { payload });
         await conversation?.send(payload);
+      } catch (error) {
+        logger.error("[Conversation] Failed to send message", { 
+          error, 
+          payload,
+          conversationId: conversation?.id 
+        });
+        throw error;
+      }
     };
features/conversation/composer/composer.tsx (4)

68-72: Consider explicit type definition instead of type alias

Rather than using a type alias, consider explicitly defining IComposerSendArgs to ensure type stability and clarity. This would prevent unexpected changes if ISendMessageParams is modified.

-export type IComposerSendArgs = ISendMessageParams;
+export type IComposerSendArgs = {
+  content: {
+    text?: string;
+    remoteAttachment?: RemoteAttachmentContent;
+  };
+  referencedMessageId?: MessageId;
+};

Line range hint 129-133: Implement or remove TODO comment

The TODO comment about fixing scroll functionality with context should be addressed.

Would you like me to help implement the scroll functionality using the context API or create a GitHub issue to track this task?


Line range hint 78-133: Consider splitting the send function for better maintainability

The send function handles both media and text messages. Consider splitting it into separate functions for better maintainability and testing.

+  const sendMediaMessage = async (mediaPreview: any, replyingToMessageId?: MessageId) => {
+    if (mediaPreview?.status === "uploading") {
+      await waitUntilMediaPreviewIsUploaded();
+    }
+    setComposerMediaPreviewStatus("sending");
+    try {
+      await saveAttachmentLocally();
+    } catch (error) {
+      sentryTrackError(error);
+    }
+    const uploadedRemoteAttachment = getUploadedRemoteAttachment();
+    if (!uploadedRemoteAttachment) {
+      throw new Error("Failed to get uploaded attachment");
+    }
+    await onSend({
+      content: { remoteAttachment: uploadedRemoteAttachment },
+      ...(replyingToMessageId && { referencedMessageId: replyingToMessageId }),
+    });
+    resetUploadedRemoteAttachment();
+    resetComposerMediaPreview();
+  };
+
+  const sendTextMessage = async (inputValue: string, replyingToMessageId?: MessageId) => {
+    if (inputValue.length > 0) {
+      await onSend({
+        content: { text: inputValue },
+        ...(replyingToMessageId && { referencedMessageId: replyingToMessageId }),
+      });
+    }
+  };

   const send = useCallback(async () => {
     const mediaPreview = getComposerMediaPreview();
     const replyingToMessageId = getCurrentConversationReplyToMessageId();
-    if (mediaPreview) {
-      // ... media handling code
-    }
-    const inputValue = getCurrentConversationInputValue();
-    if (inputValue.length > 0) {
-      // ... text handling code
-    }
+    if (mediaPreview) {
+      await sendMediaMessage(mediaPreview, replyingToMessageId);
+    }
+    const inputValue = getCurrentConversationInputValue();
+    await sendTextMessage(inputValue, replyingToMessageId);
     setCurrentConversationInputValue("");
     setCurrentConversationReplyToMessageId(null);
   }, [onSend]);

363-371: Extract duplicate styles into a constant

The style props for RemoteAttachmentImage are duplicated. Consider extracting them into a constant for reuse.

+const attachmentImageStyles = (theme: any) => ({
+  style: {
+    height: theme.avatarSize.md,
+    width: theme.avatarSize.md,
+    borderRadius: theme.borderRadius.xs,
+  },
+});

// Then use it in both places:
-containerProps={{
-  style: {
-    height: theme.avatarSize.md,
-    width: theme.avatarSize.md,
-    borderRadius: theme.borderRadius.xs,
-  },
-}}
+containerProps={attachmentImageStyles(theme)}

Also applies to: 390-398

components/Chat/ChatGroupUpdatedMessage.tsx (1)

47-52: Optimize member list rendering performance

The repeated mapping operations for members could be optimized for better performance with large lists.

+ const MemoizedChatGroupMemberJoined = memo(ChatGroupMemberJoined);
+ const MemoizedChatGroupMemberLeft = memo(ChatGroupMemberLeft);

  {content.membersAdded.map((member) => (
-   <ChatGroupMemberJoined
+   <MemoizedChatGroupMemberJoined
      key={`joined-${member.inboxId}`}
      inboxId={member.inboxId as InboxId}
    />
  ))}

  {content.membersRemoved.map((member) => (
-   <ChatGroupMemberLeft
+   <MemoizedChatGroupMemberLeft
      key={`left-${member.inboxId}`}
      inboxId={member.inboxId as InboxId}
    />
  ))}

Also applies to: 55-60

components/Chat/ConsentPopup/group-consent-popup.tsx (1)

63-75: Enhance accessibility support

Add appropriate ARIA labels and roles to improve accessibility for screen readers.

 <ConsentPopupButton
   variant="text"
   action="danger"
   icon="xmark"
   text={translate("decline")}
   onPress={onBlock}
+  accessibilityLabel={translate("decline_group_invitation")}
+  accessibilityRole="button"
 />
 <ConsentPopupButton
   variant="fill"
   icon="checkmark"
   text={translate("join_this_group")}
   onPress={onAccept}
+  accessibilityLabel={translate("accept_group_invitation")}
+  accessibilityRole="button"
+  disabled={isAccepting}
 />
hooks/useGroupConsent.ts (2)

Line range hint 133-164: Ensure consistent error handling between allow and block operations

The block operation should use the same error handling pattern as allow.

 const blockGroup = useCallback(
   async (args: IGroupConsentOptions) => {
     const { includeAddedBy, includeCreator } = args;
+    try {
       if (!group) {
         showSnackbar({
           type: "error",
           message: translate("group_not_found"),
         });
         return;
       }

       await blockGroupMutation();

       const inboxIdsToDeny: InboxId[] = [];
       // ... existing code

       if (inboxIdsToDeny.length > 0) {
-        consentToInboxIdsOnProtocolByAccount({
+        await consentToInboxIdsOnProtocolByAccount({
           account,
           inboxIds: inboxIdsToDeny,
           consent: "deny",
         });
       }
+    } catch (error) {
+      captureError(error);
+      throw error; // Re-throw to trigger mutation error handlers
+    }
   },
   [blockGroupMutation, groupCreator, account, group]
 );

167-169: Consider using derived state for loading status

The loading state could be simplified using a custom hook or useMemo.

+import { useMemo } from 'react';

-const isLoading =
-  isGroupLoading || isGroupCreatorLoading || isGroupConsentLoading;
+const isLoading = useMemo(
+  () => isGroupLoading || isGroupCreatorLoading || isGroupConsentLoading,
+  [isGroupLoading, isGroupCreatorLoading, isGroupConsentLoading]
+);
features/conversation/dm-conversation.screen.tsx (3)

77-77: Avoid redundant double-negation

The double negation !!conversation is unnecessary since the conditional expression will coerce conversation to a boolean automatically. Removing it simplifies the code without changing its functionality.

Apply this diff to simplify the condition:

-          {!!conversation ? (
+          {conversation ? (
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


116-126: Implement optimistic updates for faster user experience

There's a TODO comment indicating that adding onMutate for optimistic updates would enhance the user experience by making interactions faster. The current code has this logic commented out.

Would you like assistance in implementing the onMutate callback for optimistic updates? This would allow the UI to respond immediately while the mutation is in progress.


169-182: Implement optimistic updates when sending messages

Similarly, there's a TODO suggesting the addition of onMutate to the sendMessageAsync mutation for optimistic updates. Implementing this would make message sending feel instantaneous to the user.

Would you like help in creating the onMutate function for sendMessageAsync to optimistically update the message list?

screens/Navigation/Navigation.tsx (1)

153-156: Add screen options for consistent navigation behavior

The DmConversation screen lacks navigation options, which may cause inconsistent behavior compared to other conversation-related screens. Consider adding appropriate screen options to ensure a consistent user experience.

Apply this diff to include screen options:

 <NativeStack.Screen
   name="DmConversation"
   component={DmConversationScreen}
+  options={{
+    headerTitle: '', // Set an appropriate title or component
+    headerTitleStyle: headerTitleStyle(colorScheme),
+    animation: navigationAnimation,
+    headerTintColor: yourTintColorVariable, // Use appropriate tint color variable
+  }}
 />

This aligns DmConversation with other conversation screens like ConversationNav and GroupNav, promoting consistency in navigation transitions and header styling.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9d31a49 and 47e992c.

📒 Files selected for processing (81)
  • components/ActivityIndicator/ActivityIndicator.tsx (1 hunks)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx (0 hunks)
  • components/Chat/Attachment/attachment-container.tsx (1 hunks)
  • components/Chat/Attachment/attachment-loading.tsx (1 hunks)
  • components/Chat/Attachment/remote-attachment-image.tsx (1 hunks)
  • components/Chat/ChatGroupUpdatedMessage.tsx (2 hunks)
  • components/Chat/ConsentPopup/ConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx (1 hunks)
  • components/Chat/ConsentPopup/dm-consent-popup.tsx (1 hunks)
  • components/Chat/ConsentPopup/group-consent-popup.tsx (1 hunks)
  • components/Chat/Message/MessageTail.tsx (0 hunks)
  • components/Chat/Message/MessageTimestamp.tsx (0 hunks)
  • components/Chat/Message/V3Message.tsx (3 hunks)
  • components/Chat/Message/components/message-container.tsx (1 hunks)
  • components/Chat/Message/components/message-content-container.tsx (1 hunks)
  • components/Chat/Message/components/message-layout.tsx (1 hunks)
  • components/Chat/Message/components/message-repliable.tsx (1 hunks)
  • components/Chat/Message/components/message-space-between-messages.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-reply.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-simple-text.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-static-attachment.tsx (1 hunks)
  • components/Chat/Message/message-date-change.tsx (1 hunks)
  • components/Chat/Message/message-timestamp.tsx (1 hunks)
  • components/Chat/Message/message-utils.tsx (1 hunks)
  • components/Chat/Message/stores/message-store.tsx (0 hunks)
  • components/Conversation/V3Conversation.tsx (6 hunks)
  • components/GroupAvatar.tsx (2 hunks)
  • containers/GroupScreenMembersTable.tsx (2 hunks)
  • custom-eslint-plugin/padding-before-react-hooks.js (1 hunks)
  • design-system/loader.tsx (1 hunks)
  • features/conversation-list/hooks/useMessageText.ts (1 hunks)
  • features/conversation-list/useV3ConversationItems.ts (1 hunks)
  • features/conversation/composer/add-attachment-button.tsx (2 hunks)
  • features/conversation/composer/composer.tsx (9 hunks)
  • features/conversation/composer/send-attachment-preview.tsx (2 hunks)
  • features/conversation/conversation-context.tsx (5 hunks)
  • features/conversation/conversation-persisted-stores.ts (2 hunks)
  • features/conversation/conversation-service.ts (4 hunks)
  • features/conversation/dm-conversation.screen.tsx (1 hunks)
  • features/conversations/components/V3ConversationFromPeer.tsx (1 hunks)
  • features/conversations/utils/messageIsFromCurrentUser.ts (1 hunks)
  • features/search/components/NavigationChatButton.tsx (2 hunks)
  • hooks/useGroupConsent.ts (2 hunks)
  • hooks/useGroupCreator.ts (1 hunks)
  • hooks/useGroupMembers.ts (1 hunks)
  • navigation/useNavigation.tsx (1 hunks)
  • queries/MutationKeys.ts (0 hunks)
  • queries/QueryKeys.ts (2 hunks)
  • queries/queryClient.ts (1 hunks)
  • queries/useAddToGroupMutation.ts (1 hunks)
  • queries/useAllowGroupMutation.ts (0 hunks)
  • queries/useBlockGroupMutation.ts (1 hunks)
  • queries/useConversationQuery.ts (2 hunks)
  • queries/useConversationWithPeerQuery.ts (1 hunks)
  • queries/useDmConstentStateQuery.ts (1 hunks)
  • queries/useDmPeerInbox.ts (1 hunks)
  • queries/useGroupConsentQuery.ts (2 hunks)
  • queries/useGroupDescriptionMutation.ts (1 hunks)
  • queries/useGroupMembersQuery.ts (1 hunks)
  • queries/useGroupNameQuery.ts (1 hunks)
  • queries/useGroupPermissionPolicyQuery.ts (1 hunks)
  • queries/useGroupPinnedFrameQuery.ts (1 hunks)
  • queries/useGroupQuery.ts (1 hunks)
  • queries/usePromoteToAdminMutation.ts (1 hunks)
  • queries/usePromoteToSuperAdminMutation.ts (1 hunks)
  • queries/useRemoveFromGroupMutation.ts (1 hunks)
  • queries/useRevokeAdminMutation.ts (1 hunks)
  • queries/useRevokeSuperAdminMutation.ts (1 hunks)
  • queries/useV3ConversationListQuery.ts (1 hunks)
  • screens/ConversationReadOnly.tsx (1 hunks)
  • screens/GroupInvite.tsx (1 hunks)
  • screens/Navigation/Navigation.tsx (3 hunks)
  • screens/NewConversation/NewConversation.tsx (1 hunks)
  • screens/Profile.tsx (1 hunks)
  • utils/attachment/handleStaticAttachment.ts (0 hunks)
  • utils/capture-error.ts (1 hunks)
  • utils/groupUtils/groupActionHandlers.ts (2 hunks)
  • utils/xmtpRN/contacts.ts (2 hunks)
  • utils/xmtpRN/conversations.ts (3 hunks)
💤 Files with no reviewable changes (9)
  • queries/MutationKeys.ts
  • components/Chat/ConsentPopup/ConsentPopup.tsx
  • components/Chat/Message/stores/message-store.tsx
  • components/Chat/Message/MessageTimestamp.tsx
  • utils/attachment/handleStaticAttachment.ts
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx
  • components/Chat/Message/MessageTail.tsx
  • queries/useAllowGroupMutation.ts
  • components/Chat/Attachment/AttachmentMessagePreview.tsx
✅ Files skipped from review due to trivial changes (1)
  • features/conversation-list/hooks/useMessageText.ts
🚧 Files skipped from review as they are similar to previous changes (48)
  • components/Chat/Message/message-utils.tsx
  • screens/ConversationReadOnly.tsx
  • custom-eslint-plugin/padding-before-react-hooks.js
  • components/ActivityIndicator/ActivityIndicator.tsx
  • hooks/useGroupCreator.ts
  • screens/GroupInvite.tsx
  • navigation/useNavigation.tsx
  • queries/queryClient.ts
  • queries/useBlockGroupMutation.ts
  • queries/useGroupNameQuery.ts
  • features/conversations/components/V3ConversationFromPeer.tsx
  • features/search/components/NavigationChatButton.tsx
  • components/Chat/Attachment/attachment-loading.tsx
  • features/conversation/composer/send-attachment-preview.tsx
  • queries/QueryKeys.ts
  • features/conversation/conversation-persisted-stores.ts
  • features/conversations/utils/messageIsFromCurrentUser.ts
  • components/Chat/Message/components/message-repliable.tsx
  • queries/useGroupDescriptionMutation.ts
  • features/conversation/conversation-service.ts
  • queries/useGroupQuery.ts
  • utils/groupUtils/groupActionHandlers.ts
  • queries/useGroupPinnedFrameQuery.ts
  • queries/useRevokeAdminMutation.ts
  • utils/xmtpRN/contacts.ts
  • queries/useRevokeSuperAdminMutation.ts
  • components/GroupAvatar.tsx
  • components/Chat/Message/components/message-space-between-messages.tsx
  • queries/useGroupPermissionPolicyQuery.ts
  • design-system/loader.tsx
  • queries/usePromoteToSuperAdminMutation.ts
  • utils/capture-error.ts
  • components/Chat/Attachment/attachment-container.tsx
  • components/Chat/Message/components/message-content-container.tsx
  • queries/useV3ConversationListQuery.ts
  • queries/useGroupMembersQuery.ts
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx
  • components/Chat/Message/message-content-types/message-simple-text.tsx
  • components/Chat/Message/components/message-container.tsx
  • components/Chat/Message/message-timestamp.tsx
  • screens/NewConversation/NewConversation.tsx
  • queries/useGroupConsentQuery.ts
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx
  • components/Chat/Message/message-content-types/message-static-attachment.tsx
  • queries/useDmConstentStateQuery.ts
  • hooks/useGroupMembers.ts
  • queries/usePromoteToAdminMutation.ts
  • utils/xmtpRN/conversations.ts
🧰 Additional context used
🪛 Biome (1.9.4)
features/conversation/dm-conversation.screen.tsx

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🪛 GitHub Check: tsc
features/conversation/dm-conversation.screen.tsx

[failure] 187-187:
Property 'text' does not exist on type 'ISendMessageParams'.


[failure] 187-187:
Property 'remoteAttachment' does not exist on type 'ISendMessageParams'.

🔇 Additional comments (36)
features/conversation-list/useV3ConversationItems.ts (3)

9-16: Add null check for currentAccount

The non-null assertion operator (!) is used without a proper null check, which could lead to runtime errors.


30-36: LGTM! Clear and readable filtering conditions

The use of descriptive boolean variables makes the filtering logic easy to understand and maintain.


39-39: LGTM! Clean return statement

The return statement correctly preserves the query interface while providing the filtered data.

queries/useDmPeerInbox.ts (2)

9-12: LGTM! Clear and well-structured query key.

The renaming to dmPeerInboxIdQueryKey better reflects its purpose, and the implementation follows React Query best practices.


1-8: Verify the migration to useConversationQuery

The migration from useConversationScreenQuery to useConversationQuery in this file looks good. However, based on previous review comments, there are still references to the old query pattern in other files.

✅ Verification successful

Migration to useConversationQuery is complete

The verification shows that:

  1. No references to useConversationScreenQuery exist in the codebase
  2. The new useConversationQuery hook is properly implemented with complete functionality
  3. The changes in useDmPeerInbox.ts are part of a successful migration
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the migration status and potential breaking changes

# Check for any remaining references to the old query
rg "useConversationScreenQuery" -A 2

# Check for any direct dependencies on this file
rg "from.*useDmPeerInbox" -A 2

Length of output: 1114


Script:

#!/bin/bash
# Let's check the content of useDmPeerInboxOnConversationList to see if it's using the old query pattern
rg -l "useDmPeerInboxOnConversationList" | xargs cat

# Also check the implementation of useConversationQuery to understand the new pattern
ast-grep --pattern 'export function useConversationQuery' -A 10

Length of output: 10643


Script:

#!/bin/bash
# Let's check the actual implementation of useConversationQuery in the queries directory
fd useConversationQuery.ts | xargs cat

# Also check for any remaining files that might be using the old pattern
rg -l "useConversationScreenQuery"

Length of output: 1821

queries/useRemoveFromGroupMutation.ts (3)

Line range hint 23-23: Remove redundant non-null assertion

Since topic is now a required parameter, the non-null assertion (!) in mutationKey is unnecessary.


18-18: Verify all callers handle the breaking change

The topic parameter is now required instead of optional. This is a good change for type safety, but we should verify all callers have been updated accordingly.

✅ Verification successful

Breaking change is properly handled

The codebase search shows that useRemoveFromGroupMutation is only used in one place (hooks/useGroupMembers.ts) and it correctly provides both required parameters - account and topic. No further changes are needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all usages of useRemoveFromGroupMutation to verify they provide the required topic
ast-grep --pattern 'useRemoveFromGroupMutation($account, $_)'

Length of output: 63


Script:

#!/bin/bash
# Search for all usages of useRemoveFromGroupMutation with any arguments
rg "useRemoveFromGroupMutation" -A 2

Length of output: 1974


Line range hint 12-12: Verify if refreshGroup is still needed

There are commented references to refreshGroup. Please verify if this functionality is still required for maintaining group state consistency after member removal.

Also applies to: 63-63

components/Chat/Message/V3Message.tsx (1)

101-111: ⚠️ Potential issue

Multiple message components might render simultaneously

The current implementation allows multiple message type conditions to be true simultaneously, potentially causing multiple components to render for a single message.

Previous review suggestion still applies. Use mutually exclusive conditions:

-{isGroupUpdatedMessage(message) && (
-  <ChatGroupUpdatedMessage message={message} />
-)}
-{isReplyMessage(message) && <MessageReply message={message} />}
-{isTextMessage(message) && <MessageSimpleText message={message} />}
-{isRemoteAttachmentMessage(message) && (
-  <MessageRemoteAttachment message={message} />
-)}
-{isStaticAttachmentMessage(message) && (
-  <MessageStaticAttachment message={message} />
-)}
+{isGroupUpdatedMessage(message) ? (
+  <ChatGroupUpdatedMessage message={message} />
+) : isReplyMessage(message) ? (
+  <MessageReply message={message} />
+) : isTextMessage(message) ? (
+  <MessageSimpleText message={message} />
+) : isRemoteAttachmentMessage(message) ? (
+  <MessageRemoteAttachment message={message} />
+) : isStaticAttachmentMessage(message) ? (
+  <MessageStaticAttachment message={message} />
+) : null}
containers/GroupScreenMembersTable.tsx (1)

45-48: ⚠️ Potential issue

Improve type safety of topic handling

Using non-null assertion with optional chaining (topic ?? group?.topic)! could lead to runtime errors if both values are undefined.

Previous review suggestion still applies. Extract and validate the topic:

+const topicValue = useMemo(() => topic ?? group?.topic, [topic, group?.topic]);
+
+if (!topicValue) {
+  logger.warn('No valid topic found for group members table');
+  return null;
+}
+
-const { members, ... } = useGroupMembers((topic ?? group?.topic)!);
+const { members, ... } = useGroupMembers(topicValue);

const { data: groupPermissionPolicy } = useGroupPermissionPolicyQuery(
  currentAccount,
-  (topic ?? group?.topic)!
+  topicValue
);
components/Conversation/V3Conversation.tsx (4)

106-108: Await the asynchronous sendMessage function

The sendMessage call is asynchronous but not awaited, which could lead to unhandled promise rejections.


153-155: Remove debug console.log statements

There are console.log statements present which should be removed for production code.


202-204: Remove debug console.log statements

There are console.log statements present which should be removed for production code.


220-241: LGTM! Well-structured message list implementation

The MessagesList component is well-implemented with proper TypeScript types and efficient message rendering.

screens/Profile.tsx (1)

145-146: Consider safer alternatives to non-null assertions

The non-null assertions (!) on groupTopic could lead to runtime errors if the screen is accessed without a group topic.

components/Chat/Attachment/remote-attachment-image.tsx (4)

21-26: LGTM! Props interface is well-structured.

The type definition is clear and follows TypeScript best practices.


78-80: Implement the 'openInWebview' functionality

The onPress handler for unsupported media types is currently empty, which means users cannot open the attachment in a webview as intended.


95-98: Prevent division by zero when calculating aspect ratio

Ensure that attachment.imageSize.height is not zero before performing division to prevent potential runtime errors.


152-155: Handle potential invalid contentLength values when parsing

parseFloat(content.contentLength) may return NaN if content.contentLength is not a valid number string.

components/Chat/Message/message-content-types/message-reply.tsx (1)

216-227: Handle potential undefined return in getReplyMessageSafeText

Ensure that content.content.text exists before returning it to prevent potential runtime errors.

queries/useConversationWithPeerQuery.ts (2)

16-16: Remove unsafe non-null assertion on peer

The non-null assertion (!) on peer in the queryKey is unsafe as peer is explicitly typed as string | undefined.

-queryKey: conversationWithPeerQueryKey(account, peer!),
+queryKey: conversationWithPeerQueryKey(account, peer),

22-29: Add error handling for conversation fetching

The conversation fetching lacks explicit error handling.

-      const conversation = await getConversationByPeerByAccount({
-        account,
-        peer,
-        includeSync: true,
-      });
-
-      return conversation;
+      try {
+        const conversation = await getConversationByPeerByAccount({
+          account,
+          peer,
+          includeSync: true,
+        });
+        return conversation;
+      } catch (error) {
+        logger.error("[Conversation] Failed to fetch conversation", { 
+          peer, 
+          account, 
+          error 
+        });
+        throw error;
+      }
features/conversation/conversation-context.tsx (2)

1-1: Fix typo in import path for useDmConsentQuery

There's a typo in the import path: useDmConstentStateQuery should be useDmConsentStateQuery.

-import { useDmConsentQuery } from "@/queries/useDmConstentStateQuery";
+import { useDmConsentQuery } from "@/queries/useDmConsentStateQuery";

67-73: ⚠️ Potential issue

Handle potential undefined topic in consent queries

The topic parameter is used with a non-null assertion in useGroupConsentQuery but not in useDmConsentQuery. This inconsistency could lead to runtime errors.

-  const { data: groupConsent, isLoading: isLoadingGroupConsent } =
-    useGroupConsentQuery(currentAccount, topic!);
+  const { data: groupConsent, isLoading: isLoadingGroupConsent } =
+    useGroupConsentQuery(currentAccount, topic);

   const { data: dmConsent, isLoading: isLoadingDmConsent } = useDmConsentQuery({
     account: currentAccount,
     topic,
   });

Likely invalid or redundant comment.

features/conversation/composer/composer.tsx (1)

99-102: ⚠️ Potential issue

Remove non-null assertion operator

The use of ! operator on getUploadedRemoteAttachment() could lead to runtime errors if the attachment is undefined.

-      const uploadedRemoteAttachment = getUploadedRemoteAttachment()!;
+      const uploadedRemoteAttachment = getUploadedRemoteAttachment();
+      if (!uploadedRemoteAttachment) {
+        throw new Error("Failed to get uploaded attachment");
+      }

Likely invalid or redundant comment.

components/Chat/Message/message-date-change.tsx (1)

26-31: Consider adding error handling for animation setup

The useDerivedValue hook should handle potential edge cases where showTimeAV might be undefined.

 const showTimeProgressAV = useDerivedValue(() => {
+  if (!showTimeAV?.value) return withSpring(0, {
+    damping: SICK_DAMPING,
+    stiffness: SICK_STIFFNESS,
+  });
   return withSpring(showTimeAV.value ? 1 : 0, {
     damping: SICK_DAMPING,
     stiffness: SICK_STIFFNESS,
   });
 });
components/Chat/Message/components/message-layout.tsx (1)

51-51: Enhance accessibility for interactive message content

The Pressable component should include proper accessibility attributes.

components/Chat/ChatGroupUpdatedMessage.tsx (1)

33-36: Implement proper error handling for string content

The current implementation silently returns null when content is a string.

components/Chat/ConsentPopup/group-consent-popup.tsx (2)

23-23: Handle potential null case for currentAccount

The non-null assertion (!) on useCurrentAccount() could lead to runtime errors if the account is not available.


46-55: 🛠️ Refactor suggestion

Add loading and error states for async operations

The onAccept handler should show loading state and handle errors more gracefully.

+const [isAccepting, setIsAccepting] = useState(false);
 const onAccept = useCallback(async () => {
+  setIsAccepting(true);
   try {
     await allowGroup({
       includeCreator: false,
       includeAddedBy: false,
     });
   } catch (error) {
     captureErrorWithToast(error);
+  } finally {
+    setIsAccepting(false);
   }
 }, [allowGroup]);

Likely invalid or redundant comment.

components/Chat/ConsentPopup/dm-consent-popup.tsx (1)

48-66: ⚠️ Potential issue

Add type safety and prevent potential race conditions

The mutation success callback should verify the account is still valid before refetching.

 mutationFn: async (args: { consent: "allow" | "deny" }) => {
-  const currentAccount = getCurrentAccount()!;
+  const currentAccount = getCurrentAccount();
+  if (!currentAccount) {
+    throw new Error("No active account");
+  }
   await Promise.all([
     // ... existing code
   ]);
 },
 onSuccess: () => {
-  refetchConversationQuery(getCurrentAccount()!, topic);
+  const account = getCurrentAccount();
+  if (account) {
+    refetchConversationQuery(account, topic);
+  }
 },

Likely invalid or redundant comment.

queries/useAddToGroupMutation.ts (3)

Line range hint 23-42: Clean up redundant null checks

The topic parameter is now required, but there are still several unnecessary null checks throughout the mutation callbacks.

  mutationFn: async (addresses: string[]) => {
-   if (!group || !account || !topic) {
+   if (!group || !account) {
      return;
    }
    await group.addMembers(addresses);
    return addresses;
  },
  onMutate: async (_addresses: string[]) => {
-   if (!topic) {
-     return;
-   }
    await cancelGroupMembersQuery(account, topic);
  },
  onSuccess: (_data, _variables, _context) => {
    logger.debug("onSuccess useAddToGroupMutation");
-   if (!topic) {
-     return;
-   }
    invalidateGroupMembersQuery(account, topic);
  }

16-16: 🛠️ Refactor suggestion

Remove redundant non-null assertion in mutation key

Since topic is now a required parameter, the non-null assertion (!) in mutationKey: addMemberMutationKey(account, topic!) is redundant and should be removed.

- mutationKey: addMemberMutationKey(account, topic!),
+ mutationKey: addMemberMutationKey(account, topic),

Likely invalid or redundant comment.


Line range hint 11-11: Verify the commented out group refresh functionality

There are two related commented out lines:

  1. Import: // import { refreshGroup } from "../utils/xmtpRN/conversations"
  2. Function call: // refreshGroup(account, topic)

Please verify if this functionality should be:

  • Removed if it's no longer needed
  • Uncommented if it's required for proper group synchronization

Also applies to: 42-42

features/conversation/dm-conversation.screen.tsx (1)

225-233: Typo in parameter name peerAddresss

There's a typo in the function parameter peerAddresss; it should be peerAddress. This typo can cause bugs due to referencing an undefined variable.

Apply this diff to correct the parameter name and its usage:

-function useNewConversationHeader(peerAddresss: string) {
+function useNewConversationHeader(peerAddress: string) {

   useEffect(() => {
     navigation.setOptions({
-        headerTitle: () => <NewConversationTitle peerAddress={peerAddresss} />,
+        headerTitle: () => <NewConversationTitle peerAddress={peerAddress} />,
     });
-  }, [peerAddresss, navigation]);
+  }, [peerAddress, navigation]);
 }
screens/Navigation/Navigation.tsx (1)

79-85: Consider using a discriminated union for better type safety

The current union type for DmConversation allows both peerAddress and topic to be provided together, which might lead to runtime confusion. Using a discriminated union makes the parameters mutually exclusive and enhances type safety.

Apply this diff to implement a discriminated union:

 export type NavigationParamList = {
   // ...
   DmConversation:
-    | {
-        peerAddress: string;
-      }
-    | {
-        topic: string;
-      };
+    | {
+        type: 'peer';
+        peerAddress: string;
+      }
+    | {
+        type: 'topic';
+        topic: string;
+      };
   // ...
 };

Comment on lines +44 to +48
if (typeof replyMessageContent === "string") {
// TODO. Render simple bubble message with the content?
console.error("reply message is a string");
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace console.error with proper error tracking

Use the existing sentryTrackError utility instead of console.error for consistent error tracking.

-    console.error("reply message is a string");
+    sentryTrackError(new Error("Unexpected string content in reply message"));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof replyMessageContent === "string") {
// TODO. Render simple bubble message with the content?
console.error("reply message is a string");
return null;
}
if (typeof replyMessageContent === "string") {
// TODO. Render simple bubble message with the content?
sentryTrackError(new Error("Unexpected string content in reply message"));
return null;
}

Comment on lines 24 to +25
queryKey: conversationQueryKey(account, topic!),
queryFn: async () => {
logger.info("[Crash Debug] queryFn fetching group");
if (!topic) {
return null;
}
const conversation = await getConversationByTopicByAccount({
account,
topic,
includeSync: true,
});
return conversation;
},
queryFn: () => getConversation(account, topic!),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unsafe non-null assertion on topic

Similar to the peer query, the non-null assertion on topic is unsafe.

-    queryKey: conversationQueryKey(account, topic!),
-    queryFn: () => getConversation(account, topic!),
+    queryKey: conversationQueryKey(account, topic),
+    queryFn: () => {
+      if (!topic) throw new Error("Topic is required");
+      return getConversation(account, topic);
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryKey: conversationQueryKey(account, topic!),
queryFn: async () => {
logger.info("[Crash Debug] queryFn fetching group");
if (!topic) {
return null;
}
const conversation = await getConversationByTopicByAccount({
account,
topic,
includeSync: true,
});
return conversation;
},
queryFn: () => getConversation(account, topic!),
queryKey: conversationQueryKey(account, topic),
queryFn: () => {
if (!topic) throw new Error("Topic is required");
return getConversation(account, topic);
},

conversationNotFound: !conversation && !isLoadingConversation,
conversationVersion: conversation?.version,
isAllowedConversation,
isBlockedConversation: conversation?.state === "denied", // TODO: implement this
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove TODO comment and implement conversation blocking

The code contains a TODO comment about implementing conversation blocking, but the feature is already being used.

Either implement the blocking feature properly or remove the reliance on the state === "denied" check until it's implemented.

-        isBlockedConversation: conversation?.state === "denied", // TODO: implement this
+        isBlockedConversation: false, // Blocking feature not yet implemented
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isBlockedConversation: conversation?.state === "denied", // TODO: implement this
isBlockedConversation: false, // Blocking feature not yet implemented

Comment on lines +48 to +70
return (
<AnimatedHStack
layout={theme.animation.reanimatedSpringLayoutTransition}
style={{
// ...debugBorder("red"),
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
columnGap: theme.spacing["4xs"],
marginVertical: theme.spacing.sm,
}}
>
<Text preset="smaller" color="secondary">
{messageDate}
</Text>
<AnimatedText
preset="smaller"
color="secondary"
style={timeInlineAnimatedStyle}
>
{messageTime}
</AnimatedText>
</AnimatedHStack>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility support for time display

The animated time display should be accessible to screen readers.

 <AnimatedHStack
   layout={theme.animation.reanimatedSpringLayoutTransition}
+  accessibilityRole="text"
+  accessibilityLabel={`Message sent ${messageDate} at ${messageTime}`}
   style={{
     alignSelf: "center",
     alignItems: "center",
     justifyContent: "center",
     columnGap: theme.spacing["4xs"],
     marginVertical: theme.spacing.sm,
   }}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<AnimatedHStack
layout={theme.animation.reanimatedSpringLayoutTransition}
style={{
// ...debugBorder("red"),
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
columnGap: theme.spacing["4xs"],
marginVertical: theme.spacing.sm,
}}
>
<Text preset="smaller" color="secondary">
{messageDate}
</Text>
<AnimatedText
preset="smaller"
color="secondary"
style={timeInlineAnimatedStyle}
>
{messageTime}
</AnimatedText>
</AnimatedHStack>
return (
<AnimatedHStack
layout={theme.animation.reanimatedSpringLayoutTransition}
accessibilityRole="text"
accessibilityLabel={`Message sent ${messageDate} at ${messageTime}`}
style={{
// ...debugBorder("red"),
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
columnGap: theme.spacing["4xs"],
marginVertical: theme.spacing.sm,
}}
>
<Text preset="smaller" color="secondary">
{messageDate}
</Text>
<AnimatedText
preset="smaller"
color="secondary"
style={timeInlineAnimatedStyle}
>
{messageTime}
</AnimatedText>
</AnimatedHStack>

Comment on lines +30 to +32
const handleReply = useCallback(() => {
setCurrentConversationReplyToMessageId(messageId);
}, [messageId]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for setCurrentConversationReplyToMessageId

The handleReply callback should handle potential failures when setting the reply message ID.

 const handleReply = useCallback(() => {
-  setCurrentConversationReplyToMessageId(messageId);
+  try {
+    setCurrentConversationReplyToMessageId(messageId);
+  } catch (error) {
+    console.error('Failed to set reply message:', error);
+    // TODO: Show user feedback
+  }
 }, [messageId]);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +63 to +69
{content.metadataFieldsChanged.map((entry, index) => (
<ChatGroupMetadataUpdate
key={`metadata-${index}`}
metadataEntry={entry}
initiatorInboxId={content.initiatedByInboxId as InboxId}
/>
))}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error boundary for metadata updates

The metadata update rendering should be wrapped in an error boundary to prevent the entire component from crashing.

+ import { ErrorBoundary } from '@/components/ErrorBoundary';

  {content.metadataFieldsChanged.map((entry, index) => (
+   <ErrorBoundary key={`metadata-${index}`} fallback={<Text>Failed to load metadata update</Text>}>
      <ChatGroupMetadataUpdate
-       key={`metadata-${index}`}
        metadataEntry={entry}
        initiatorInboxId={content.initiatedByInboxId as InboxId}
      />
+   </ErrorBoundary>
  ))}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +31 to +44
const onBlock = useCallback(async () => {
groupRemoveRestoreHandler(
"unknown", // To display "Remove & Block inviter"
colorScheme,
groupName,
allowGroup,
blockGroup
)((success: boolean) => {
if (success) {
navigation.pop();
}
// If not successful, do nothing (user canceled)
});
}, [groupName, colorScheme, allowGroup, blockGroup, navigation]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error boundary to handle failures gracefully

The component should be wrapped in an error boundary to gracefully handle any failures in the consent operations.

+import { ErrorBoundary } from "@/components/ErrorBoundary";

 export function GroupConsentPopup(props: { topic: ConversationTopic }) {
+  return (
+    <ErrorBoundary fallback={<ConsentPopupError />}>
+      <GroupConsentPopupContent {...props} />
+    </ErrorBoundary>
+  );
+}

+function GroupConsentPopupContent(props: { topic: ConversationTopic }) {
   const { topic } = props;
   // ... rest of the component

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +77 to +92
async (selectedIndex?: number) => {
if (selectedIndex === 0) {
try {
await consentToInboxIdsOnProtocolByAccountAsync({
consent: "deny",
});
navigation.pop();
} catch (error) {
captureError(error);
showSnackbar({
type: "error",
message: `Error consenting`,
});
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error messages and type safety

The error handling should provide more specific error messages and ensure type safety for the action sheet callback.

-async (selectedIndex?: number) => {
+async (selectedIndex: number | undefined) => {
   if (selectedIndex === 0) {
     try {
       await consentToInboxIdsOnProtocolByAccountAsync({
         consent: "deny",
       });
       navigation.pop();
     } catch (error) {
       captureError(error);
       showSnackbar({
         type: "error",
-        message: `Error consenting`,
+        message: translate("error_blocking_contact"),
       });
     }
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async (selectedIndex?: number) => {
if (selectedIndex === 0) {
try {
await consentToInboxIdsOnProtocolByAccountAsync({
consent: "deny",
});
navigation.pop();
} catch (error) {
captureError(error);
showSnackbar({
type: "error",
message: `Error consenting`,
});
}
}
}
async (selectedIndex: number | undefined) => {
if (selectedIndex === 0) {
try {
await consentToInboxIdsOnProtocolByAccountAsync({
consent: "deny",
});
navigation.pop();
} catch (error) {
captureError(error);
showSnackbar({
type: "error",
message: translate("error_blocking_contact"),
});
}
}
}

Comment on lines 67 to 83
await Promise.all([
consentToGroupsOnProtocolByAccount({
account,
groupIds: [getV3IdFromTopic(topic)],
consent: "allow",
}),
...(inboxIdsToAllow.length > 0
? [
consentToInboxIdsOnProtocolByAccount({
account,
inboxIds: inboxIdsToAllow,
consent: "allow",
}),
]
: []),
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add cleanup handling for concurrent operations

The Promise.all should handle cleanup for partial failures.

+const results: Array<{ success: boolean; operation: string }> = [];
 await Promise.all([
   consentToGroupsOnProtocolByAccount({
     account,
     groupIds: [getV3IdFromTopic(topic)],
     consent: "allow",
-  }),
+  }).then(() => results.push({ success: true, operation: 'group' }))
+    .catch(() => results.push({ success: false, operation: 'group' })),
   ...(inboxIdsToAllow.length > 0
     ? [
         consentToInboxIdsOnProtocolByAccount({
           account,
           inboxIds: inboxIdsToAllow,
           consent: "allow",
-        }),
+        }).then(() => results.push({ success: true, operation: 'inbox' }))
+          .catch(() => results.push({ success: false, operation: 'inbox' })),
       ]
     : []),
 ]);
+
+if (results.some(r => !r.success)) {
+  throw new Error(`Failed operations: ${results.filter(r => !r.success).map(r => r.operation).join(', ')}`);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await Promise.all([
consentToGroupsOnProtocolByAccount({
account,
groupIds: [getV3IdFromTopic(topic)],
consent: "allow",
}),
...(inboxIdsToAllow.length > 0
? [
consentToInboxIdsOnProtocolByAccount({
account,
inboxIds: inboxIdsToAllow,
consent: "allow",
}),
]
: []),
]);
const results: Array<{ success: boolean; operation: string }> = [];
await Promise.all([
consentToGroupsOnProtocolByAccount({
account,
groupIds: [getV3IdFromTopic(topic)],
consent: "allow",
}).then(() => results.push({ success: true, operation: 'group' }))
.catch(() => results.push({ success: false, operation: 'group' })),
...(inboxIdsToAllow.length > 0
? [
consentToInboxIdsOnProtocolByAccount({
account,
inboxIds: inboxIdsToAllow,
consent: "allow",
}).then(() => results.push({ success: true, operation: 'inbox' }))
.catch(() => results.push({ success: false, operation: 'inbox' })),
]
: []),
]);
if (results.some(r => !r.success)) {
throw new Error(`Failed operations: ${results.filter(r => !r.success).map(r => r.operation).join(', ')}`);
}

Comment on lines 184 to 197
const handleSendMessage = useCallback(
async (args: IComposerSendArgs) => {
try {
const { text, remoteAttachment, referencedMessageId } = args;
const newConversation = await createNewConversationAsync(peerAddress);
await sendMessageAsync({
conversation: newConversation,
text,
remoteAttachment,
referencedMessageId,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type error: 'text' and 'remoteAttachment' do not exist on type 'IComposerSendArgs'

In the handleSendMessage function, the destructured properties text and remoteAttachment are not present on the type IComposerSendArgs, leading to a TypeScript error.

To resolve this issue, update the IComposerSendArgs interface to include text and remoteAttachment, or adjust the destructuring to match the actual properties of IComposerSendArgs.

Apply this diff if IComposerSendArgs should include these properties:

 interface IComposerSendArgs {
   referencedMessageId?: MessageId;
+  text?: string;
+  remoteAttachment?: RemoteAttachmentContent;
 }

Alternatively, if IComposerSendArgs is correctly defined elsewhere, ensure you're using the appropriate type that includes text and remoteAttachment.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: tsc

[failure] 187-187:
Property 'text' does not exist on type 'ISendMessageParams'.


[failure] 187-187:
Property 'remoteAttachment' does not exist on type 'ISendMessageParams'.

Copy link
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: 3

🧹 Outside diff range and nitpick comments (9)
components/Conversation/V3Conversation.tsx (2)

210-214: Consider extracting common consent popup logic

The consent popup rendering logic is duplicated between DmContent and GroupContent. Consider extracting this into a shared component or hook.

Example implementation:

const ConsentPopupWrapper = ({ 
  isAllowed, 
  isLoading, 
  children 
}: { 
  isAllowed: boolean;
  isLoading: boolean;
  children: React.ReactNode;
}) => {
  if (isAllowed || isLoading) return null;
  return <>{children}</>;
};

Usage:

- ListHeaderComponent={
-   !isAllowedConversation && !isLoadingConversationConsent ? (
-     <GroupConsentPopup topic={topic} />
-   ) : undefined
- }
+ ListHeaderComponent={
+   <ConsentPopupWrapper 
+     isAllowed={isAllowedConversation} 
+     isLoading={isLoadingConversationConsent}
+   >
+     <GroupConsentPopup topic={topic} />
+   </ConsentPopupWrapper>
+ }

233-241: Consider memoizing the renderItem function

The renderItem function is recreated on every render. Consider memoizing it with useCallback to optimize performance.

+ const renderMessage = useCallback(({ item, index }: { item: string, index: number }) => (
+   <V3Message
+     nextMessageId={messageIds[index - 1]}
+     previousMessageId={messageIds[index + 1]}
+     messageId={item}
+   />
+ ), [messageIds]);

  return (
    <Animated.FlatList
      inverted
      data={messageIds}
-     renderItem={({ item, index }) => {
-       return (
-         <V3Message
-           nextMessageId={messageIds[index - 1]}
-           previousMessageId={messageIds[index + 1]}
-           messageId={item}
-         />
-       );
-     }}
+     renderItem={renderMessage}
features/conversation/dm-conversation.screen.tsx (7)

1-6: Enhance documentation with implementation details and usage guidelines.

While the WIP comment indicates the architectural goal of decoupling conversations, consider adding:

  • Implementation details and constraints
  • Usage guidelines and examples
  • Migration steps for existing code

77-81: Simplify boolean condition.

The double negation !!conversation is unnecessary.

-      {!!conversation ? (
+      {conversation ? (
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


198-203: Enhance error handling with specific error messages.

The current error handling shows a generic message. Consider providing more specific error messages based on the error type.

-        showSnackbar({
-          message: "Failed to send message",
-        });
+        const errorMessage = error instanceof Error 
+          ? error.message 
+          : "Failed to send message";
+        showSnackbar({
+          message: errorMessage,
+        });

218-225: Implement empty state UI for new conversations.

The TODO comment indicates that an empty state UI is needed. This should provide context and guidance to users starting a new conversation.

Would you like me to help implement an empty state component that includes:

  • Welcome message
  • Guidelines for starting a conversation
  • Privacy reminders

254-257: Implement empty state for conversations without messages.

Similar to the new conversation component, this needs an empty state UI to provide context when there are no messages.

Would you like me to help implement an empty state that shows:

  • Conversation start date
  • Participant information
  • Message composition hints

250-250: Replace non-null assertion with proper type checking.

Using the non-null assertion operator (!) can lead to runtime errors. Consider proper type checking instead.

-  } = useConversationMessages(currentAccount, conversation.topic!);
+  } = useConversationMessages(
+    currentAccount,
+    conversation.topic ?? throw new Error("Conversation topic is required")
+  );

281-281: Improve type safety in header title component.

The non-null assertion on topic should be replaced with proper type checking.

-      headerTitle: () => <DmConversationTitle topic={topic!} />,
+      headerTitle: () => topic 
+        ? <DmConversationTitle topic={topic} />
+        : <Text>Loading...</Text>,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 47e992c and 8daf61a.

⛔ Files ignored due to path filters (1)
  • queries/__snapshots__/QueryKeys.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • components/Conversation/V3Conversation.tsx (7 hunks)
  • features/conversation/dm-conversation.screen.tsx (1 hunks)
  • hooks/useGroupCreator.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/useGroupCreator.ts
🧰 Additional context used
🪛 Biome (1.9.4)
features/conversation/dm-conversation.screen.tsx

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🔇 Additional comments (5)
components/Conversation/V3Conversation.tsx (4)

106-108: Await the asynchronous sendMessage function

The sendMessage call is not awaited, which could lead to unhandled promise rejections or unexpected behavior.


153-155: Remove debug console.log statements

These console.log statements should be removed for production code.


161-171: LGTM: Well-implemented consent popup integration

The conditional rendering of the DmConsentPopup is well implemented with proper checks for consent status and loading state.


336-338: LGTM: Proper message structure

The message structure with the content object is correctly implemented.

features/conversation/dm-conversation.screen.tsx (1)

228-236: Fix typo in parameter name peerAddresss.

The parameter has an extra 's' in the name.

Comment on lines +128 to +182
const { mutateAsync: sendMessageAsync, status: sendMessageStatus } =
useMutation({
mutationFn: async (args: {
conversation: ConversationWithCodecsType;
text?: string;
remoteAttachment?: RemoteAttachmentContent;
referencedMessageId?: MessageId;
}) => {
const { conversation, text, remoteAttachment, referencedMessageId } =
args;

if (referencedMessageId) {
if (remoteAttachment) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { remoteAttachment },
},
});
}
if (text) {
await conversation.send({
reply: {
reference: referencedMessageId,
content: { text },
},
});
}
return;
}

if (remoteAttachment) {
await conversation.send({
remoteAttachment,
});
}

if (text) {
await conversation.send(text);
}
},
// TODO: Add this for optimistic update and faster UX
// onMutate: (args) => {
// try {
// const { conversation } = args;
// const currentAccount = getCurrentAccount()!;
// addConversationMessage(currentAccount, conversation.topic!, {
// id: "RANDOM_MESSAGE_ID",
// content: { text: "RANDOM_MESSAGE_TEXT" },
// });
// } catch (error) {
// console.log("error:", error);
// }
// },
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor message sending logic for better maintainability.

The message sending logic has complex branching that could be simplified. Consider extracting the message sending logic into separate functions for different message types.

+ const sendReplyMessage = async (
+   conversation: ConversationWithCodecsType,
+   referencedMessageId: MessageId,
+   text?: string,
+   remoteAttachment?: RemoteAttachmentContent
+ ) => {
+   if (remoteAttachment) {
+     await conversation.send({
+       reply: { reference: referencedMessageId, content: { remoteAttachment } },
+     });
+   }
+   if (text) {
+     await conversation.send({
+       reply: { reference: referencedMessageId, content: { text } },
+     });
+   }
+ };
+
+ const sendRegularMessage = async (
+   conversation: ConversationWithCodecsType,
+   text?: string,
+   remoteAttachment?: RemoteAttachmentContent
+ ) => {
+   if (remoteAttachment) {
+     await conversation.send({ remoteAttachment });
+   }
+   if (text) {
+     await conversation.send(text);
+   }
+ };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +48 to +49
// @ts-ignore
const { peerAddress } = props.route.params;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove @ts-ignore and properly type the route params.

Instead of using @ts-ignore, properly type the route parameters to maintain type safety.

-  // @ts-ignore
-  const { peerAddress } = props.route.params;
+  const { peerAddress } = props.route.params as { peerAddress: string };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +61 to +73
if (isLoading) {
return (
<Screen contentContainerStyle={{ flex: 1 }}>
<Center
style={{
flex: 1,
}}
>
<Loader />
</Center>
</Screen>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance loading state with error handling.

The loading state should handle potential errors and provide feedback to users.

+  if (error) {
+    return (
+      <Screen contentContainerStyle={{ flex: 1 }}>
+        <Center style={{ flex: 1 }}>
+          <Text>Failed to load conversation. Please try again.</Text>
+        </Center>
+      </Screen>
+    );
+  }

Committable suggestion skipped: line range outside the PR's diff.

@alexrisch alexrisch force-pushed the ts/message-refactoring-attachments branch from 8daf61a to 71fc31d Compare December 4, 2024 15:25
Copy link
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

🧹 Outside diff range and nitpick comments (25)
queries/useConversationWithPeerQuery.ts (1)

18-18: Improve debug log message

The current debug message could be more descriptive by including the peer address being queried.

-      logger.info("[Crash Debug] queryFn fetching conversation with peer");
+      logger.info(`[Crash Debug] Fetching conversation with peer: ${peer}`);
features/conversation-list/useV3ConversationItems.ts (1)

30-36: Consider extracting filter logic for better maintainability

While the current implementation is clear, the filter logic could be extracted into a separate function for better reusability and testability.

+ const isConversationVisible = (
+   conversation: ConversationType,
+   pinnedTopics: Set<string>,
+   deletedTopics: Set<string>
+ ) => {
+   const isAllowed = conversation.state === "allowed";
+   const isNotPinned = !pinnedTopics.has(conversation.topic);
+   const isNotDeleted = !deletedTopics.has(conversation.topic);
+   return isAllowed && isNotPinned && isNotDeleted;
+ };

  const conversationsFiltered = useMemo(() => {
    const pinnedTopics = new Set(pinnedConversationTopics);
    const deletedTopics = new Set(
      Object.entries(topicsData)
        .filter(([_, data]) => data?.status === "deleted")
        .map(([topic]) => topic)
    );

-   return conversations?.filter((conversation) => {
-     const isAllowed = conversation.state === "allowed";
-     const isNotPinned = !pinnedTopics.has(conversation.topic);
-     const isNotDeleted = !deletedTopics.has(conversation.topic);
-
-     return isAllowed && isNotPinned && isNotDeleted;
-   });
+   return conversations?.filter(conversation => 
+     isConversationVisible(conversation, pinnedTopics, deletedTopics)
+   );
  }, [conversations, pinnedConversationTopics, topicsData]);
components/Chat/Message/message-content-types/message-reply.tsx (1)

82-87: Consider extracting complex style calculations.

The border radius calculation is complex and repeated throughout the component. Consider extracting it into a theme constant or utility function.

+ const getMessageBorderRadius = (theme, padding) => 
+   theme.borderRadius.message.attachment - padding / 2;

  containerProps={{
    style: {
      width: "100%",
-     borderRadius:
-       theme.borderRadius.message.attachment -
-       theme.spacing.message.replyMessage.horizontalPadding / 2,
+     borderRadius: getMessageBorderRadius(
+       theme,
+       theme.spacing.message.replyMessage.horizontalPadding
+     ),
    },
  }}
features/conversation/composer/composer.tsx (4)

68-68: Consider removing unnecessary type alias

The type IComposerSendArgs is just an alias for ISendMessageParams. Consider using ISendMessageParams directly to reduce type indirection.

-export type IComposerSendArgs = ISendMessageParams;

Line range hint 78-133: Consider splitting the send function

The send function handles both media preview and text input cases. Consider splitting it into separate functions for better maintainability.

-  const send = useCallback(async () => {
+  const sendMediaPreview = useCallback(async () => {
     const mediaPreview = getComposerMediaPreview();
+    if (!mediaPreview) return;
+
+    if (mediaPreview?.status === "uploading") {
+      await waitUntilMediaPreviewIsUploaded();
+    }
+
+    setComposerMediaPreviewStatus("sending");
+
+    try {
+      await saveAttachmentLocally();
+    } catch (error) {
+      sentryTrackError(error);
+    }
+
+    const uploadedRemoteAttachment = getUploadedRemoteAttachment();
+    if (!uploadedRemoteAttachment) {
+      console.error('Failed to get uploaded remote attachment');
+      return;
+    }
+
+    const replyingToMessageId = getCurrentConversationReplyToMessageId();
+    await onSend({
+      content: {
+        remoteAttachment: uploadedRemoteAttachment,
+      },
+      ...(replyingToMessageId && {
+        referencedMessageId: replyingToMessageId,
+      }),
+    });
+
+    resetUploadedRemoteAttachment();
+    resetComposerMediaPreview();
+  }, [onSend]);
+
+  const sendTextMessage = useCallback(async () => {
+    const inputValue = getCurrentConversationInputValue();
+    if (inputValue.length === 0) return;
+
+    const replyingToMessageId = getCurrentConversationReplyToMessageId();
+    await onSend({
+      content: {
+        text: inputValue,
+      },
+      ...(replyingToMessageId && {
+        referencedMessageId: replyingToMessageId,
+      }),
+    });
+  }, [onSend]);
+
+  const send = useCallback(async () => {
+    await sendMediaPreview();
+    await sendTextMessage();
+
+    // Reset stuff
+    setCurrentConversationInputValue("");
+    setCurrentConversationReplyToMessageId(null);
   }, [onSend]);

Line range hint 129-132: Address TODO comment about scroll functionality

The TODO comment indicates missing scroll functionality. This could affect user experience when sending messages.

Would you like me to help implement the scroll functionality or create a GitHub issue to track this?


363-371: Extract duplicate styles into a shared constant

The same style object is duplicated for RemoteAttachmentImage. Consider extracting it into a shared constant.

+const attachmentImageStyles = (theme) => ({
+  height: theme.avatarSize.md,
+  width: theme.avatarSize.md,
+  borderRadius: theme.borderRadius.xs,
+});

// Then use it in both places:
<RemoteAttachmentImage
  messageId={messageId}
  remoteMessageContent={content}
  containerProps={{
-    style: {
-      height: theme.avatarSize.md,
-      width: theme.avatarSize.md,
-      borderRadius: theme.borderRadius.xs,
-    },
+    style: attachmentImageStyles(theme),
  }}
/>

Also applies to: 390-398

queries/useGroupConsentQuery.ts (2)

Line range hint 15-22: Simplify enabled condition

The enabled condition uses double non-null assertions which can be simplified.

-    enabled: !!group && !!topic,
+    enabled: Boolean(group && topic),

29-33: Add error handling for missing data

The function should handle cases where query data is undefined.

export const getGroupConsentQueryData = (
  account: string,
  topic: ConversationTopic
) =>
-  queryClient.getQueryData<GroupConsentQueryData>(
+  queryClient.getQueryData<GroupConsentQueryData>(
    groupConsentQueryKey(account, topic)
-  );
+  ) ?? null;
queries/useConversationQuery.ts (1)

50-57: Add error handling for refetch operation

The refetch operation should handle potential failures.

export function refetchConversationQuery(
  account: string,
  topic: ConversationTopic
) {
-  return queryClient.refetchQueries({
+  return queryClient.refetchQueries({
    queryKey: conversationQueryKey(account, topic),
-  });
+  }).catch((error) => {
+    console.error('Failed to refetch conversation:', error);
+    throw error;
+  });
}
components/Chat/ConsentPopup/dm-consent-popup.tsx (1)

108-115: Consider adding loading state UI feedback

The current implementation hides the popup during loading, which might confuse users on slow connections.

Consider showing a loading indicator instead of null when the status is "pending".

hooks/useGroupConsent.ts (1)

101-118: Enhance error handling in mutation

The error handling in the mutation could be improved by providing more specific error messages and recovery actions.

 onError: (error, _variables, context) => {
-  captureError(error);
+  logger.error('Group consent mutation failed', { error, topic });
+  captureError(error, { 
+    tags: { operation: 'group_consent' },
+    extra: { topic }
+  });
   if (!context) {
+    showSnackbar({
+      type: 'error',
+      message: translate('group_consent_error')
+    });
     return;
   }
features/conversation/conversation-context.tsx (1)

Line range hint 92-144: Refactor message sending logic for better maintainability

The message sending logic is complex and could be simplified for better maintainability.

+type MessagePayload = {
+  text?: string;
+  remoteAttachment?: RemoteAttachmentContent;
+  reference?: MessageId;
+};
+
+const createMessagePayload = ({ content, referencedMessageId }: ISendMessageParams): MessagePayload[] => {
+  const payloads: MessagePayload[] = [];
+  
+  if (referencedMessageId) {
+    if (content.remoteAttachment) {
+      payloads.push({
+        remoteAttachment: content.remoteAttachment,
+        reference: referencedMessageId
+      });
+    }
+    if (content.text) {
+      payloads.push({
+        text: content.text,
+        reference: referencedMessageId
+      });
+    }
+  } else {
+    if (content.remoteAttachment) {
+      payloads.push({ remoteAttachment: content.remoteAttachment });
+    }
+    if (content.text) {
+      payloads.push({ text: content.text });
+    }
+  }
+  
+  return payloads;
+};
+
 const sendMessage = useCallback(
   async ({ referencedMessageId, content }: ISendMessageParams) => {
-    // ... existing implementation ...
+    const payloads = createMessagePayload({ content, referencedMessageId });
+    
+    for (const payload of payloads) {
+      await sendCallback(payload);
+    }
   },
   [conversation, currentAccount, peerAddress]
 );
components/Conversation/V3Conversation.tsx (4)

161-171: Simplify consent popup conditional rendering

The conditional logic for rendering the DmConsentPopup can be simplified for better readability.

 ListHeaderComponent={
-  !isAllowedConversation &&
-  peerInboxId &&
-  !isLoadingConversationConsent ? (
+  (!isAllowedConversation && peerInboxId && !isLoadingConversationConsent) ? (
     <DmConsentPopup
       peerInboxId={peerInboxId}
       topic={topic}
       conversationId={conversationId}
     />
   ) : undefined
 }

210-214: Simplify group consent popup conditional rendering

The conditional logic for rendering the GroupConsentPopup can be simplified for better readability.

 ListHeaderComponent={
-  !isAllowedConversation && !isLoadingConversationConsent ? (
+  (!isAllowedConversation && !isLoadingConversationConsent) ? (
     <GroupConsentPopup topic={topic} />
   ) : undefined
 }

233-241: Consider memoizing the renderItem function

To optimize performance, consider memoizing the renderItem function using useCallback to prevent unnecessary re-renders.

+ const renderMessage = useCallback(({ item, index }: { item: string, index: number }) => (
+   <V3Message
+     nextMessageId={messageIds[index - 1]}
+     previousMessageId={messageIds[index + 1]}
+     messageId={item}
+   />
+ ), [messageIds]);

 <Animated.FlatList
   inverted
   data={messageIds}
-  renderItem={({ item, index }) => {
-    return (
-      <V3Message
-        nextMessageId={messageIds[index - 1]}
-        previousMessageId={messageIds[index + 1]}
-        messageId={item}
-      />
-    );
-  }}
+  renderItem={renderMessage}

336-338: Add type definition for message content

Consider adding a type definition for the message content structure to improve type safety.

+ type MessageContent = {
+   text: string;
+ };

 sendMessage({
-  content: {
+  content: {
     text: "👋",
   },
 });
features/conversation/dm-conversation.screen.tsx (8)

1-6: Enhance file-level documentation

The current documentation is minimal and marked as "work in progress". Consider adding:

  • Purpose and responsibilities of each component
  • Key dependencies and their roles
  • Usage examples or integration points

61-73: Add error handling to loading state

The loading state should handle potential errors and provide feedback to users.

 if (isLoading) {
   return (
     <Screen contentContainerStyle={{ flex: 1 }}>
       <Center
         style={{
           flex: 1,
         }}
       >
         <Loader />
+        {error && <Text>Failed to load conversation. Please try again.</Text>}
       </Center>
     </Screen>
   );
 }

77-77: Simplify boolean conversion

Replace double negation with Boolean constructor or comparison.

-      {!!conversation ? (
+      {conversation ? (
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


199-201: Enhance error message specificity

The current error message is generic. Consider providing more specific error messages based on the error type.

-        message: "Failed to send message",
+        message: error instanceof Error 
+          ? `Failed to send message: ${error.message}`
+          : "Failed to send message: Unknown error",

218-225: Implement empty state UI

The TODO comment indicates that an empty state UI is needed. This is important for better user experience.

Would you like me to help implement an empty state component that shows a welcome message or instructions for starting a new conversation?


254-257: Implement empty state UI for existing conversations

The TODO comment indicates that an empty state UI is needed. This is important for showing appropriate feedback when there are no messages.

Would you like me to help implement an empty state component that shows appropriate messaging for conversations without messages?


250-250: Handle potential undefined topic more safely

The non-null assertion on conversation.topic could be dangerous if the topic is undefined.

- } = useConversationMessages(currentAccount, conversation.topic!);
+ } = useConversationMessages(
+   currentAccount,
+   conversation.topic ?? throw new Error("Conversation topic is required")
+ );

281-281: Handle potential undefined topic more safely in header

The non-null assertion on topic could be dangerous if the topic is undefined.

-      headerTitle: () => <DmConversationTitle topic={topic!} />,
+      headerTitle: () => topic 
+        ? <DmConversationTitle topic={topic} />
+        : <Text>Loading...</Text>,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8daf61a and 71fc31d.

⛔ Files ignored due to path filters (1)
  • queries/__snapshots__/QueryKeys.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (80)
  • components/ActivityIndicator/ActivityIndicator.tsx (1 hunks)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx (0 hunks)
  • components/Chat/Attachment/attachment-container.tsx (1 hunks)
  • components/Chat/Attachment/attachment-loading.tsx (1 hunks)
  • components/Chat/Attachment/remote-attachment-image.tsx (1 hunks)
  • components/Chat/ChatGroupUpdatedMessage.tsx (2 hunks)
  • components/Chat/ConsentPopup/ConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx (1 hunks)
  • components/Chat/ConsentPopup/dm-consent-popup.tsx (1 hunks)
  • components/Chat/ConsentPopup/group-consent-popup.tsx (1 hunks)
  • components/Chat/Message/MessageTail.tsx (0 hunks)
  • components/Chat/Message/MessageTimestamp.tsx (0 hunks)
  • components/Chat/Message/V3Message.tsx (3 hunks)
  • components/Chat/Message/components/message-container.tsx (1 hunks)
  • components/Chat/Message/components/message-content-container.tsx (1 hunks)
  • components/Chat/Message/components/message-layout.tsx (1 hunks)
  • components/Chat/Message/components/message-repliable.tsx (1 hunks)
  • components/Chat/Message/components/message-space-between-messages.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-reply.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-simple-text.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-static-attachment.tsx (1 hunks)
  • components/Chat/Message/message-date-change.tsx (1 hunks)
  • components/Chat/Message/message-timestamp.tsx (1 hunks)
  • components/Chat/Message/message-utils.tsx (1 hunks)
  • components/Chat/Message/stores/message-store.tsx (0 hunks)
  • components/Conversation/V3Conversation.tsx (7 hunks)
  • components/GroupAvatar.tsx (2 hunks)
  • containers/GroupScreenMembersTable.tsx (2 hunks)
  • custom-eslint-plugin/padding-before-react-hooks.js (1 hunks)
  • design-system/loader.tsx (1 hunks)
  • features/conversation-list/hooks/useMessageText.ts (1 hunks)
  • features/conversation-list/useV3ConversationItems.ts (1 hunks)
  • features/conversation/composer/add-attachment-button.tsx (2 hunks)
  • features/conversation/composer/composer.tsx (9 hunks)
  • features/conversation/composer/send-attachment-preview.tsx (2 hunks)
  • features/conversation/conversation-context.tsx (5 hunks)
  • features/conversation/conversation-persisted-stores.ts (2 hunks)
  • features/conversation/conversation-service.ts (4 hunks)
  • features/conversation/dm-conversation.screen.tsx (1 hunks)
  • features/conversations/components/V3ConversationFromPeer.tsx (1 hunks)
  • features/conversations/utils/messageIsFromCurrentUser.ts (1 hunks)
  • features/search/components/NavigationChatButton.tsx (2 hunks)
  • hooks/useGroupConsent.ts (2 hunks)
  • hooks/useGroupCreator.ts (1 hunks)
  • hooks/useGroupMembers.ts (1 hunks)
  • navigation/useNavigation.tsx (1 hunks)
  • queries/MutationKeys.ts (0 hunks)
  • queries/QueryKeys.ts (2 hunks)
  • queries/queryClient.ts (1 hunks)
  • queries/useAddToGroupMutation.ts (1 hunks)
  • queries/useAllowGroupMutation.ts (0 hunks)
  • queries/useBlockGroupMutation.ts (1 hunks)
  • queries/useConversationQuery.ts (2 hunks)
  • queries/useConversationWithPeerQuery.ts (1 hunks)
  • queries/useDmConstentStateQuery.ts (1 hunks)
  • queries/useDmPeerInbox.ts (1 hunks)
  • queries/useGroupConsentQuery.ts (2 hunks)
  • queries/useGroupDescriptionMutation.ts (1 hunks)
  • queries/useGroupMembersQuery.ts (1 hunks)
  • queries/useGroupNameQuery.ts (1 hunks)
  • queries/useGroupPermissionPolicyQuery.ts (1 hunks)
  • queries/useGroupPinnedFrameQuery.ts (1 hunks)
  • queries/useGroupQuery.ts (1 hunks)
  • queries/usePromoteToAdminMutation.ts (1 hunks)
  • queries/usePromoteToSuperAdminMutation.ts (1 hunks)
  • queries/useRemoveFromGroupMutation.ts (1 hunks)
  • queries/useRevokeAdminMutation.ts (1 hunks)
  • queries/useRevokeSuperAdminMutation.ts (1 hunks)
  • queries/useV3ConversationListQuery.ts (1 hunks)
  • screens/ConversationReadOnly.tsx (1 hunks)
  • screens/Navigation/Navigation.tsx (4 hunks)
  • screens/NewConversation/NewConversation.tsx (1 hunks)
  • screens/Profile.tsx (1 hunks)
  • utils/attachment/handleStaticAttachment.ts (0 hunks)
  • utils/capture-error.ts (1 hunks)
  • utils/groupUtils/groupActionHandlers.ts (2 hunks)
  • utils/xmtpRN/contacts.ts (2 hunks)
  • utils/xmtpRN/conversations.ts (3 hunks)
💤 Files with no reviewable changes (9)
  • components/Chat/Message/MessageTimestamp.tsx
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx
  • components/Chat/ConsentPopup/ConsentPopup.tsx
  • components/Chat/Message/MessageTail.tsx
  • queries/MutationKeys.ts
  • components/Chat/Message/stores/message-store.tsx
  • queries/useAllowGroupMutation.ts
  • components/Chat/Attachment/AttachmentMessagePreview.tsx
  • utils/attachment/handleStaticAttachment.ts
🚧 Files skipped from review as they are similar to previous changes (56)
  • hooks/useGroupMembers.ts
  • components/ActivityIndicator/ActivityIndicator.tsx
  • queries/useGroupNameQuery.ts
  • queries/queryClient.ts
  • components/Chat/Attachment/attachment-loading.tsx
  • components/Chat/Message/message-utils.tsx
  • screens/ConversationReadOnly.tsx
  • navigation/useNavigation.tsx
  • components/Chat/Message/components/message-space-between-messages.tsx
  • utils/capture-error.ts
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx
  • features/conversations/components/V3ConversationFromPeer.tsx
  • custom-eslint-plugin/padding-before-react-hooks.js
  • components/Chat/Attachment/attachment-container.tsx
  • components/Chat/Message/components/message-repliable.tsx
  • components/Chat/Message/components/message-container.tsx
  • features/search/components/NavigationChatButton.tsx
  • features/conversation/composer/send-attachment-preview.tsx
  • queries/QueryKeys.ts
  • components/Chat/Message/components/message-content-container.tsx
  • components/Chat/Message/message-date-change.tsx
  • features/conversation-list/hooks/useMessageText.ts
  • design-system/loader.tsx
  • components/Chat/Message/message-timestamp.tsx
  • hooks/useGroupCreator.ts
  • queries/useGroupQuery.ts
  • features/conversation/conversation-service.ts
  • features/conversations/utils/messageIsFromCurrentUser.ts
  • components/GroupAvatar.tsx
  • components/Chat/Message/message-content-types/message-simple-text.tsx
  • queries/useGroupPinnedFrameQuery.ts
  • utils/groupUtils/groupActionHandlers.ts
  • queries/useGroupDescriptionMutation.ts
  • queries/useRevokeSuperAdminMutation.ts
  • queries/useBlockGroupMutation.ts
  • utils/xmtpRN/contacts.ts
  • features/conversation/composer/add-attachment-button.tsx
  • queries/useGroupPermissionPolicyQuery.ts
  • components/Chat/Message/components/message-layout.tsx
  • features/conversation/conversation-persisted-stores.ts
  • queries/useRevokeAdminMutation.ts
  • queries/useAddToGroupMutation.ts
  • components/Chat/ConsentPopup/group-consent-popup.tsx
  • utils/xmtpRN/conversations.ts
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx
  • queries/useV3ConversationListQuery.ts
  • queries/usePromoteToAdminMutation.ts
  • screens/NewConversation/NewConversation.tsx
  • queries/useRemoveFromGroupMutation.ts
  • queries/usePromoteToSuperAdminMutation.ts
  • components/Chat/Message/message-content-types/message-static-attachment.tsx
  • queries/useDmPeerInbox.ts
  • queries/useGroupMembersQuery.ts
  • queries/useDmConstentStateQuery.ts
  • screens/Profile.tsx
  • components/Chat/ChatGroupUpdatedMessage.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
features/conversation/dm-conversation.screen.tsx

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🔇 Additional comments (33)
containers/GroupScreenMembersTable.tsx (3)

18-18: LGTM: Clean import organization

The new imports are well-organized and properly typed, supporting the component's enhanced functionality.

Also applies to: 20-22


Line range hint 51-186: LGTM: Well-structured implementation

The implementation demonstrates good practices:

  • Comprehensive error handling with proper logging
  • Efficient use of memoization
  • Clean separation of concerns

33-33: ⚠️ Potential issue

Improve type safety in topic handling

The current implementation uses non-null assertions with fallback logic (topic ?? group?.topic)! which could be unsafe if both values are undefined.

Consider implementing the previously suggested improvement:

+ const topicValue = useMemo(() => topic ?? group?.topic, [topic, group?.topic]);
+ 
+ if (!topicValue) {
+   logger.warn('No valid topic found for group members table');
+   return null;
+ }
+ 
- const { members, ... } = useGroupMembers((topic ?? group?.topic)!);
+ const { members, ... } = useGroupMembers(topicValue);
  
  const { data: groupPermissionPolicy } = useGroupPermissionPolicyQuery(
    currentAccount,
-   (topic ?? group?.topic)!
+   topicValue
  );

Also applies to: 45-45, 48-48

queries/useConversationWithPeerQuery.ts (4)

1-6: LGTM! Imports are well-organized

The imports are clean, necessary, and properly typed.


7-13: LGTM! Hook signature is well-typed

The parameters are properly typed and the options parameter allows for flexible query customization.


16-16: ⚠️ Potential issue

Remove unsafe non-null assertion on peer

The non-null assertion (!) on peer in the queryKey is unsafe as peer is explicitly typed as string | undefined. While the query is only enabled when peer exists, it's better to maintain type safety.

-queryKey: conversationWithPeerQueryKey(account, peer!),
+queryKey: conversationWithPeerQueryKey(account, peer),

22-29: ⚠️ Potential issue

Add error handling for conversation fetching

The conversation fetching lacks explicit error handling. Consider adding try-catch to handle potential network or API errors gracefully.

-      const conversation = await getConversationByPeerByAccount({
-        account,
-        peer,
-        includeSync: true,
-      });
-
-      return conversation;
+      try {
+        const conversation = await getConversationByPeerByAccount({
+          account,
+          peer,
+          includeSync: true,
+        });
+        return conversation;
+      } catch (error) {
+        logger.error(`Failed to fetch conversation with peer ${peer}:`, error);
+        throw error;
+      }
features/conversation-list/useV3ConversationItems.ts (3)

9-16: Add null check for currentAccount

The unsafe use of the non-null assertion operator ! with currentAccount persists. This could lead to runtime errors if currentAccount is null.


23-27: LGTM! Efficient implementation of topic sets

The use of Set for both pinned and deleted topics provides O(1) lookup performance. The transformation of topic data using Object.entries().filter().map() is clean and efficient.


39-39: LGTM! Clean return statement

The return statement correctly spreads the query properties while replacing the data with the filtered results.

screens/Navigation/Navigation.tsx (2)

81-87: Consider using a discriminated union for better type safety

The current union type allows for potential runtime confusion as both peerAddress and topic could technically be provided together.


153-156: Screen options should be added for consistent navigation behavior

Based on the analysis of other conversation-related screens in the codebase, the DmConversation screen should include navigation options for consistency.

components/Chat/Attachment/remote-attachment-image.tsx (6)

1-26: LGTM! Well-structured imports and type definitions.

The imports are organized logically, and the props interface is properly typed with clear documentation.


78-80: Implement the missing onPress handler for unsupported media types.

The empty onPress handler prevents users from opening unsupported attachments in a webview.


95-98: Add safety check for aspect ratio calculation.

The aspect ratio calculation could lead to a division by zero error if height is 0.


116-140: LGTM! Well-implemented container component.

The component is properly memoized and follows best practices for styling and theme integration.


152-155: Add validation for contentLength parsing.

The current implementation doesn't handle invalid contentLength values that could result in NaN.


157-157: Handle potential null value from getCurrentAccount().

Using the non-null assertion operator (!) could lead to runtime errors if no account is available.

components/Chat/Message/message-content-types/message-reply.tsx (2)

44-48: Replace console.error with proper error tracking.

Use the existing sentryTrackError utility for consistent error tracking across the application.


216-227: Handle potential undefined text content.

The function should handle cases where content.content.text is undefined.

features/conversation/composer/composer.tsx (2)

363-371: ⚠️ Potential issue

Add null check for content before rendering RemoteAttachmentImage

Ensure content exists before attempting to render RemoteAttachmentImage to prevent potential runtime errors.

+      if (!content?.content?.remoteAttachment) {
+        return null;
+      }

       return (
         <RemoteAttachmentImage
           messageId={content.reference}
           remoteMessageContent={content.content.remoteAttachment}
           containerProps={{
             style: {
               height: theme.avatarSize.md,
               width: theme.avatarSize.md,
               borderRadius: theme.borderRadius.xs,
             },
           }}
         />
       );

Likely invalid or redundant comment.


99-102: ⚠️ Potential issue

Remove non-null assertion operator

Using ! operator on getUploadedRemoteAttachment() could lead to runtime errors. Add proper null checking.

-      const uploadedRemoteAttachment = getUploadedRemoteAttachment()!;
+      const uploadedRemoteAttachment = getUploadedRemoteAttachment();
+      if (!uploadedRemoteAttachment) {
+        console.error('Failed to get uploaded remote attachment');
+        return;
+      }

       await onSend({
         content: {
           remoteAttachment: uploadedRemoteAttachment,
         },

Likely invalid or redundant comment.

queries/useConversationQuery.ts (1)

24-25: ⚠️ Potential issue

Remove unsafe non-null assertions

The non-null assertions on topic remain unsafe and should be handled properly.

-    queryKey: conversationQueryKey(account, topic!),
-    queryFn: () => getConversation(account, topic!),
+    queryKey: conversationQueryKey(account, topic),
+    queryFn: () => {
+      if (!topic) throw new Error("Topic is required");
+      return getConversation(account, topic);
+    },
components/Chat/ConsentPopup/dm-consent-popup.tsx (2)

102-104: 🛠️ Refactor suggestion

Improve error handling in handleAccept

Similar to the block action, the accept error message should be more specific.

-      captureErrorWithToast(ensureError(error), {
-        message: `Error consenting`,
-      });
+      captureErrorWithToast(ensureError(error), {
+        message: translate("error_accepting_contact", {
+          details: ensureError(error).message
+        }),
+      });

Likely invalid or redundant comment.


85-89: 🛠️ Refactor suggestion

Improve error message specificity

The generic error message should be more descriptive.

-            showSnackbar({
-              type: "error",
-              message: `Error consenting`,
-            });
+            showSnackbar({
+              type: "error",
+              message: translate("error_blocking_contact", {
+                details: ensureError(error).message
+              }),
+            });

Likely invalid or redundant comment.

components/Chat/Message/V3Message.tsx (1)

101-111: ⚠️ Potential issue

Multiple message components may render simultaneously

The current implementation allows multiple message type conditions to be true simultaneously, potentially causing multiple components to render for a single message.

Apply this diff to ensure mutually exclusive rendering:

-            {isGroupUpdatedMessage(message) && (
-              <ChatGroupUpdatedMessage message={message} />
-            )}
-            {isReplyMessage(message) && <MessageReply message={message} />}
-            {isTextMessage(message) && <MessageSimpleText message={message} />}
-            {isRemoteAttachmentMessage(message) && (
-              <MessageRemoteAttachment message={message} />
-            )}
-            {isStaticAttachmentMessage(message) && (
-              <MessageStaticAttachment message={message} />
-            )}
+            {isGroupUpdatedMessage(message) ? (
+              <ChatGroupUpdatedMessage message={message} />
+            ) : isReplyMessage(message) ? (
+              <MessageReply message={message} />
+            ) : isTextMessage(message) ? (
+              <MessageSimpleText message={message} />
+            ) : isRemoteAttachmentMessage(message) ? (
+              <MessageRemoteAttachment message={message} />
+            ) : isStaticAttachmentMessage(message) ? (
+              <MessageStaticAttachment message={message} />
+            ) : null}
hooks/useGroupConsent.ts (1)

68-83: ⚠️ Potential issue

Add cleanup handling for concurrent operations

The Promise.all operation should handle cleanup for partial failures to ensure system consistency.

Apply this diff to add proper cleanup handling:

+const results: Array<{ success: boolean; operation: string }> = [];
 await Promise.all([
   consentToGroupsOnProtocolByAccount({
     account,
     groupIds: [getV3IdFromTopic(topic)],
     consent: "allow",
-  }),
+  }).then(() => results.push({ success: true, operation: 'group' }))
+    .catch(() => results.push({ success: false, operation: 'group' })),
   ...(inboxIdsToAllow.length > 0
     ? [
         consentToInboxIdsOnProtocolByAccount({
           account,
           inboxIds: inboxIdsToAllow,
           consent: "allow",
-        }),
+        }).then(() => results.push({ success: true, operation: 'inbox' }))
+          .catch(() => results.push({ success: false, operation: 'inbox' })),
       ]
     : []),
 ]);
+
+if (results.some(r => !r.success)) {
+  throw new Error(`Failed operations: ${results.filter(r => !r.success).map(r => r.operation).join(', ')}`);
+}
features/conversation/conversation-context.tsx (2)

1-1: ⚠️ Potential issue

Fix typo in import path for useDmConsentQuery

There is a typo in the import path that could cause import errors.

-import { useDmConsentQuery } from "@/queries/useDmConstentStateQuery";
+import { useDmConsentQuery } from "@/queries/useDmConsentStateQuery";

174-174: ⚠️ Potential issue

Remove TODO comment and implement conversation blocking

The code contains a TODO comment about implementing conversation blocking, but the feature is already being used.

-        isBlockedConversation: conversation?.state === "denied", // TODO: implement this
+        isBlockedConversation: conversation?.state === "denied",
components/Conversation/V3Conversation.tsx (3)

5-27: LGTM: Import statements are well-organized

The new imports are properly structured and necessary for the consent popup and composer functionality.


153-155: Remove debug console.log statements

These console.log statements should be removed from production code.


102-113: ⚠️ Potential issue

Await the asynchronous sendMessage function

The sendMessage call in handleSend is not awaited, which could lead to unhandled promise rejections or race conditions.

 const handleSend = useCallback(
   async (args: IComposerSendArgs) => {
-    sendMessage(args);
+    await sendMessage(args);
   },
   [sendMessage]
 );
features/conversation/dm-conversation.screen.tsx (1)

128-182: 🛠️ Refactor suggestion

Simplify message sending logic

The current implementation has complex branching that could be simplified by extracting message sending logic into separate functions.

+ const sendReplyMessage = async (
+   conversation: ConversationWithCodecsType,
+   referencedMessageId: MessageId,
+   text?: string,
+   remoteAttachment?: RemoteAttachmentContent
+ ) => {
+   const content = {};
+   if (text) content.text = text;
+   if (remoteAttachment) content.remoteAttachment = remoteAttachment;
+   await conversation.send({
+     reply: { reference: referencedMessageId, content },
+   });
+ };
+
+ const sendRegularMessage = async (
+   conversation: ConversationWithCodecsType,
+   text?: string,
+   remoteAttachment?: RemoteAttachmentContent
+ ) => {
+   if (remoteAttachment) {
+     await conversation.send({ remoteAttachment });
+   }
+   if (text) {
+     await conversation.send(text);
+   }
+ };

Likely invalid or redundant comment.


const fromMe = useMessageContextStoreContext((s) => s.fromMe);

const currentAccount = useCurrentAccount()!;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential undefined current account.

The non-null assertion on useCurrentAccount() could lead to runtime errors. Consider adding proper error handling.

- const currentAccount = useCurrentAccount()!;
+ const currentAccount = useCurrentAccount();
+ if (!currentAccount) {
+   return null; // or show an appropriate error state
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const currentAccount = useCurrentAccount()!;
const currentAccount = useCurrentAccount();
if (!currentAccount) {
return null; // or show an appropriate error state
}

function useConversationMessageForReplyMessage(
messageId: MessageId
): DecodedMessage<[ReplyCodec]> | undefined {
const currentAccount = useCurrentAccount()!;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential undefined current account in hook.

The non-null assertion on useCurrentAccount() could lead to runtime errors. Consider adding proper error handling.

- const currentAccount = useCurrentAccount()!;
+ const currentAccount = useCurrentAccount();
+ if (!currentAccount) {
+   return undefined;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const currentAccount = useCurrentAccount()!;
const currentAccount = useCurrentAccount();
if (!currentAccount) {
return undefined;
}

Comment on lines +48 to +61
mutationFn: async (args: { consent: "allow" | "deny" }) => {
const currentAccount = getCurrentAccount()!;
await Promise.all([
consentToGroupsOnProtocolByAccount({
account: currentAccount,
groupIds: [conversationId],
consent: args.consent,
}),
consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [peerInboxId],
consent: args.consent,
}),
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential race conditions in mutation

The concurrent Promise.all calls could lead to inconsistent states if one fails.

mutationFn: async (args: { consent: "allow" | "deny" }) => {
-      const currentAccount = getCurrentAccount()!;
+      const currentAccount = getCurrentAccount();
+      if (!currentAccount) {
+        throw new Error("No active account");
+      }
+      try {
       await Promise.all([
         consentToGroupsOnProtocolByAccount({
           account: currentAccount,
           groupIds: [conversationId],
           consent: args.consent,
         }),
         consentToInboxIdsOnProtocolByAccount({
           account: currentAccount,
           inboxIds: [peerInboxId],
           consent: args.consent,
         }),
       ]);
+      } catch (error) {
+        // Ensure both operations are rolled back if either fails
+        await Promise.all([
+          consentToGroupsOnProtocolByAccount({
+            account: currentAccount,
+            groupIds: [conversationId],
+            consent: "deny",
+          }),
+          consentToInboxIdsOnProtocolByAccount({
+            account: currentAccount,
+            inboxIds: [peerInboxId],
+            consent: "deny",
+          }),
+        ]).catch(console.error);
+        throw error;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mutationFn: async (args: { consent: "allow" | "deny" }) => {
const currentAccount = getCurrentAccount()!;
await Promise.all([
consentToGroupsOnProtocolByAccount({
account: currentAccount,
groupIds: [conversationId],
consent: args.consent,
}),
consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [peerInboxId],
consent: args.consent,
}),
]);
mutationFn: async (args: { consent: "allow" | "deny" }) => {
const currentAccount = getCurrentAccount();
if (!currentAccount) {
throw new Error("No active account");
}
try {
await Promise.all([
consentToGroupsOnProtocolByAccount({
account: currentAccount,
groupIds: [conversationId],
consent: args.consent,
}),
consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [peerInboxId],
consent: args.consent,
}),
]);
} catch (error) {
// Ensure both operations are rolled back if either fails
await Promise.all([
consentToGroupsOnProtocolByAccount({
account: currentAccount,
groupIds: [conversationId],
consent: "deny",
}),
consentToInboxIdsOnProtocolByAccount({
account: currentAccount,
inboxIds: [peerInboxId],
consent: "deny",
}),
]).catch(console.error);
throw error;
}

Comment on lines +78 to +80
if (!message) {
console.log("no message found", messageId);
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling for missing messages

The current error handling only logs to console. Consider adding error tracking and providing a fallback UI for better user experience.

 if (!message) {
-  console.log("no message found", messageId);
+  logger.error("Message not found", { messageId });
+  return (
+    <VStack>
+      <Text>Message unavailable</Text>
+    </VStack>
+  );
   return null;
 }

Committable suggestion skipped: line range outside the PR's diff.

Copy link
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

🧹 Outside diff range and nitpick comments (1)
hooks/useGroupConsent.ts (1)

Line range hint 98-106: Add error handling for concurrent operations in blockGroup

The concurrent operations in blockGroup should handle potential failures and rollback changes if needed.

 if (inboxIdsToDeny.length > 0) {
-  consentToInboxIdsOnProtocolByAccount({
-    account,
-    inboxIds: inboxIdsToDeny,
-    consent: "deny",
-  });
+  try {
+    await consentToInboxIdsOnProtocolByAccount({
+      account,
+      inboxIds: inboxIdsToDeny,
+      consent: "deny",
+    });
+  } catch (error) {
+    captureError(error);
+    throw new Error("Failed to deny inbox consents");
+  }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 71fc31d and b84d2c3.

📒 Files selected for processing (6)
  • features/GroupInvites/joinGroup/JoinGroup.client.ts (4 hunks)
  • hooks/useGroupConsent.ts (2 hunks)
  • queries/groupConsentMutationUtils.ts (4 hunks)
  • queries/useAllowGroupMutation.ts (2 hunks)
  • queries/useBlockGroupMutation.ts (2 hunks)
  • screens/Navigation/Navigation.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • queries/useBlockGroupMutation.ts
🔇 Additional comments (8)
screens/Navigation/Navigation.tsx (3)

1-1: LGTM!

The import follows the project's conventions and is appropriately placed.


81-87: Consider using a discriminated union for better type safety

The current union type allows for potential runtime confusion as both peerAddress and topic could technically be provided together. Consider using a discriminated union to make the parameters mutually exclusive.

  DmConversation:
-   | {
-       peerAddress: string;
-     }
-   | {
-       topic: string;
-     };
+   | {
+       type: 'peer';
+       peerAddress: string;
+     }
+   | {
+       type: 'topic';
+       topic: string;
+     };

156-159: Screen options should be added for consistent navigation behavior

Based on the analysis of other conversation-related screens in the codebase, the DmConversation screen should include navigation options for consistency. Specifically:

  • Add headerTitle for the screen title
  • Add headerTitleStyle: headerTitleStyle(colorScheme) for consistent styling
  • Add animation: navigationAnimation for consistent transitions
  • Consider adding headerTintColor configuration similar to other conversation screens

Example screens like GroupNav.tsx and ConversationNav.tsx demonstrate these common patterns for conversation-type screens in the app.

queries/groupConsentMutationUtils.ts (2)

11-15: LGTM! Improved type safety with SDK types

Good improvement using the SDK's ConsentState type instead of a custom Consent type.


36-40: Add error handling for concurrent operations

The mutation functions should handle cleanup for partial failures in Promise.all operations.

Also applies to: 84-88

features/GroupInvites/joinGroup/JoinGroup.client.ts (3)

1-1: LGTM! Type safety improvements

The changes enhance type safety through:

  • Using the more descriptive interface name IGroupConsentOptions
  • Adding proper typing with GroupWithCodecsType

Also applies to: 26-26, 53-53


220-223: LGTM! Improved mutation call structure

The mutation call now explicitly includes the required parameters (group and account), making the code more maintainable and self-documenting.


220-223: Verify type compatibility for GroupWithCodecsType cast

The type casting conversation as GroupWithCodecsType might indicate a potential type mismatch. Let's verify that all callers provide compatible conversation objects.

✅ Verification successful

Based on the gathered information, I can now provide the final response:

Type casting to GroupWithCodecsType appears safe

The type casting is appropriate here because:

  • The GroupWithCodecsType is properly imported from "@/utils/xmtpRN/client"
  • The conversation object is used correctly with its expected properties (topic, id) before the cast
  • The type is a valid type extending the base Group type with codec support
  • The mutation observer's implementation confirms it expects a group parameter
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of liveAllowGroup to ensure type compatibility

# Search for calls to liveAllowGroup
echo "Searching for liveAllowGroup calls:"
rg -A 5 "liveAllowGroup.*conversation.*"

# Search for GroupWithCodecsType usage
echo -e "\nChecking GroupWithCodecsType usage:"
ast-grep --pattern 'conversation: $type' | rg 'GroupWithCodecsType|ConversationWithCodecsType'

Length of output: 307


Script:

#!/bin/bash
# Let's try a different approach to verify type compatibility

# Check the type definition and usage
echo "Searching for GroupWithCodecsType definition:"
rg "type.*GroupWithCodecsType"

# Check the conversation object type in the component
echo -e "\nChecking conversation type in JoinGroup component:"
rg -A 5 "interface.*JoinGroup" features/GroupInvites/joinGroup/

# Look for type declarations or interfaces related to the conversation parameter
echo -e "\nChecking conversation parameter types:"
ast-grep --pattern 'conversation: $_'

# Check imports and type usage in the file
echo -e "\nChecking imports and type usage in JoinGroup.client.ts:"
rg "import.*GroupWithCodecsType" features/GroupInvites/joinGroup/

Length of output: 1018


Script:

#!/bin/bash
# Let's examine the conversation object's type and usage in JoinGroup.client.ts

# Check the type of conversation in the file
echo "Checking conversation variable declaration and usage:"
rg -B 5 -A 5 "conversation.*GroupWithCodecsType" features/GroupInvites/joinGroup/JoinGroup.client.ts

# Look for the type definition of the conversation parameter
echo -e "\nChecking function parameters and type annotations:"
ast-grep --pattern 'function $name($params) { $$$}'  features/GroupInvites/joinGroup/JoinGroup.client.ts

# Check the allowGroupMutationObserver definition
echo -e "\nChecking allowGroupMutationObserver definition:"
rg -B 5 "allowGroupMutationObserver" features/GroupInvites/joinGroup/JoinGroup.client.ts

Length of output: 1245


Script:

#!/bin/bash
# Let's examine the conversation object's structure and the mutation observer's type expectations

# Check the createAllowGroupMutationObserver implementation
echo "Checking createAllowGroupMutationObserver implementation:"
rg -B 5 -A 10 "createAllowGroupMutationObserver" queries/useAllowGroupMutation.ts

# Check the conversation object's properties being used
echo -e "\nChecking conversation object usage:"
rg -B 3 "const.*{.*}.*=.*conversation" features/GroupInvites/joinGroup/JoinGroup.client.ts

# Look for the Group type definition
echo -e "\nChecking Group type definition:"
rg -A 5 "type.*Group.*=|interface.*Group" utils/xmtpRN/client.ts

Length of output: 1218


Script:

#!/bin/bash
# Let's fix the regex and check additional type information

# Check conversation object destructuring with fixed regex
echo "Checking conversation object usage:"
rg -B 3 "const.*\{.*\}.*conversation" features/GroupInvites/joinGroup/JoinGroup.client.ts

# Check the MutationObserver type expectations
echo -e "\nChecking MutationObserver type definition:"
rg -B 5 "class.*MutationObserver" queries/useAllowGroupMutation.ts

# Check the actual mutation method parameters
echo -e "\nChecking mutation method parameters:"
rg -B 5 "mutate.*\{.*group" queries/useAllowGroupMutation.ts

Length of output: 889

async (args: IGroupConsentOptions) => {
const { includeAddedBy, includeCreator } = args;

if (!group) {
showSnackbar({
type: "error",
message: translate("group_not_found"),
});
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent error handling between allowGroup and blockGroup

While allowGroup throws an error, blockGroup shows a snackbar. Consider standardizing the error handling approach across both functions.

 async (args: IGroupConsentOptions) => {
   const { includeAddedBy, includeCreator } = args;

   if (!group) {
-    showSnackbar({
-      type: "error",
-      message: translate("group_not_found"),
-    });
-    return;
+    throw new Error(translate("group_not_found"));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async (args: IGroupConsentOptions) => {
const { includeAddedBy, includeCreator } = args;
if (!group) {
showSnackbar({
type: "error",
message: translate("group_not_found"),
});
return;
}
async (args: IGroupConsentOptions) => {
const { includeAddedBy, includeCreator } = args;
if (!group) {
throw new Error(translate("group_not_found"));
}

Comment on lines +114 to +137
const onErrorAllowGroupMutation = (
error: unknown,
variables: AllowGroupMutationVariables,
context?: {
previousConsent: ConsentState | undefined;
previousConversation: ConversationQueryData | undefined;
}
) => {
const { account, group } = variables;

const topic = group.topic;
captureError(error);
if (!context) {
return;
}

if (context.previousConsent) {
setGroupConsentQueryData(account, topic, context.previousConsent);
}

if (context.previousConversation) {
setConversationQueryData(account, topic, context.previousConversation);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add cleanup for partial failures in error handler

The error handler should clean up any partially completed operations.

 const onErrorAllowGroupMutation = (
   error: unknown,
   variables: AllowGroupMutationVariables,
   context?: {
     previousConsent: ConsentState | undefined;
     previousConversation: ConversationQueryData | undefined;
   }
 ) => {
   const { account, group } = variables;
   const topic = group.topic;
   captureError(error);
+
+  // Ensure all queries are properly cancelled before restoring state
+  queryClient.cancelQueries({ queryKey: ["groupConsent", account, topic] });
+  queryClient.cancelQueries({ queryKey: ["conversation", account, topic] });
+
   if (!context) {
     return;
   }

   if (context.previousConsent) {
     setGroupConsentQueryData(account, topic, context.previousConsent);
   }

   if (context.previousConversation) {
     setConversationQueryData(account, topic, context.previousConversation);
   }
+
+  // Invalidate queries to ensure fresh data
+  queryClient.invalidateQueries({ queryKey: ["groupConsent", account, topic] });
+  queryClient.invalidateQueries({ queryKey: ["conversation", account, topic] });
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +71 to +86
await Promise.all([
consentToGroupsOnProtocolByAccount({
account,
groupIds: [getV3IdFromTopic(groupTopic)],
consent: "allow",
}),
...(inboxIdsToAllow.length > 0
? [
consentToInboxIdsOnProtocolByAccount({
account,
inboxIds: inboxIdsToAllow,
consent: "allow",
}),
]
: []),
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error handling in Promise.all operations

The concurrent operations should track individual successes/failures and handle partial failures appropriately.

+  const results = await Promise.allSettled([
-  await Promise.all([
     consentToGroupsOnProtocolByAccount({
       account,
       groupIds: [getV3IdFromTopic(groupTopic)],
       consent: "allow",
     }),
     ...(inboxIdsToAllow.length > 0
       ? [
           consentToInboxIdsOnProtocolByAccount({
             account,
             inboxIds: inboxIdsToAllow,
             consent: "allow",
           }),
         ]
       : []),
   ]);
+
+  const failures = results.filter(
+    (result): result is PromiseRejectedResult =>
+      result.status === "rejected"
+  );
+
+  if (failures.length > 0) {
+    throw new Error(
+      `Failed operations: ${failures
+        .map((failure) => failure.reason)
+        .join(", ")}`
+    );
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await Promise.all([
consentToGroupsOnProtocolByAccount({
account,
groupIds: [getV3IdFromTopic(groupTopic)],
consent: "allow",
}),
...(inboxIdsToAllow.length > 0
? [
consentToInboxIdsOnProtocolByAccount({
account,
inboxIds: inboxIdsToAllow,
consent: "allow",
}),
]
: []),
]);
const results = await Promise.allSettled([
consentToGroupsOnProtocolByAccount({
account,
groupIds: [getV3IdFromTopic(groupTopic)],
consent: "allow",
}),
...(inboxIdsToAllow.length > 0
? [
consentToInboxIdsOnProtocolByAccount({
account,
inboxIds: inboxIdsToAllow,
consent: "allow",
}),
]
: []),
]);
const failures = results.filter(
(result): result is PromiseRejectedResult =>
result.status === "rejected"
);
if (failures.length > 0) {
throw new Error(
`Failed operations: ${failures
.map((failure) => failure.reason)
.join(", ")}`
);
}

Comment on lines +101 to +110
await cancelGroupConsentQuery(account, topic);
setGroupConsentQueryData(account, topic, "allowed");
const previousConsent = getGroupConsentQueryData(account, topic);

const previousConversation = getConversationQueryData(account, topic);

if (previousConversation) {
previousConversation.state = "allowed";
setConversationQueryData(account, topic, previousConversation);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make state updates atomic in onMutate

The state updates in onMutate should be atomic to prevent potential race conditions.

-  await cancelGroupConsentQuery(account, topic);
-  setGroupConsentQueryData(account, topic, "allowed");
-  const previousConsent = getGroupConsentQueryData(account, topic);
-
-  const previousConversation = getConversationQueryData(account, topic);
-
-  if (previousConversation) {
-    previousConversation.state = "allowed";
-    setConversationQueryData(account, topic, previousConversation);
-  }
+  const previousStates = await queryClient.executeMutation(() => {
+    const previousConsent = getGroupConsentQueryData(account, topic);
+    const previousConversation = getConversationQueryData(account, topic);
+    
+    cancelGroupConsentQuery(account, topic);
+    setGroupConsentQueryData(account, topic, "allowed");
+    
+    if (previousConversation) {
+      previousConversation.state = "allowed";
+      setConversationQueryData(account, topic, previousConversation);
+    }
+    
+    return { previousConsent, previousConversation };
+  });

Committable suggestion skipped: line range outside the PR's diff.

@alexrisch alexrisch force-pushed the ts/message-refactoring-attachments branch from b84d2c3 to b425fec Compare December 4, 2024 23:19
Copy link
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

🧹 Outside diff range and nitpick comments (12)
queries/useConversationWithPeerQuery.ts (2)

7-13: Consider adding explicit return type for better type safety

While the return type is correctly inferred from useQuery, adding an explicit return type would improve type safety and documentation.

 export const useConversationWithPeerQuery = (
   account: string,
   peer: string | undefined,
   options?: Partial<
     UseQueryOptions<ConversationWithCodecsType | null | undefined>
   >
-) => {
+): UseQueryResult<ConversationWithCodecsType | null | undefined> => {

18-18: Review debug logging message

The "[Crash Debug]" prefix suggests this is temporary debugging code. Consider either:

  1. Removing it if it was only for development
  2. Replacing it with a more appropriate production-ready message
-      logger.info("[Crash Debug] queryFn fetching conversation with peer");
+      logger.info(`Fetching conversation with peer: ${peer}`);
hooks/useGroupConsent.ts (1)

Line range hint 81-94: Improve error handling for concurrent operations

The Promise.all should handle cleanup for partial failures to maintain data consistency.

Consider using Promise.allSettled and implementing proper cleanup:

-      await Promise.all([
+      const results = await Promise.allSettled([
         consentToGroupsOnProtocolByAccount({
           account,
           groupIds: [getV3IdFromTopic(topic)],
           consent: "allow",
         }),
         ...(inboxIdsToAllow.length > 0
           ? [consentToInboxIdsOnProtocolByAccount({
               account,
               inboxIds: inboxIdsToAllow,
               consent: "allow",
             })]
           : []),
       ]);
+      
+      const failures = results.filter(
+        (result): result is PromiseRejectedResult => result.status === "rejected"
+      );
+      
+      if (failures.length > 0) {
+        throw new Error(
+          `Failed operations: ${failures.map(f => f.reason).join(", ")}`
+        );
+      }
components/Chat/Message/V3Message.tsx (1)

119-138: Remove commented out code

The commented out useConversationMessageById function should be removed if it's no longer needed. If it's intended for future use, consider tracking it in a separate issue.

features/conversation/conversation-context.tsx (1)

Line range hint 92-144: Simplify message sending logic

The current implementation has duplicate code for handling attachments and text content. Consider refactoring to reduce duplication.

Apply this diff to simplify the logic:

 async ({ referencedMessageId, content }: ISendMessageParams) => {
   const sendCallback = async (payload: any) => {
     if (!conversation && !peerAddress) {
       return;
     }

     if (!conversation && peerAddress) {
       const newConversation = await createConversationByAccount(
         currentAccount,
         peerAddress
       );
       updateNewConversation(newConversation.topic);
       await newConversation.send(payload);
       addConversationToConversationListQuery(
         currentAccount,
         newConversation
       );
       return;
     }

     await conversation?.send(payload);
   };

+  const createPayload = (content: ISendMessageParams['content'], reference?: MessageId) => {
+    if (reference) {
+      return {
+        reply: {
+          reference,
+          content: content.remoteAttachment
+            ? { remoteAttachment: content.remoteAttachment }
+            : { text: content.text }
+        }
+      };
+    }
+    return content.remoteAttachment
+      ? { remoteAttachment: content.remoteAttachment }
+      : content.text;
+  };
+
+  await sendCallback(createPayload(content, referencedMessageId));
-  if (referencedMessageId) {
-    if (content.remoteAttachment) {
-      await sendCallback({
-        reply: {
-          reference: referencedMessageId,
-          content: { remoteAttachment: content.remoteAttachment },
-        },
-      });
-    }
-    if (content.text) {
-      await sendCallback({
-        reply: {
-          reference: referencedMessageId,
-          content: { text: content.text },
-        },
-      });
-    }
-    return;
-  }
-
-  if (content.remoteAttachment) {
-    await sendCallback({
-      remoteAttachment: content.remoteAttachment,
-    });
-  }
-
-  if (content.text) {
-    await sendCallback(content.text);
-  }
components/Chat/Message/message-content-types/message-reply.tsx (2)

82-87: Simplify border radius calculations

Consider moving the complex border radius calculations to theme constants for better maintainability.

+// In theme file
+const messageTheme = {
+  borderRadius: {
+    nestedAttachment: theme.borderRadius.message.attachment - 
+      theme.spacing.message.replyMessage.horizontalPadding / 2,
+  }
+}

-borderRadius:
-  theme.borderRadius.message.attachment -
-  theme.spacing.message.replyMessage.horizontalPadding /
-    2,
+borderRadius: theme.messageTheme.borderRadius.nestedAttachment,

205-207: Improve error tracking message

The error message could be more descriptive to aid debugging.

 sentryTrackError(
-  `Reply message reference message content is not handled with default text ${replyMessageSafeText}`
+  new Error(
+    `Unhandled reply message content type. Message ID: ${replyMessage.id}, ` +
+    `Content type: ${typeof content}, Default text: ${replyMessageSafeText}`
+  )
 );
features/conversation/dm-conversation.screen.tsx (2)

116-126: Consider implementing optimistic updates.

The TODO comments indicate that optimistic updates are planned. Implementing them would improve the user experience by making the UI feel more responsive.

Consider implementing the commented-out optimistic update logic:

+onMutate: (peerAddress) => {
+  const currentAccount = getCurrentAccount()!;
+  queryClient.setQueryData<ConversationWithCodecsType>(
+    conversationWithPeerQueryKey(currentAccount, peerAddress),
+    () => ({
+      topic: `RANDOM_TOPIC_${Math.random()}`,
+    } satisfies DmWithCodecsType)
+  );
+},

218-218: Implement empty state UI.

The TODO comment indicates that an empty state UI is missing. This could affect user experience when there are no messages.

Would you like me to help generate an empty state component implementation?

features/conversation/composer/composer.tsx (3)

68-72: Consider adding JSDoc comments for better documentation.

The type definitions would benefit from JSDoc comments explaining the purpose and usage of each type.

Add JSDoc comments to improve code documentation:

+/**
+ * Arguments for sending a message through the composer.
+ * @typedef {ISendMessageParams} IComposerSendArgs
+ */
 export type IComposerSendArgs = ISendMessageParams;

+/**
+ * Props for the Composer component.
+ * @interface IComposerProps
+ */
 type IComposerProps = {
+  /** Callback function to handle message sending */
   onSend: (args: IComposerSendArgs) => Promise<void>;
 };

Line range hint 129-132: Address the TODO comment about scroll functionality.

The TODO comment indicates missing scroll functionality that needs to be implemented using a context function.

Would you like me to help implement the scroll functionality or create a GitHub issue to track this task?


363-371: Extract common styles to reduce duplication.

The RemoteAttachmentImage component is used in two places with identical style props. Consider extracting these styles to a constant or theme variable.

Extract the common styles:

+const attachmentImageStyles = {
+  height: theme.avatarSize.md,
+  width: theme.avatarSize.md,
+  borderRadius: theme.borderRadius.xs,
+};

 <RemoteAttachmentImage
   messageId={content.reference}
   remoteMessageContent={content.content.remoteAttachment}
   containerProps={{
-    style: {
-      height: theme.avatarSize.md,
-      width: theme.avatarSize.md,
-      borderRadius: theme.borderRadius.xs,
-    },
+    style: attachmentImageStyles,
   }}
 />

Also applies to: 390-398

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b84d2c3 and b425fec.

⛔ Files ignored due to path filters (1)
  • queries/__snapshots__/QueryKeys.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (79)
  • components/ActivityIndicator/ActivityIndicator.tsx (1 hunks)
  • components/Chat/Attachment/AttachmentMessagePreview.tsx (0 hunks)
  • components/Chat/Attachment/attachment-container.tsx (1 hunks)
  • components/Chat/Attachment/attachment-loading.tsx (1 hunks)
  • components/Chat/Attachment/remote-attachment-image.tsx (1 hunks)
  • components/Chat/ChatGroupUpdatedMessage.tsx (2 hunks)
  • components/Chat/ConsentPopup/ConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx (0 hunks)
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx (1 hunks)
  • components/Chat/ConsentPopup/dm-consent-popup.tsx (1 hunks)
  • components/Chat/ConsentPopup/group-consent-popup.tsx (1 hunks)
  • components/Chat/Message/MessageTail.tsx (0 hunks)
  • components/Chat/Message/MessageTimestamp.tsx (0 hunks)
  • components/Chat/Message/V3Message.tsx (3 hunks)
  • components/Chat/Message/components/message-container.tsx (1 hunks)
  • components/Chat/Message/components/message-content-container.tsx (1 hunks)
  • components/Chat/Message/components/message-layout.tsx (1 hunks)
  • components/Chat/Message/components/message-repliable.tsx (1 hunks)
  • components/Chat/Message/components/message-space-between-messages.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-reply.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-simple-text.tsx (1 hunks)
  • components/Chat/Message/message-content-types/message-static-attachment.tsx (1 hunks)
  • components/Chat/Message/message-date-change.tsx (1 hunks)
  • components/Chat/Message/message-timestamp.tsx (1 hunks)
  • components/Chat/Message/message-utils.tsx (1 hunks)
  • components/Chat/Message/stores/message-store.tsx (0 hunks)
  • components/Conversation/V3Conversation.tsx (7 hunks)
  • components/GroupAvatar.tsx (2 hunks)
  • containers/GroupScreenMembersTable.tsx (2 hunks)
  • custom-eslint-plugin/padding-before-react-hooks.js (1 hunks)
  • design-system/loader.tsx (1 hunks)
  • features/conversation-list/hooks/useMessageText.ts (1 hunks)
  • features/conversation-list/useV3ConversationItems.ts (1 hunks)
  • features/conversation/composer/add-attachment-button.tsx (2 hunks)
  • features/conversation/composer/composer.tsx (9 hunks)
  • features/conversation/composer/send-attachment-preview.tsx (2 hunks)
  • features/conversation/conversation-context.tsx (5 hunks)
  • features/conversation/conversation-persisted-stores.ts (2 hunks)
  • features/conversation/conversation-service.ts (4 hunks)
  • features/conversation/dm-conversation.screen.tsx (1 hunks)
  • features/conversations/components/V3ConversationFromPeer.tsx (1 hunks)
  • features/conversations/utils/messageIsFromCurrentUser.ts (1 hunks)
  • features/search/components/NavigationChatButton.tsx (2 hunks)
  • hooks/useGroupConsent.ts (2 hunks)
  • hooks/useGroupCreator.ts (1 hunks)
  • hooks/useGroupMembers.ts (1 hunks)
  • navigation/useNavigation.tsx (1 hunks)
  • queries/MutationKeys.ts (0 hunks)
  • queries/QueryKeys.ts (2 hunks)
  • queries/queryClient.ts (1 hunks)
  • queries/useAddToGroupMutation.ts (1 hunks)
  • queries/useAllowGroupMutation.ts (1 hunks)
  • queries/useBlockGroupMutation.ts (2 hunks)
  • queries/useConversationQuery.ts (2 hunks)
  • queries/useConversationWithPeerQuery.ts (1 hunks)
  • queries/useDmConstentStateQuery.ts (1 hunks)
  • queries/useDmPeerInbox.ts (1 hunks)
  • queries/useGroupConsentQuery.ts (2 hunks)
  • queries/useGroupDescriptionMutation.ts (1 hunks)
  • queries/useGroupMembersQuery.ts (1 hunks)
  • queries/useGroupNameQuery.ts (1 hunks)
  • queries/useGroupPermissionPolicyQuery.ts (1 hunks)
  • queries/useGroupPinnedFrameQuery.ts (1 hunks)
  • queries/useGroupQuery.ts (1 hunks)
  • queries/usePromoteToAdminMutation.ts (1 hunks)
  • queries/usePromoteToSuperAdminMutation.ts (1 hunks)
  • queries/useRemoveFromGroupMutation.ts (1 hunks)
  • queries/useRevokeAdminMutation.ts (1 hunks)
  • queries/useRevokeSuperAdminMutation.ts (1 hunks)
  • screens/ConversationReadOnly.tsx (1 hunks)
  • screens/Navigation/Navigation.tsx (4 hunks)
  • screens/NewConversation/NewConversation.tsx (1 hunks)
  • screens/Profile.tsx (1 hunks)
  • utils/attachment/handleStaticAttachment.ts (0 hunks)
  • utils/capture-error.ts (1 hunks)
  • utils/groupUtils/groupActionHandlers.ts (2 hunks)
  • utils/xmtpRN/contacts.ts (2 hunks)
  • utils/xmtpRN/conversations.ts (3 hunks)
💤 Files with no reviewable changes (8)
  • components/Chat/Message/MessageTimestamp.tsx
  • components/Chat/Message/MessageTail.tsx
  • components/Chat/ConsentPopup/ConsentPopup.tsx
  • queries/MutationKeys.ts
  • utils/attachment/handleStaticAttachment.ts
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx
  • components/Chat/Attachment/AttachmentMessagePreview.tsx
  • components/Chat/Message/stores/message-store.tsx
🚧 Files skipped from review as they are similar to previous changes (56)
  • components/Chat/Message/message-utils.tsx
  • features/conversations/components/V3ConversationFromPeer.tsx
  • screens/ConversationReadOnly.tsx
  • components/ActivityIndicator/ActivityIndicator.tsx
  • hooks/useGroupMembers.ts
  • queries/useGroupNameQuery.ts
  • features/conversations/utils/messageIsFromCurrentUser.ts
  • queries/queryClient.ts
  • hooks/useGroupCreator.ts
  • navigation/useNavigation.tsx
  • custom-eslint-plugin/padding-before-react-hooks.js
  • features/conversation/conversation-service.ts
  • features/conversation/composer/send-attachment-preview.tsx
  • features/conversation-list/hooks/useMessageText.ts
  • queries/useGroupQuery.ts
  • components/Chat/Message/components/message-repliable.tsx
  • queries/useGroupPermissionPolicyQuery.ts
  • queries/QueryKeys.ts
  • utils/xmtpRN/contacts.ts
  • queries/useGroupPinnedFrameQuery.ts
  • features/search/components/NavigationChatButton.tsx
  • queries/useRevokeAdminMutation.ts
  • features/conversation/conversation-persisted-stores.ts
  • features/conversation-list/useV3ConversationItems.ts
  • queries/useBlockGroupMutation.ts
  • queries/useRevokeSuperAdminMutation.ts
  • components/GroupAvatar.tsx
  • utils/xmtpRN/conversations.ts
  • queries/useGroupDescriptionMutation.ts
  • queries/usePromoteToAdminMutation.ts
  • screens/NewConversation/NewConversation.tsx
  • components/Chat/Message/components/message-space-between-messages.tsx
  • queries/useAddToGroupMutation.ts
  • queries/usePromoteToSuperAdminMutation.ts
  • features/conversation/composer/add-attachment-button.tsx
  • components/Chat/Attachment/attachment-loading.tsx
  • utils/capture-error.ts
  • queries/useRemoveFromGroupMutation.ts
  • utils/groupUtils/groupActionHandlers.ts
  • queries/useGroupMembersQuery.ts
  • components/Chat/Message/message-content-types/message-simple-text.tsx
  • components/Chat/Message/message-content-types/message-remote-attachment.tsx
  • design-system/loader.tsx
  • components/Chat/Attachment/attachment-container.tsx
  • queries/useDmPeerInbox.ts
  • components/Chat/Message/components/message-container.tsx
  • components/Chat/ConsentPopup/group-consent-popup.tsx
  • components/Chat/Message/message-timestamp.tsx
  • queries/useConversationQuery.ts
  • components/Chat/Message/message-date-change.tsx
  • components/Chat/ConsentPopup/consent-popup.design-system.tsx
  • queries/useGroupConsentQuery.ts
  • components/Chat/Message/message-content-types/message-static-attachment.tsx
  • components/Chat/Message/components/message-content-container.tsx
  • components/Chat/Message/components/message-layout.tsx
  • queries/useDmConstentStateQuery.ts
🧰 Additional context used
🪛 Biome (1.9.4)
features/conversation/dm-conversation.screen.tsx

[error] 77-77: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🔇 Additional comments (35)
queries/useConversationWithPeerQuery.ts (3)

1-6: LGTM! Imports are well-organized and properly typed.

The imports cover all necessary dependencies for the hook's functionality.


16-16: Remove unsafe non-null assertion on peer

The non-null assertion (!) on peer in the queryKey is unsafe as peer is explicitly typed as string | undefined.


22-29: Add error handling for conversation fetching

The conversation fetching lacks explicit error handling. Consider adding try-catch to handle potential network or API errors gracefully.

hooks/useGroupConsent.ts (3)

19-22: LGTM: Well-structured type definition

The IGroupConsentOptions type clearly defines the optional flags for including creator and added-by users in the consent operation.


66-75: Inconsistent error handling between allowGroup and blockGroup

While allowGroup throws an error, blockGroup shows a snackbar. Consider standardizing the error handling approach across both functions.


51-52: ⚠️ Potential issue

Add null check for group

The non-null assertion (!) on line 51 could lead to runtime errors if the group is undefined.

Apply this diff to add proper error handling:

-      if (!group) {
-        throw new Error("Group is required");
+      if (!group) {
+        throw new Error(translate("group_not_found"));
       }

Likely invalid or redundant comment.

components/Chat/ConsentPopup/dm-consent-popup.tsx (2)

48-61: Handle potential race conditions in mutation

The concurrent Promise.all calls could lead to inconsistent states if one fails.


77-92: Improve error messages and type safety

The error handling should provide more specific error messages and ensure type safety for the action sheet callback.

queries/useAllowGroupMutation.ts (3)

71-86: Improve error handling in Promise.all operations

The concurrent operations should track individual successes/failures and handle partial failures appropriately.


101-110: Make state updates atomic in onMutate

The state updates in onMutate should be atomic to prevent potential race conditions.


114-137: Add cleanup for partial failures in error handler

The error handler should clean up any partially completed operations.

components/Chat/Message/V3Message.tsx (2)

1-29: LGTM! Well-organized imports and type definitions.

The imports are logically grouped, and the V3MessageProps type is properly defined with required message ID properties.


101-111: Ensure only one message component is rendered per message

The current implementation allows multiple message type conditions to be true simultaneously, which could lead to multiple components being rendered for a single message.

Apply this diff to make the conditions mutually exclusive:

-            {isGroupUpdatedMessage(message) && (
-              <ChatGroupUpdatedMessage message={message} />
-            )}
-            {isReplyMessage(message) && <MessageReply message={message} />}
-            {isTextMessage(message) && <MessageSimpleText message={message} />}
-            {isRemoteAttachmentMessage(message) && (
-              <MessageRemoteAttachment message={message} />
-            )}
-            {isStaticAttachmentMessage(message) && (
-              <MessageStaticAttachment message={message} />
-            )}
+            {isGroupUpdatedMessage(message) ? (
+              <ChatGroupUpdatedMessage message={message} />
+            ) : isReplyMessage(message) ? (
+              <MessageReply message={message} />
+            ) : isTextMessage(message) ? (
+              <MessageSimpleText message={message} />
+            ) : isRemoteAttachmentMessage(message) ? (
+              <MessageRemoteAttachment message={message} />
+            ) : isStaticAttachmentMessage(message) ? (
+              <MessageStaticAttachment message={message} />
+            ) : null}
components/Chat/Attachment/remote-attachment-image.tsx (4)

116-140: LGTM! Well-structured container component.

The AttachmentPreviewContainer is properly memoized and handles props correctly.


78-80: ⚠️ Potential issue

Implement the 'openInWebview' functionality

The onPress handler for unsupported media types is currently empty, which means users cannot open the attachment in a webview as intended.

Would you like assistance in implementing the openInWebview functionality?


95-98: ⚠️ Potential issue

Prevent division by zero when calculating aspect ratio

The aspect ratio calculation could throw an error if the image height is zero.

Apply this diff to add a safety check:

 const aspectRatio =
-  fitAspectRatio && attachment.imageSize
-    ? attachment.imageSize.width / attachment.imageSize.height
+  fitAspectRatio && attachment.imageSize && attachment.imageSize.height !== 0
+    ? attachment.imageSize.width / attachment.imageSize.height
     : undefined;

152-160: ⚠️ Potential issue

Handle potential invalid contentLength values and null account

Two potential issues in the attachment fetching logic:

  1. parseFloat(content.contentLength) may return NaN
  2. Non-null assertion on getCurrentAccount() could lead to runtime errors

Apply this diff to handle both cases:

+ const parsedContentLength = parseFloat(content.contentLength);
+ const account = getCurrentAccount();
+ 
  if (
    content.contentLength &&
+   !isNaN(parsedContentLength) &&
-   parseFloat(content.contentLength) <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE
+   parsedContentLength <= MAX_AUTOMATIC_DOWNLOAD_ATTACHMENT_SIZE &&
+   account
  ) {
    const decryptedLocalAttachment = await fetchAndDecodeRemoteAttachment({
-     account: getCurrentAccount()!,
+     account,
      messageId: messageId,
      remoteAttachmentContent: content,
    });
features/conversation/conversation-context.tsx (3)

1-1: ⚠️ Potential issue

Fix typo in import path for useDmConsentQuery

The import path contains a typo: useDmConstentStateQuery should be useDmConsentStateQuery.

Apply this diff to fix the import path:

-import { useDmConsentQuery } from "@/queries/useDmConstentStateQuery";
+import { useDmConsentQuery } from "@/queries/useDmConsentStateQuery";

67-73: ⚠️ Potential issue

Handle potential undefined topic when querying consent

Using non-null assertion with topic! could lead to runtime errors if topic is undefined.

Apply this diff to handle the undefined case:

-  const { data: groupConsent, isLoading: isLoadingGroupConsent } =
-    useGroupConsentQuery(currentAccount, topic!);
+  const { data: groupConsent, isLoading: isLoadingGroupConsent } = topic
+    ? useGroupConsentQuery(currentAccount, topic)
+    : { data: undefined, isLoading: false };

174-174: ⚠️ Potential issue

Remove TODO comment and implement conversation blocking

The code uses conversation?.state === "denied" for blocking but marks it as TODO.

Either implement the blocking feature properly or remove the reliance on the state check:

-        isBlockedConversation: conversation?.state === "denied", // TODO: implement this
+        isBlockedConversation: false, // Blocking feature not yet implemented
components/Chat/ChatGroupUpdatedMessage.tsx (3)

14-15: LGTM: Type-safe message handling

The change from raw content to DecodedMessage improves type safety and better integrates with the XMTP SDK.

Also applies to: 23-23, 27-27


44-44: LGTM: Enhanced temporal context

The addition of MessageDateChange improves user experience by providing clear temporal context for group updates.


31-36: ⚠️ Potential issue

Improve error handling for string content

The current implementation silently returns null when content is a string. Consider implementing proper error handling and user feedback.

 if (typeof content === "string") {
-  // TODO
-  return null;
+  return (
+    <VStack style={themed($container)}>
+      <Text color="error" preset="smaller">
+        {translate("message_parse_error")}
+      </Text>
+    </VStack>
+  );
 }

Likely invalid or redundant comment.

containers/GroupScreenMembersTable.tsx (1)

45-48: ⚠️ Potential issue

Remove unsafe non-null assertions

The non-null assertions on the topic fallback could lead to runtime errors if both topic and group?.topic are undefined.

-const { members, ... } = useGroupMembers((topic ?? group?.topic)!);
+const topicValue = topic ?? group?.topic;
+if (!topicValue) {
+  return null;
+}
+const { members, ... } = useGroupMembers(topicValue);

 const { data: groupPermissionPolicy } = useGroupPermissionPolicyQuery(
   currentAccount,
-  (topic ?? group?.topic)!
+  topicValue
 );

Likely invalid or redundant comment.

components/Chat/Message/message-content-types/message-reply.tsx (3)

233-233: ⚠️ Potential issue

Remove unsafe non-null assertion

The non-null assertion on useCurrentAccount could lead to runtime errors.

-const currentAccount = useCurrentAccount()!;
+const currentAccount = useCurrentAccount();
+if (!currentAccount) {
+  return undefined;
+}

Likely invalid or redundant comment.


44-48: ⚠️ Potential issue

Replace console.error with proper error tracking

Use sentryTrackError instead of console.error for better error tracking.

 if (typeof replyMessageContent === "string") {
-  // TODO. Render simple bubble message with the content?
-  console.error("reply message is a string");
-  return null;
+  sentryTrackError(new Error("Unexpected string content in reply message"));
+  return (
+    <MessageLayout>
+      <BubbleContainer fromMe={fromMe}>
+        <MessageText inverted={fromMe}>{replyMessageContent}</MessageText>
+      </BubbleContainer>
+    </MessageLayout>
+  );
 }

Likely invalid or redundant comment.


216-227: ⚠️ Potential issue

Add null check for content.content.text

The function should handle cases where content.content.text is undefined.

 function getReplyMessageSafeText(replyMessage: DecodedMessage<[ReplyCodec]>) {
   try {
     const content = replyMessage.content();
     if (typeof content === "string") {
       return content;
     }
-    return content.content.text;
+    return content.content.text ?? replyMessage.fallback;
   } catch (error) {
     sentryTrackError(error);
     return replyMessage.fallback;
   }
 }

Likely invalid or redundant comment.

features/conversation/dm-conversation.screen.tsx (2)

48-49: Remove @ts-ignore and properly type the route params.

The @ts-ignore comment suppresses TypeScript errors but reduces type safety. Consider properly typing the route parameters.


228-236: Fix typo in parameter name 'peerAddresss'.

The parameter name contains an extra 's'.

screens/Navigation/Navigation.tsx (2)

156-159: Add screen options for consistent navigation behavior.

Other screens in the navigation stack include screen options for consistent styling and behavior.


81-87: Consider using a discriminated union for better type safety.

The current union type allows for potential runtime confusion as both parameters could be provided together.

components/Conversation/V3Conversation.tsx (3)

153-155: Remove debug console.log statements.

Debug statements should be removed from production code.

Also applies to: 202-204


106-108: Await the asynchronous sendMessage function.

The sendMessage call is likely asynchronous but is not awaited.


219-222: LGTM: Well-typed MessagesList component.

The MessagesList component has proper TypeScript typing with clear props interface.

screens/Profile.tsx (1)

148-149: ⚠️ Potential issue

Remove non-null assertions for safer type checking.

The non-null assertions (!) on groupTopic could lead to runtime errors if the screen is accessed without a group topic.

The previous review comment about safer alternatives to non-null assertions still applies here. Consider implementing the suggested changes to handle undefined topics gracefully.

Comment on lines +39 to +42
if (!replyMessageContent) {
// TODO
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement proper error handling for null content

The TODO comment should be addressed with proper error handling.

 if (!replyMessageContent) {
-  // TODO
-  return null;
+  return (
+    <MessageLayout>
+      <Text color="error" preset="smaller">
+        {translate("message_content_error")}
+      </Text>
+    </MessageLayout>
+  );
 }

Committable suggestion skipped: line range outside the PR's diff.

@alexrisch alexrisch enabled auto-merge (squash) December 5, 2024 14:54
@alexrisch alexrisch merged commit 4e4764c into release/3.0.0 Dec 5, 2024
4 of 5 checks passed
@alexrisch alexrisch deleted the ts/message-refactoring-attachments branch December 5, 2024 14:54
technoplato pushed a commit that referenced this pull request Dec 17, 2024
* finish attachment + refactor file names + fix new dm convo

* add consent for groups and dms v3 + some refactor

* fix tsc

* fix send attachment

* fix tests and tsc

* fixed types

---------

Co-authored-by: Alex Risch <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants