-
Notifications
You must be signed in to change notification settings - Fork 7
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
Conversation
Caution Review failedThe pull request is closed. WalkthroughThe pull request introduces several significant changes across multiple components in a chat application. Key modifications include the replacement of the Changes
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
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
2f8e18d
to
7d961f4
Compare
There was a problem hiding this 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 strategyThe 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 configurationcomponents/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 interfaceThe 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 documentationAs this is a container for attachments, consider the following improvements:
- Add proper accessibility role and attributes
- Add JSDoc documentation explaining the component's purpose and usage
- 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 renderingWhile 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 codeThe 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 maintainabilityThe 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 configurableThe 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
toLoaderProps
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 toXMTPSimpleTextMessage
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 casesThe 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 implementationWhile the Loader implementation works, there are two potential improvements:
- The transform values (-12) are hardcoded and might break with different loader sizes
- 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
fromSendAttachmentPreviewProps
type- Remove
error
from the destructured parameters in the component definition🔗 Analysis chain
Line range hint
13-19
: Verify the unused error propThe 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/conversationLength 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.tsxLength of output: 2669
queries/useConversationQuery.ts (3)
65-65
: Remove unnecessary empty lineThis 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 strategyWhile the crash debug logging is helpful, consider:
- Adding structured logging with more context (e.g., account, topic)
- 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 strategyWith both conversation queries now using
includeSync: true
, it would be valuable to:
- Document the sync strategy in relation to attachments
- 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 importThe
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 valuesWhile 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 handlingTwo potential improvements for robustness:
- Consider validating the timestamp format before passing it to
getLocalizedTime
- 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 stylesThe 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 trackerThese TODOs contain important future improvements that should be tracked:
- Migration to ReanimatedSwipeable with Expo SDK 52
- Performance optimization using SharedValue
- 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 maintainabilityA few suggestions to enhance the implementation:
- Avoid unsafe type assertion with
(translation as any)._value
- Extract animation threshold values to named constants
- 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 readabilityThe animation logic is complex and could benefit from better organization:
- Extract animation constants
- Add comments explaining the animation stages
- 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 managementThe conversation creation logic needs additional safeguards:
- Error handling for failed conversation creation
- Loading state management during async operations
- Validation of peerAddress format
- 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 maintainabilityThe current implementation has several areas for improvement:
- Duplicate logic for handling attachments in different contexts
- Missing validation for attachments
- 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 managementThe current implementation could benefit from:
- Implementing React Error Boundaries for handling async operation failures
- Moving complex state logic to custom hooks
- Adding proper loading states for async operations
- 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.
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
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-specificWould 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 useDecodedMessageWithCodecsType
. 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:
- Resetting status on error
- Separating concerns into smaller functions
- 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 componentsChatGroupMemberLeft 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 togetherThe imports could be better organized by grouping them into these categories:
- React/React Native core
- Components/UI
- Hooks/Queries
- Types
- 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 messagesThe current error handling uses generic "An error occurred" messages across all operations. Consider:
- Providing operation-specific error messages
- Adding error context to logs
- 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 performanceThe
tableViewItems
calculation has a large dependency array which could trigger unnecessary recalculations. Consider:
- Breaking down the calculation into smaller memoized parts
- 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 experienceThe 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 renderingThe message rendering implementation looks correct, but there might be room for performance improvements:
- Consider implementing windowing/virtualization for better performance with large message lists
- The
nextMessageId
andpreviousMessageId
props provide good context for message groupingscreens/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 functionWhile 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 propertiesThe
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 thestyle
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 codeThere 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 whenreplyMessageContent
is nullThere's a TODO comment indicating that the handling for when
replyMessageContent
isnull
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 whenreplyMessageContent
is a stringThere'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 renderingRemoteAttachmentImage
The code for rendering
RemoteAttachmentImage
here is similar to the code in theMessageReply
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 inenabled
optionThe double negation
!!peerAddress
is unnecessary becausepeerAddress
will be coerced to a boolean in the context of theenabled
option. You can simplify the code by directly passingBoolean(peerAddress)
or simplypeerAddress
.Apply this diff to simplify the code:
- enabled: !!peerAddress, + enabled: Boolean(peerAddress),
75-75
: Simplify conditional rendering without double negationUsing
!!conversation
in the conditional rendering is unnecessary sinceconversation
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 foronSend
callsIn the
send
function, calls toonSend
are not wrapped intry...catch
blocks. IfonSend
throws an error, it could leave the application in an inconsistent state without proper user feedback. Consider adding error handling to manage potential errors fromonSend
.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 touseEffect
to prevent memory leaksThe
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
: RefactorReplyPreviewMessageContent
for better maintainabilityThe
ReplyPreviewMessageContent
component has multipleif
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 forSendButton
componentThe
SendButton
lacks accessibility labels, which might affect users relying on assistive technologies. Add anaccessibilityLabel
prop to theIconButton
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 inonKeyPress
eventThe
onKeyPress
event handler checks forevent.altKey
,event.metaKey
, andevent.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
: SimplifyisLandscape
calculationThe 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
⛔ 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:
- There's a dedicated
retryWithBackoff
utility (utils/retryWithBackoff.ts
) that's used throughout the XMTP integration - 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)
- This retry mechanism is applied to critical operations:
- Streaming conversations:
streamConversations(account)
- Streaming messages:
streamAllMessages(account)
- Database reconnection:
reconnectXmtpClientsDbConnections()
- Streaming conversations:
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:
- The component only has two props:
fromMe
(boolean) andchildren
- The
fromMe
prop is a primitive boolean that will have referential stability - While it receives children, they are part of a controlled message rendering flow and won't cause unnecessary re-renders
- 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 theisTextMessage
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:
- Adding a comment explaining why sync is required
- 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:
- Incomplete implementation for blocked conversations (TODO comment)
- Missing type safety for conversation states
- 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:
- The
estimatedItemSize
TODO needs to be implemented - 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:
-
estimatedItemSize
is actively used in other components:- ChatDumb.tsx uses
estimatedItemSize={34}
- Chat.tsx uses
estimatedItemSize={80}
- EmojiRowList.tsx uses
estimatedItemSize={49}
- ChatDumb.tsx uses
-
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.
/** | ||
* @deprecated Use the Loader component from @design-system instead | ||
*/ | ||
export default Loader; |
There was a problem hiding this comment.
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
export const MessageSpaceBetweenMessages = memo( | ||
function MessageSpaceBetweenMessages() { | ||
const { theme } = useAppTheme(); | ||
return <VStack style={{ height: theme.spacing["4xs"] }} />; | ||
} | ||
); |
There was a problem hiding this comment.
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.
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(); |
There was a problem hiding this comment.
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.
const { theme } = useAppTheme(); | |
const { children, fromMe } = props; |
{ | ||
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, |
There was a problem hiding this comment.
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
-
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.
-
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:
- The aspect ratio is hardcoded but commented as "default". Consider making it configurable through props.
- 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
if (typeof content === "string") { | ||
// TODO | ||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Implement the string content handling, or
- 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()!, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
remoteAttachment?: RemoteAttachmentContentt; | |
remoteAttachment?: RemoteAttachmentContent; |
function useNewConversationHeader(peerAddresss: string) { | ||
const navigation = useRouter(); | ||
|
||
useEffect(() => { | ||
navigation.setOptions({ | ||
headerTitle: () => <NewConversationTitle peerAddress={peerAddresss} />, | ||
}); | ||
}, [peerAddresss, navigation]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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]); | |
} |
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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)
export type IComposerSendArgs = { | ||
text?: string; | ||
remoteAttachment?: RemoteAttachmentContent | null; | ||
referencedMessageId?: MessageId | null; | ||
}; | ||
|
||
type IComposerProps = { | ||
onSend: (args: IComposerSendArgs) => Promise<void>; | ||
}; |
There was a problem hiding this comment.
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.
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>; | |
}; |
There was a problem hiding this 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 debuggingAdding 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 fortopic
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 assertionSince
topic
is now a required parameter, the non-null assertion (topic!
) ingroupPinnedFrameQueryKey
is unnecessary.Apply this diff:
- queryKey: groupPinnedFrameQueryKey(account, topic!), + queryKey: groupPinnedFrameQueryKey(account, topic),
Line range hint
20-20
: Verify enabled conditionThe
enabled
condition still checks for!!topic
which is redundant sincetopic
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 assertionsSince
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:
- Remove unnecessary non-null assertion (
!
) inqueryKey
- Remove redundant null check in
queryFn
- Simplify
enabled
condition sincetopic
is always definedcomponents/Chat/Attachment/attachment-container.tsx (2)
1-7
: Consider creating a specific props interface for AttachmentContainerWhile 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 systemThis 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 handlingWhile the basic implementation is functional, consider these improvements:
- Add type narrowing for better error handling
- 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 supportThe generic error message might not provide enough context to users. Consider:
- Adding i18n support for the error message
- 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 optimizationGood 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 attachmentsWhile 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 interfaceFor 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 codeClean 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 assertionsWhile 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 integrationWhile the type definition is well-documented, consider these improvements:
- Define default values for commonly used props
- Constrain the
color
prop to your design system's color palette for consistencyExample 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 improvementsThe implementation is clean, but consider these enhancements:
- Add
accessibilityLabel
for screen readers- Provide default props for common use cases
- Consider if
memo
is necessary for this simple componentExample 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 casesSince this is a design system component used in attachment loading, consider:
- Creating specialized variants (e.g.,
AttachmentLoader
,InlineLoader
)- Documenting integration patterns with the attachment system
- 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 informationThe 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 configurationSpreading 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 safetyThe mutation function has several areas that could be improved:
- The error case returns
undefined
without proper typing- Missing explicit error messages for invalid states
- 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
andtopic
, 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
: Maketopic
parameter required to avoid non-null assertionsThe function uses non-null assertions (
!
) withtopic
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 componentsThere'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 stylesThe 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 propThe 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 conditionSince
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 extensibilityWhile 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 functionsThe 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 assertionsWhile the
enabled: !!topic
check prevents execution with undefined topic, the non-null assertions (!
) inqueryKey
andqueryFn
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 statementThe commented import
refreshGroup
is not being used and appears to be related to the commented-out code in theonSuccess
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 handlerThe
topic
null check inonMutate
is unnecessary sincetopic
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 handlerThe
topic
null check inonError
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 theonSuccess
handler should be removed to maintain clean code.🔗 Analysis chain
Line range hint
53-57
: Clean up or implement commented code in onSuccessThe
onSuccess
handler contains a commented-outrefreshGroup
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 tsLength 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.tsLength of output: 159
Script:
#!/bin/bash # View the content of conversations.ts cat utils/xmtpRN/conversations.tsLength of output: 14536
queries/useRevokeAdminMutation.ts (5)
Line range hint
22-28
: Remove redundant null check fortopic
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 onMutateThe
!topic
check at the start ofonMutate
is unnecessary sincetopic
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
- Remove the redundant
!topic
check- 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 codeThe 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 keyThe
!
operator in the mutation key suggests it was designed for the previous optionaltopic
. Now thattopic
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 fortopic
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 codeThere 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 assertionThe mutation key uses a non-null assertion (
!
) ontopic
which is no longer needed sincetopic
is now required.- mutationKey: revokeSuperAdminMutationKey(account, topic!), + mutationKey: revokeSuperAdminMutationKey(account, topic),queries/usePromoteToSuperAdminMutation.ts (5)
13-13
: Clean up commented codeThere's commented out code related to
refreshGroup
both in imports andonSuccess
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 topicSince
topic
is now a required parameter (not optional), the null check!topic
inmutationFn
is redundant and can be removed. TypeScript guarantees thattopic
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 onMutateThe check and error throw for
!topic
is redundant sincetopic
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 logicThe
!topic
check inonError
is redundant sincetopic
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 strategyThe mutation implementation mixes TypeScript type guarantees with runtime checks inconsistently. Consider implementing a more structured error handling approach:
- Use TypeScript's type system to handle compile-time guarantees (as started with making
topic
required)- Implement proper error types for runtime errors (e.g., network errors, permission errors)
- 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
- The
mutationFn
should return a consistent type instead ofundefined
- The
onMutate
context type should be explicitly definedConsider 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:
- Use constants for consent states instead of string literals
- Consider using Promises instead of callbacks for better async handling
- 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:
- The
groupName
parameter being undefined could lead to confusing UI messages- Missing validation for required action handlers
- Errors from
allowGroup
andblockGroup
handlers aren't propagatedConsider adding these safeguards:
- Validate required parameters
- Add error handling for the action handlers
- 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 propsThe 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 configurationWhile 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 sentAtWhile 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 statementSince 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); <AnimatedHStackqueries/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 inmutationFn
,onMutate
, andonError
.
Line range hint
23-29
: Remove redundant null checks and non-null assertions.Since
topic
is now a required parameter:
- The
!topic
check inmutationFn
is redundant- The non-null assertion
topic!
inmutationKey
is unnecessaryApply 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 inonMutate
andonError
callbacks sincetopic
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 implementationWhile the loading overlay implementation is solid, there are a few suggestions for improvement:
- The transform values (-12px) appear to be magic numbers that might break if the Loader size changes
- 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
- The
error
prop is defined but not used in theSendAttachmentPreview
component. The prop is passed fromAttachmentsPreview
component with valuemediaPreview.status === "error"
, but no error UI is rendered. Either implement error handling UI or remove the unused prop.- 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
- 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.- 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 typescriptLength 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.tsxLength of output: 20057
components/Chat/ConsentPopup/group-consent-popup.tsx (3)
31-44
: Replace magic string with a typed constantThe 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 handlingThe 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 validationConsider 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 importdebugBorder
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 constantsThe spring animation uses
SICK_DAMPING
andSICK_STIFFNESS
constants. Consider adding documentation explaining the chosen values and their visual effect.
36-40
: Extract fallback height as a constantThe 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 codeRemove the commented-out debug border line to maintain clean code.
42-66
: Consider extracting animation configurationThe 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 assertionSince
topic
is now required, the non-null assertion (!
) ingroupMembersQueryKey(account, topic!)
is no longer needed.- queryKey: groupMembersQueryKey(account, topic!), + queryKey: groupMembersQueryKey(account, topic),
Line range hint
23-42
: Consider reducing code duplication between hooksBoth 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 handlingThe 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:
- Migration to ReanimatedSwipeable
- Better performance using SharedValue
- 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:
- The type assertion
(translation as any)._value
is unsafe and could break at runtime.- 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 functionThere'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:
- Define the conversion factor as a named constant
- Add input validation for negative numbers
- 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:
- If these are generic XMTP message utilities, they belong in
@xmtp/react-native-sdk
- 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 useDecodedMessageWithCodecsType
.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 messagesConsider 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 typesThe 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 UXThe
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 updatesThe
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 strategyWhile logging the error is good, consider these improvements:
- Add more context to the error message (e.g., account details)
- 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:
- Timing measurement for operations
- Error handling for missing client
- 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 actionsThe 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 functionOther conversation-related screens are organized in their own navigation functions (e.g.,
ConversationNav
,NewConversationNav
). Consider moving the DmConversation screen to a separateDmConversationNav
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 robustnessThe current polling implementation has several areas for improvement:
- Hard-coded polling attempts and timeout
- No exponential backoff
- No cleanup on component unmount
- Deeply nested conditional logic
Consider refactoring to:
- Extract polling logic into a custom hook
- Implement exponential backoff
- Add cleanup on unmount
- 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 handlingThe 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:
- Consolidates related states into a single state object
- Makes state transitions more explicit
- Centralizes status-based rendering logic
- Improves maintainability
Also applies to: 191-227
components/GroupAvatar.tsx (1)
Line range hint
205-213
: Fix type safety issue with topic parameterThere's a type safety issue where an optional
topic
prop is being used with a non-null assertion. This could lead to runtime errors iftopic
is undefined.Consider one of these solutions:
- Make
topic
required in props (preferred):type GroupAvatarProps = { - topic?: ConversationTopic; + topic: ConversationTopic; }
- 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:
- Aligns with the reported changes in the AI summary
- Makes the contract clearer - if a topic is required for the component to work, it should be a required prop
- 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 functionThe 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 operationsThe current error handling in
handleRightAction
could be more informative and consistent:
- The generic "An error occurred" message doesn't help users understand what went wrong
- 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 performanceThe search functionality could benefit from several performance optimizations:
- The search logic is complex and could trigger unnecessary re-renders
- State updates could be batched
- 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 supportThe component could benefit from improved accessibility features to better support users with disabilities:
- Missing accessibility labels on interactive elements
- No screen reader announcements for loading states
- 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 implementationThe 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 preventionThe 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 mechanismThe 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 betweenhandleBlock
andhandleAccept
.In
handleBlock
, errors are handled usingcaptureError
andshowSnackbar
. InhandleAccept
, errors are handled usingcaptureErrorWithToast
. 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 pathsThe 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 ofconsole.log
Using
console.log
for logging errors is not ideal in production code. Consider using a dedicated logging library orconsole.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 codebaseThe
...debugBorder()
line is commented out within thestyle
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 codeThere 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 atype
rather than aninterface
, theI
prefix might be misleading. Consider renaming it toGroupConsentOptions
for clarity and consistency with TypeScript naming conventions.
121-121
: Unnecessary non-null assertion on 'topic'The non-null assertion operator
!
ontopic
may be unnecessary here, astopic
is already defined as a non-nullableConversationTopic
. 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 executionThe function
consentToInboxIdsOnProtocolByAccount
appears to be asynchronous. Calling it withoutawait
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 functionsThe logic for constructing
inboxIdsToDeny
inblockGroup
mirrors the construction ofinboxIdsToAllow
inallowGroupMutation
. 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
andblockGroup
functions.features/conversation/conversation-context.tsx (2)
Line range hint
91-105
: Add error handling in conversation creation and message sendingWhen creating a new conversation and sending a message, exceptions might occur during
createConversationByAccount
ornewConversation.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 forisBlockedConversation
or remove the placeholderThe
isBlockedConversation
property uses a placeholder with a TODO comment:isBlockedConversation: conversation?.state === "denied", // TODO: implement thisWould 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 componentThe
ChatGroupMemberJoined
andChatGroupMemberLeft
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 whenreplyMessageContent
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 missingreplyMessageReference
.If
replyMessageReference
is not available, the component doesn't renderMessageReplyReferenceContent
. 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 wherecontent
is neither string nor containsremoteAttachment
.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 ofRemoteAttachmentImage
.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 ofMessageText
.Similarly, simplify the condition when rendering
MessageText
.Update the condition as:
- {!!replyMessageContent.content.text && ( + {replyMessageContent.content.text && (
216-227
: Avoid redundant error tracking ingetReplyMessageSafeText
.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 negationThe 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 statesThere 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 experienceThe 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
: EnsureonSend
is not called with an empty objectAll properties in
IComposerSendArgs
are optional, which could allowonSend
to be called with an empty object. If at least one oftext
,remoteAttachment
, orreferencedMessageId
is required foronSend
to function correctly, consider enforcing this through type definitions or runtime validation.
13-13
: Ensure consistent import path aliasingThe 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
⛔ 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:
- The only usage of
useGroupCreator
is inuseGroupConsent.ts
where it's called with atopic
parameter that comes from the component's parameters - The hook's implementation already assumes
topic
is required as shown by theenabled: !!topic
condition in the query configuration - 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
:
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
andfilter
- 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
andsentryTrackError
- 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:
- That
getV3IdFromTopic
handles all possible topic formats correctly - 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:
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.
- Full topic format:
- 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:
- The
useConversationQuery
function maintains a compatible interface, acceptingaccount
andtopic
parameters - The usage in
useGroupNameQuery
correctly utilizes the new query function to fetch conversation data - The data structure remains consistent, as evidenced by the type checking on
conversation.version
andconversation.groupName()
- 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:
-
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
-
No TODO/FIXME comments related to group messaging functionality issues were found in the codebase.
-
The group consent functionality is used in a well-structured way across the application, with proper separation of concerns.
-
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
andisLoadingConversationConsent
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
: usesgroupTopic!
screens/NewConversation/NewConversation.tsx
: usesroute.params?.addingToGroupTopic!
hooks/useGroupPendingRequests.ts
: usestopic
containers/GroupScreenName.tsx
: usestopic
components/GroupAvatar.tsx
: usestopic!
containers/GroupScreenImage.tsx
: usestopic
containers/GroupScreenDescription.tsx
: usestopic
containers/GroupScreenAddition.tsx
: usestopic
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 cachesetConversationQueryData
: Updates cache with new datarefetchConversationQuery
: Triggers a fresh fetchgetConversationQueryData
: 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 setincludeSync: 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:
- Ensure all callers of
groupRemoveRestoreHandler
are updated to use the new type - 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:
- All callers of
groupRemoveRestoreHandler
are already using the newConsentState
type:components/Chat/ConsentPopup/group-consent-popup.tsx
containers/GroupScreenConsentTable.tsx
ConsentState
from XMTP SDK includes "denied" as a valid state, confirmed by multiple usages in the codebase whereConsentState.DENIED
is used for consent checks.- 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:
- Group data remains consistent after promoting admins
- The UI updates correctly without manual refresh
- 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
:
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 useMessageId | 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
anduserCanDoGroupActions
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
:
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
:
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
- The peer address should be masked in the error logs.
- 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
:
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
:
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
andutils/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
:
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
:
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:
- The component only renders metadata changes when
content.metadataFieldsChanged
exists and has entries - The SDK's
GroupUpdatedCodec
ensuresinitiatedByInboxId
is present for group updates - 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
:
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
:
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:
- The component properly checks for undefined messages with
!!previousMessage
and!!nextMessage
before using them in_hasPreviousMessageInSeries
and_hasNextMessageInSeries
calculations. - There's a null check for the main message (
if (!message) { return null; }
). - 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 |
There was a problem hiding this comment.
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 bothtopic
andgroup?.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
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", | ||
}); | ||
} |
There was a problem hiding this comment.
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:
- The type cast to Error is unsafe
- 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.
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", | ||
}); | ||
} |
There was a problem hiding this comment.
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
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"; | ||
|
There was a problem hiding this comment.
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 itqueries/useGroupMembersQuery.ts
contains the implementationfeatures/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!), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
console.log("isAllowedConversation:", isAllowedConversation); | ||
console.log("isLoadingConversationConsent:", isLoadingConversationConsent); | ||
|
There was a problem hiding this comment.
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
async (args: IComposerSendArgs) => { | ||
sendMessage(args); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
async (args: IComposerSendArgs) => { | |
sendMessage(args); | |
}, | |
async (args: IComposerSendArgs) => { | |
await sendMessage(args); | |
}, |
containerProps={{ | ||
style: { | ||
height: theme.avatarSize.md, | ||
width: theme.avatarSize.md, | ||
borderRadius: theme.borderRadius.xs, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
await onSend({ | ||
remoteAttachment: uploadedRemoteAttachment, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
containerProps={{ | ||
style: { | ||
height: theme.avatarSize.md, | ||
width: theme.avatarSize.md, | ||
borderRadius: theme.borderRadius.xs, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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 aliasThe type alias
IComposerSendArgs = ISendMessageParams
adds an extra layer of indirection without providing additional value. Consider usingISendMessageParams
directly for better code clarity.-export type IComposerSendArgs = ISendMessageParams;
Line range hint
78-133
: Consider splitting the send function for better maintainabilityThe
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 constantThe
containerProps
styles forRemoteAttachmentImage
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 contentThe 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 statementDebug statements should not be committed to production code.
-console.log("payload:", payload);
Line range hint
92-144
: Refactor message sending logic to reduce complexityThe 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 handlingThe 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 sendingThe 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
📒 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! |
There was a problem hiding this comment.
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
9d31a49
to
47e992c
Compare
Performance Comparison ReportSignificant Changes To DurationThere are no entries Meaningless Changes To DurationShow entries
Show details
Render Count ChangesThere are no entries Render IssuesThere are no entries Added ScenariosThere are no entries Removed ScenariosThere are no entries |
There was a problem hiding this 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 reusabilityThe 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 getDmPeerInboxWhile 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 mutationFnSince
topic
is now a required parameter, the null check is redundant. Onlygroup
andaccount
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 callbacksSince
topic
is now a required parameter, the null checks inonMutate
andonError
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 failuresThe
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 feedbackThe 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 mechanismWhen 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 codeThe 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 actionsGeneric "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 conditionThe 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 precisionThe 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 detailsConsider 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 handlingThe 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 implementationConsider the following improvements:
- Handle potential null case for
currentAccount
- 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 calculationsConsider 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 queryFnThe 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 refetchConversationQueryThe 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 sendMessageThe 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 aliasRather than using a type alias, consider explicitly defining
IComposerSendArgs
to ensure type stability and clarity. This would prevent unexpected changes ifISendMessageParams
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 commentThe 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 maintainabilityThe
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 constantThe 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 performanceThe 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 supportAdd 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 operationsThe 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 statusThe 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-negationThe double negation
!!conversation
is unnecessary since the conditional expression will coerceconversation
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 experienceThere's a
TODO
comment indicating that addingonMutate
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 messagesSimilarly, there's a
TODO
suggesting the addition ofonMutate
to thesendMessageAsync
mutation for optimistic updates. Implementing this would make message sending feel instantaneous to the user.Would you like help in creating the
onMutate
function forsendMessageAsync
to optimistically update the message list?screens/Navigation/Navigation.tsx (1)
153-156
: Add screen options for consistent navigation behaviorThe
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 likeConversationNav
andGroupNav
, promoting consistency in navigation transitions and header styling.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 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:
- No references to
useConversationScreenQuery
exist in the codebase - The new
useConversationQuery
hook is properly implemented with complete functionality - 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
:
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
:
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
:
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
:
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
:
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:
- Import:
// import { refreshGroup } from "../utils/xmtpRN/conversations"
- 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;
+ };
// ...
};
if (typeof replyMessageContent === "string") { | ||
// TODO. Render simple bubble message with the content? | ||
console.error("reply message is a string"); | ||
return null; | ||
} |
There was a problem hiding this comment.
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.
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; | |
} |
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!), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
isBlockedConversation: conversation?.state === "denied", // TODO: implement this | |
isBlockedConversation: false, // Blocking feature not yet implemented |
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> |
There was a problem hiding this comment.
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.
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> |
const handleReply = useCallback(() => { | ||
setCurrentConversationReplyToMessageId(messageId); | ||
}, [messageId]); |
There was a problem hiding this comment.
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.
{content.metadataFieldsChanged.map((entry, index) => ( | ||
<ChatGroupMetadataUpdate | ||
key={`metadata-${index}`} | ||
metadataEntry={entry} | ||
initiatorInboxId={content.initiatedByInboxId as InboxId} | ||
/> | ||
))} |
There was a problem hiding this comment.
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.
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]); |
There was a problem hiding this comment.
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.
async (selectedIndex?: number) => { | ||
if (selectedIndex === 0) { | ||
try { | ||
await consentToInboxIdsOnProtocolByAccountAsync({ | ||
consent: "deny", | ||
}); | ||
navigation.pop(); | ||
} catch (error) { | ||
captureError(error); | ||
showSnackbar({ | ||
type: "error", | ||
message: `Error consenting`, | ||
}); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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.
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"), | |
}); | |
} | |
} | |
} |
hooks/useGroupConsent.ts
Outdated
await Promise.all([ | ||
consentToGroupsOnProtocolByAccount({ | ||
account, | ||
groupIds: [getV3IdFromTopic(topic)], | ||
consent: "allow", | ||
}), | ||
...(inboxIdsToAllow.length > 0 | ||
? [ | ||
consentToInboxIdsOnProtocolByAccount({ | ||
account, | ||
inboxIds: inboxIdsToAllow, | ||
consent: "allow", | ||
}), | ||
] | ||
: []), | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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(', ')}`); | |
} |
const handleSendMessage = useCallback( | ||
async (args: IComposerSendArgs) => { | ||
try { | ||
const { text, remoteAttachment, referencedMessageId } = args; | ||
const newConversation = await createNewConversationAsync(peerAddress); | ||
await sendMessageAsync({ | ||
conversation: newConversation, | ||
text, | ||
remoteAttachment, | ||
referencedMessageId, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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'.
There was a problem hiding this 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 logicThe 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 functionThe 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
⛔ 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.
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); | ||
// } | ||
// }, | ||
}); |
There was a problem hiding this comment.
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.
// @ts-ignore | ||
const { peerAddress } = props.route.params; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
if (isLoading) { | ||
return ( | ||
<Screen contentContainerStyle={{ flex: 1 }}> | ||
<Center | ||
style={{ | ||
flex: 1, | ||
}} | ||
> | ||
<Loader /> | ||
</Center> | ||
</Screen> | ||
); | ||
} |
There was a problem hiding this comment.
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.
8daf61a
to
71fc31d
Compare
There was a problem hiding this 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 messageThe 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 maintainabilityWhile 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 aliasThe type
IComposerSendArgs
is just an alias forISendMessageParams
. Consider usingISendMessageParams
directly to reduce type indirection.-export type IComposerSendArgs = ISendMessageParams;
Line range hint
78-133
: Consider splitting the send functionThe
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 functionalityThe 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 constantThe 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 conditionThe 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 dataThe 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 operationThe 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 feedbackThe 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 mutationThe 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 maintainabilityThe 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 renderingThe 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 renderingThe 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 functionTo 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 contentConsider 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 documentationThe 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 stateThe 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 conversionReplace 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 specificityThe 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 UIThe 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 conversationsThe 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 safelyThe 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 headerThe 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
⛔ 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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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()!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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()!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
const currentAccount = useCurrentAccount()!; | |
const currentAccount = useCurrentAccount(); | |
if (!currentAccount) { | |
return undefined; | |
} |
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, | ||
}), | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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; | |
} |
if (!message) { | ||
console.log("no message found", messageId); | ||
return null; |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 blockGroupThe 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
📒 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; | ||
} |
There was a problem hiding this comment.
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.
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")); | |
} |
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); | ||
} | ||
}; |
There was a problem hiding this comment.
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.
await Promise.all([ | ||
consentToGroupsOnProtocolByAccount({ | ||
account, | ||
groupIds: [getV3IdFromTopic(groupTopic)], | ||
consent: "allow", | ||
}), | ||
...(inboxIdsToAllow.length > 0 | ||
? [ | ||
consentToInboxIdsOnProtocolByAccount({ | ||
account, | ||
inboxIds: inboxIdsToAllow, | ||
consent: "allow", | ||
}), | ||
] | ||
: []), | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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(", ")}` | |
); | |
} |
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); | ||
} |
There was a problem hiding this comment.
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.
b84d2c3
to
b425fec
Compare
There was a problem hiding this 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 safetyWhile 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 messageThe "[Crash Debug]" prefix suggests this is temporary debugging code. Consider either:
- Removing it if it was only for development
- 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 operationsThe 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 codeThe 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 logicThe 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 calculationsConsider 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 messageThe 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
⛔ 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
:
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
:
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
:
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
:
Handle potential invalid contentLength values and null account
Two potential issues in the attachment fetching logic:
parseFloat(content.contentLength)
may returnNaN
- 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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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.
if (!replyMessageContent) { | ||
// TODO | ||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
* 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]>
Summary by CodeRabbit
Release Notes
New Features
Loader
component for enhanced loading indicators.AttachmentContainer
andAttachmentLoading
components for improved attachment handling.DmConsentPopup
andGroupConsentPopup
for managing user consent in conversations.MessageContainer
,MessageContentContainer
, andMessageLayout
for better message structuring in chat.V3Conversation
component for improved conversation handling and consent management.MessageDateChange
,MessageRemoteAttachment
, andMessageReply
components to enrich message interactions.Bug Fixes
Refactor
useGroupConsent
,useConversationQuery
, anduseAllowGroupMutation
.Chores