Conversation
…rofile sections - Added ChatContext for shared state management across tabs. - Created TabLayout component to manage navigation and state for chats, friends, and profile. - Developed FriendsTab for managing friend requests and searching for users. - Implemented ChatsTab for displaying chat lists and handling chat interactions. - Built ProfileTab for user profile management and settings access. - Integrated WebSocket for real-time updates on friend statuses and chat messages. - Enhanced user experience with notifications and loading states.
… and sandboxing features
…hance logout flow in ProfileTab
…ltiple components
…t Native integration - Modified project.pbxproj to include React and ReactNativeDependencies frameworks in the Embed Pods Frameworks build phase. - Removed unnecessary privacy bundles from the Copy Pods Resources build phase. - Updated AppDelegate.swift to set the window background color and override user interface style to dark mode. - Changed Info.plist to enable the new architecture for React Native and set the UIUserInterfaceStyle to Dark.
…y across multiple components
Bumps [react-native-screens](https://github.com/software-mansion/react-native-screens) from 4.18.0 to 4.19.0. - [Release notes](https://github.com/software-mansion/react-native-screens/releases) - [Commits](software-mansion/react-native-screens@4.18.0...4.19.0) --- updated-dependencies: - dependency-name: react-native-screens dependency-version: 4.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
…native-screens-4.19.0
Bumps [@react-native/codegen](https://github.com/facebook/react-native/tree/HEAD/packages/react-native-codegen) from 0.82.1 to 0.83.1. - [Release notes](https://github.com/facebook/react-native/releases) - [Changelog](https://github.com/facebook/react-native/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react-native/commits/v0.83.1/packages/react-native-codegen) --- updated-dependencies: - dependency-name: "@react-native/codegen" dependency-version: 0.83.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [expo-linear-gradient](https://github.com/expo/expo/tree/HEAD/packages/expo-linear-gradient) from 15.0.7 to 15.0.8. - [Changelog](https://github.com/expo/expo/blob/main/packages/expo-linear-gradient/CHANGELOG.md) - [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-linear-gradient) --- updated-dependencies: - dependency-name: expo-linear-gradient dependency-version: 15.0.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [expo-audio](https://github.com/expo/expo/tree/HEAD/packages/expo-audio) from 1.0.16 to 1.1.1. - [Changelog](https://github.com/expo/expo/blob/main/packages/expo-audio/CHANGELOG.md) - [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-audio) --- updated-dependencies: - dependency-name: expo-audio dependency-version: 1.1.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [expo-status-bar](https://github.com/expo/expo/tree/HEAD/packages/expo-status-bar) from 3.0.8 to 3.0.9. - [Changelog](https://github.com/expo/expo/blob/main/packages/expo-status-bar/CHANGELOG.md) - [Commits](https://github.com/expo/expo/commits/HEAD/packages/expo-status-bar) --- updated-dependencies: - dependency-name: expo-status-bar dependency-version: 3.0.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…native/codegen-0.83.1
…tatus-bar-3.0.9
…inear-gradient-15.0.8
…ext menu functionality
…s and streamline modal usage
… control in chat component
…ralOptions, ProfileCard, and ScheduleMessageSheet - Added SwiftUI support for iOS in CreatePollSheet, replacing existing components with SwiftUI counterparts. - Implemented SwiftUI BottomSheet and VStack for better UI consistency and performance. - Enhanced EphemeralOptions to utilize SwiftUI for displaying ephemeral message durations. - Refactored ProfileCard to use SwiftUI for rendering user information and actions. - Updated ScheduleMessageSheet to leverage SwiftUI DateTimePicker for scheduling messages. - Added fallback to React Native components for Android and non-iOS platforms.
…and ProfileCard components
…creens - Added new SwiftUI components and modifiers in ChatScreen for improved reaction handling. - Updated ChatListWidget to always show the ProfileCard, even when user details are loading. - Enhanced CreatePollSheet with additional SwiftUI components and improved layout. - Improved EphemeralOptions with SwiftUI integration for better user experience. - Refactored ProfileCard to use a shared content component and improved styling. - Updated ScheduleMessageSheet to include new SwiftUI components and better layout.
…nents - Integrated SwiftUI components in ProfileCard for improved UI consistency. - Added functionality to display user initials and badges in ProfileCard. - Refactored ScheduleMessageSheet to utilize SwiftUI for scheduling messages. - Implemented a custom date and time picker using SwiftUI. - Improved layout and styling for both components to enhance user experience.
Updated the REACT_NATIVE_PATH in the Xcode project configuration to reference @types/react version 19.1.17 instead of 19.2.7 for both debug and release configurations.
… poll creation and hydration logic
…ed visibility in GroupMemberPicker
…ollSheet, EphemeralOptions, and ScheduleMessageSheet components
…ve SwiftUI support and implement BlurView for improved UI. Update styles for better visual consistency and adjust modal layouts.
…ing background tasks
…roved error handling
… and signing processes
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughThis pull request performs a major version bump (1.1.4 → 2.0.3) with substantial architectural changes: migrates from PIN-based identity bootstrapping to login-integrated E2EE initialization, introduces SwiftUI components for iOS, implements backup envelope encryption support, refactors tab-based navigation with real-time WebSocket integration, and systematically replaces GlassCard components with native View containers across the app. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant App
participant LoginService
participant CryptoService
participant StorageService
participant WebSocketService
User->>App: Open app / Focus screen
App->>App: validateTokenAndNavigate()
alt Token exists
App->>StorageService: Retrieve token
App->>StorageService: Retrieve encrypted identity
alt Local identity missing
App->>App: Clear token & user data
App->>App: Navigate to login
else Local identity exists
App->>CryptoService: initializeFromLogin(password, token, identityKey)
activate CryptoService
CryptoService->>CryptoService: Decrypt identity with password
CryptoService->>StorageService: Persist identity
CryptoService->>CryptoService: Attempt initializeBackupKey() (non-blocking)
deactivate CryptoService
App->>WebSocketService: Connect & register handlers
App->>App: Navigate to /(tabs)
end
else No token
App->>App: Navigate to login
end
sequenceDiagram
participant ChatComponent
participant EnvelopeCache
participant CryptoService
participant Storage as StorageService
ChatComponent->>ChatComponent: Fetch messages with envelopes
loop For each message
ChatComponent->>EnvelopeCache: Check decryptionCache[messageId]
alt Cached
EnvelopeCache-->>ChatComponent: Return decrypted content
else Not cached
ChatComponent->>CryptoService: Attempt decryptEnvelope()
alt Success
CryptoService-->>ChatComponent: Decrypted payload
ChatComponent->>EnvelopeCache: Store in decryptionCache
else Envelope decryption fails
ChatComponent->>CryptoService: Attempt decryptFromBackup(backupEnvelopes)
alt Backup success
CryptoService-->>ChatComponent: Decrypted from backup
ChatComponent->>EnvelopeCache: Cache result
else Both fail
ChatComponent->>ChatComponent: Use fallback "[Encrypted message]"
ChatComponent->>ChatComponent: Mark for re-encrypt prompt
end
end
end
end
ChatComponent->>ChatComponent: Render messages with decrypted content
sequenceDiagram
participant TabLayout
participant WebSocket as WebSocketService
participant CryptoService
participant ChatContext
participant UIComponents
TabLayout->>TabLayout: Initialize (hydrate cache, fetch user)
TabLayout->>WebSocket: Connect with token
activate WebSocket
WebSocket->>WebSocket: Listen for message events
alt User status update received
WebSocket->>ChatContext: Update userStatuses
ChatContext->>UIComponents: Re-render with new presence
else New message received
WebSocket->>TabLayout: Trigger loadChats()
TabLayout->>ChatContext: Update chat list & unread count
ChatContext->>UIComponents: Re-render chat list
else Typing indicator
WebSocket->>ChatContext: Update typingUsers
ChatContext->>UIComponents: Show typing indicator
end
deactivate WebSocket
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
components/NativeContextMenu.tsx (1)
47-52:⚠️ Potential issue | 🟡 MinorProps
onMenuWillShow,onMenuWillHide, andprevieware declared but not used.These props are defined in the interface but are not wired up in any of the three rendering paths (SwiftUI, LegacyContextMenu, or fallback). This could lead to caller confusion when callbacks are passed but never invoked.
💡 Options to address
- Remove unused props if they're not planned for immediate implementation
- Wire them up in the SwiftUI and LegacyContextMenu paths:
- SwiftUI
ContextMenumay support equivalent callbacksreact-native-context-menu-viewsupportsonPreviewPress,onMenuWillShow,onMenuWillHide<LegacyContextMenu title={title} actions={contextMenuActions} onPress={handlePress} previewBackgroundColor="transparent" dropdownMenuMode={dropdownMenuMode} style={style} + onMenuWillShow={onMenuWillShow} + onMenuWillHide={onMenuWillHide} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/NativeContextMenu.tsx` around lines 47 - 52, The props preview, onMenuWillShow, and onMenuWillHide declared on the NativeContextMenu component are never used; either remove them from the interface or wire them into all rendering paths (SwiftUI branch, LegacyContextMenu branch, and the fallback). If wiring, forward preview into the SwiftUI ContextMenu/ContextMenuPreview and into LegacyContextMenu props (and the react-native-context-menu-view equivalents such as onPreviewPress/onMenuWillShow/onMenuWillHide) and invoke/forward callbacks at the points where the menu is shown/dismissed so consumers receive the events; ensure NativeContextMenu's prop names map to the underlying platform prop names where they differ.screens/PrivacyScreen.tsx (1)
35-35:⚠️ Potential issue | 🟡 MinorRemove unused
isBootstrappingstate.The
isBootstrappingstate is declared at line 35 but never set to any value other than its initialfalse. It's referenced in the guard condition at line 226, ingetRotateKeysText()at line 312, and in conditional rendering at line 424, but since it never changes, these checks are dead code. Remove the state declaration and its references.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/PrivacyScreen.tsx` at line 35, The isBootstrapping useState declaration is dead code and should be removed: delete the const [isBootstrapping, setIsBootstrapping] = useState(false) declaration, remove all references to isBootstrapping in the guard condition (where it’s checked around line ~226), in getRotateKeysText() (referenced around line ~312), and in the conditional rendering block (around line ~424), and update logic to rely on the real bootstrap status variable(s) already used elsewhere; also remove useState from the import list if it becomes unused. Ensure no other code expects setIsBootstrapping to exist and adjust any boolean expressions accordingly.services/CryptoService.ts (1)
95-110:⚠️ Potential issue | 🔴 CriticalReject Math.random fallback for cryptographic entropy.
The randomBytes function falls back to Math.random, which is cryptographically unsafe and produces predictable output. This directly affects identity key generation (via generateNewKeyPair), encryption nonces, and password salts. If Expo's secure RNG is unavailable, the entire encryption layer becomes predictable. Fail fast with a hard error instead of silently generating weak cryptographic material.
🔒 Proposed fix (fail fast on missing secure RNG)
try { Crypto.getRandomValues(bytes); return bytes; } catch (error) { - console.warn('[CryptoService] Falling back to Math.random entropy', error); - for (let i = 0; i < bytes.length; i += 1) { - bytes[i] = Math.floor(Math.random() * 256); - } - return bytes; + console.error('[CryptoService] Secure RNG unavailable', error); + throw new Error('Secure RNG unavailable'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/CryptoService.ts` around lines 95 - 110, The randomBytes function currently falls back to Math.random which is insecure; modify randomBytes in CryptoService.ts to remove the Math.random fallback and instead throw a descriptive error when a secure RNG is not available (check globalThis.crypto?.getRandomValues and the Crypto.getRandomValues branch), and update any call sites like generateNewKeyPair to allow the error to propagate or handle it so key generation fails fast rather than producing weak entropy.screens/RegisterScreen.tsx (1)
58-90:⚠️ Potential issue | 🟠 MajorAvoid logging emails/usernames and full API responses.
These logs can leak PII and potentially tokens in production logs. Prefer removing them or gating behind
__DEV__with redaction.🛠️ Suggested change
- console.log('Starting registration for:', e, '(username:', u, ')'); + if (__DEV__) { + console.log('Starting registration'); + } ... - console.log('Register response:', response); + if (__DEV__) { + console.log('Register response status:', { success: response.success, statusCode: response.statusCode }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/RegisterScreen.tsx` around lines 58 - 90, Remove console.log statements that print PII (email `e`, username `u`) and full API responses (`response`) in the registration flow inside RegisterScreen; instead either fully remove them or wrap them in a development-only guard (if (__DEV__)) and redact sensitive fields before logging. Specifically update usages around ApiService.post call, the `response` handling, and the branches that call notificationService.show and router.replace so that you only log non-sensitive flags (e.g., `response.success` or `verified`) or sanitized messages, and never include `e`, `u`, passwords, tokens, or the raw `response` object in production logs. Ensure router.replace and notificationService.show calls are unchanged except for removing any logging of PII.
🟡 Minor comments (10)
README.md-104-141 (1)
104-141:⚠️ Potential issue | 🟡 MinorAdd a language to the fenced code block.
Markdownlint flags the project structure block as missing a language specifier. Adding one (e.g.,
text) will satisfy MD040 without changing content.✅ Suggested fix
-``` +```text Mobile/ ├── app/ # Expo Router routes (screens) │ ├── (tabs)/ # Tab-based navigation screens │ │ ├── home.tsx # Home/chat list screen │ │ ├── friends.tsx # Friends management screen │ │ ├── profile.tsx # User profile screen │ │ └── _layout.tsx # Tab navigation layout │ ├── chat/ # Chat screens │ │ └── [id].tsx # Individual chat screen (dynamic route) │ ├── login.tsx # Login screen │ ├── register.tsx # Registration screen │ ├── verify.tsx # Email verification screen │ └── _layout.tsx # Root layout with providers ├── components/ # Reusable UI components │ ├── MessageBubble.tsx # Message display component │ ├── ChatList.tsx # Chat list component │ └── ... ├── context/ # React context providers │ ├── AuthContext.tsx # Authentication state │ ├── ChatContext.tsx # Chat/messaging state │ └── ... ├── hooks/ # Custom React hooks │ ├── useAuth.ts # Authentication hook │ ├── useChat.ts # Chat management hook │ └── ... ├── services/ # Core services │ ├── ApiService.ts # REST API client │ ├── WebSocketService.ts # WebSocket communication │ ├── CryptoService.ts # Encryption/decryption │ ├── IdentityService.ts # Identity key management │ ├── ReencryptionService.ts # Message re-encryption │ ├── StorageService.ts # Secure storage wrapper │ └── ... ├── types/ # TypeScript type definitions ├── constants/ # App constants └── assets/ # Static assets (images, fonts) -``` +```🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 104 - 141, The fenced code block showing the project tree in README.md is missing a language specifier which triggers MD040; update the triple-backtick fence that wraps the Mobile/ tree to include a language like text (i.e., change ``` to ```text) so the block is explicitly marked as plain text—look for the block beginning with the "Mobile/" line and the closing triple backticks and add the language specifier there.screens/SettingsScreen.tsx-32-33 (1)
32-33:⚠️ Potential issue | 🟡 MinorNotification toggle has no effect; Dark Mode state is unused.
notificationsEnabledstate only updates UI—there's no integration withNotificationServiceto actually enable/disable push notifications. This could mislead users into thinking they've toggled notifications.darkModeEnabledstate setter is connected to a disabled switch (line 181), making thesetDarkModeEnabledcall dead code.Consider either:
- Wiring
notificationsEnabledto actual notification permission/preference logic, or- Removing/disabling the toggle until the feature is implemented (similar to Dark Mode).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/SettingsScreen.tsx` around lines 32 - 33, notificationsEnabled is only toggling UI and never calls into the app's notification logic, and darkModeEnabled/setDarkModeEnabled are wired to a disabled switch (dead code); update the SettingsScreen to either wire the notification toggle to the real NotificationService (e.g., call NotificationService.requestPermission(), NotificationService.enablePush() or NotificationService.updatePreference(...) inside the onToggle handler where setNotificationsEnabled is used) and handle async success/failure by reverting state if needed, or remove/disable the notifications toggle until implemented; also remove or fully implement darkModeEnabled/setDarkModeEnabled (used in the same component) so the setter isn't dead—either connect it to your theme manager (e.g., ThemeContext.toggleDarkMode or ThemeService.setDarkMode) or remove the state and disabled switch to avoid dead code.services/UserCacheService.ts-113-124 (1)
113-124:⚠️ Potential issue | 🟡 MinorPotential race condition with in-progress hydration.
If
clear()is called whilestartHydration()is still running, the async hydration will complete and setthis.hydrated = truein itsfinallyblock, overwriting thefalseset here. This could leave the cache in an inconsistent state wherehydratedistruebut the cache is empty.Consider awaiting or cancelling the in-progress hydration before clearing state:
Proposed fix
clear(): void { if (this.persistTimer) { clearTimeout(this.persistTimer); this.persistTimer = null; } this.cache.clear(); - this.hydrated = false; - this.hydrating = null; + // Only reset hydration state if not currently hydrating + // The next hydrate() call will re-trigger startHydration() + if (!this.hydrating) { + this.hydrated = false; + } StorageService.removeItem(STORAGE_KEY).catch((err) => console.warn('[UserCache] Failed to remove persisted cache:', err) ); }Alternatively, track a cancellation token or await the hydration promise before clearing.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/UserCacheService.ts` around lines 113 - 124, The clear() method can race with an in-flight startHydration() because startHydration()'s finally will set this.hydrated = true after clear() set it false; update clear() to cancel or await any ongoing hydration by checking and handling this.hydrating: either await this.hydrating before clearing state, or set a cancellation flag/token that startHydration() checks (e.g., a this.hydrationCancelled boolean or a CancellationToken) and have startHydration() skip setting this.hydrated when cancelled; ensure you reference and update the this.hydrating promise and this.hydrated in both clear() and startHydration() to avoid the overwrite race.components/GroupMemberPicker.tsx-141-156 (1)
141-156:⚠️ Potential issue | 🟡 MinorGuard against invalid
last_seenvalues.
Date.parsecan returnNaN, which currently producesNaNd ago. Add a simple check to fall back toofflinewhen parsing fails.🧪 Proposed fix
const getPresenceLabel = (friend: Friend): string => { const lastSeen = friend.last_seen; if (friend.status === 'online') return 'online'; if (friend.status === 'idle') return 'idle'; if (lastSeen) { - const diff = Date.now() - Date.parse(lastSeen); + const parsed = Date.parse(lastSeen); + if (Number.isNaN(parsed)) return 'offline'; + const diff = Date.now() - parsed; const minutes = Math.floor(diff / 60000); if (minutes < 3) return 'idle'; if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } return 'offline'; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/GroupMemberPicker.tsx` around lines 141 - 156, The getPresenceLabel function currently assumes friend.last_seen parses to a valid date, which can yield NaN and produce "NaNd ago"; update getPresenceLabel (in GroupMemberPicker.tsx) to validate the parsed timestamp: after computing const parsed = Date.parse(lastSeen) check if Number.isNaN(parsed) (or !isFinite(parsed)) and if so return 'offline'; otherwise use parsed for diff calculation and continue the existing minutes/hours/days logic; keep existing status checks for friend.status ('online'/'idle').components/ScheduleMessageSheet.tsx-95-106 (1)
95-106:⚠️ Potential issue | 🟡 MinorHandle Android DateTimePicker dismiss events.
On Android,
onChangefires even when the user dismisses the picker. Checkevent.type === 'dismissed'and close the picker instead of advancing to time mode.🛠️ Proposed fix
- const handleCustomDateChange = (_event: any, date?: Date) => { + const handleCustomDateChange = (event: any, date?: Date) => { if (Platform.OS === 'android') { + if (event?.type === 'dismissed') { + setShowCustomPicker(false); + return; + } if (pickerMode === 'date') { setPickerMode('time'); } else { setShowCustomPicker(false); } } if (date) { setCustomDate(date); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ScheduleMessageSheet.tsx` around lines 95 - 106, The Android DateTimePicker dismiss isn't handled: inside handleCustomDateChange check the incoming _event (use its type property) and if Platform.OS === 'android' and event.type === 'dismissed' then close the picker via setShowCustomPicker(false) (do not advance pickerMode or setCustomDate); otherwise keep the existing logic that advances from 'date' to 'time' and sets setCustomDate(date) when date is present. Ensure you reference pickerMode, setPickerMode, setShowCustomPicker, setCustomDate and Platform.OS in the updated control flow.app/chat/[id].tsx-4722-4726 (1)
4722-4726:⚠️ Potential issue | 🟡 Minor“Off” scheduling path doesn’t close the sheet.
The early return leaves the sheet open despite the inline comment.✅ Suggested fix
if (!scheduledFor) { // User selected "Off" - just close the sheet + setShowScheduleSheet(false); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/chat/`[id].tsx around lines 4722 - 4726, handleScheduleMessage currently returns early when scheduledFor is null but doesn't close the scheduling sheet; update handleScheduleMessage so the "Off" path invokes the component's sheet-close logic (the same function or setter used elsewhere in this file—e.g., closeScheduleSheet(), schedulingSheetRef.current?.close(), or setIsSchedulingOpen(false)) before returning so the sheet is dismissed when the user selects "Off".app/(tabs)/_layout.tsx-49-53 (1)
49-53:⚠️ Potential issue | 🟡 MinorAlign the hook error message with the actual provider.
ChatProviderdoesn’t exist in this file, so the error can mislead during debugging.✅ Suggested fix
- throw new Error('useChatContext must be used within ChatProvider'); + throw new Error('useChatContext must be used within <ChatContext.Provider>');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(tabs)/_layout.tsx around lines 49 - 53, The hook useChatContext throws a misleading error referencing a non-existent ChatProvider; update the thrown message in useChatContext to reference the actual context provider (e.g., "useChatContext must be used within ChatContext.Provider" or the real provider component name used in the codebase) so the error points to the correct symbol (useChatContext / ChatContext) during debugging.app/(tabs)/_layout.tsx-131-145 (1)
131-145:⚠️ Potential issue | 🟡 MinorClear the unread debounce timer on unmount.
If the tab layout unmounts while a timeout is pending, the callback can still fire and update state on an unmounted component.
✅ Suggested fix
+ useEffect(() => { + return () => { + if (unreadTimeoutRef.current) { + clearTimeout(unreadTimeoutRef.current); + } + }; + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(tabs)/_layout.tsx around lines 131 - 145, The debounce timer in loadUnreadSummary uses unreadTimeoutRef but isn't cleared on unmount; add a cleanup effect in the component that checks unreadTimeoutRef.current and calls clearTimeout(unreadTimeoutRef.current) (and nulls the ref) in the returned cleanup function so the scheduled loadUnreadSummary won't run after unmount. Place the useEffect cleanup alongside the other hooks in the same component that defines loadUnreadSummary/unreadTimeoutRef.app/(tabs)/index.tsx-56-69 (1)
56-69:⚠️ Potential issue | 🟡 MinorAwait unread summary refresh to keep errors in the try/catch.
loadUnreadSummary(true)returns a promise; withoutawait, any rejection escapes the handler and skips logging.🔧 Suggested fix
- loadUnreadSummary(true); + await loadUnreadSummary(true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(tabs)/index.tsx around lines 56 - 69, The handler handleMarkChatRead doesn't await loadUnreadSummary(true), so any rejection escapes the try/catch; update handleMarkChatRead to await the promise returned by loadUnreadSummary(true) (i.e., await loadUnreadSummary(true)) inside the existing try block after the ApiService.post call so errors are caught and logged by the catch; ensure you still await ApiService.post and StorageService.getAuthToken as currently implemented.components/CreatePollSheet.tsx-60-72 (1)
60-72:⚠️ Potential issue | 🟡 MinorGuard add/remove against rapid taps to enforce min/max limits.
Using
options.lengthfrom render can be stale if multiple taps land before re-render, letting the list exceed bounds. Prefer checkingprev.lengthinside the updater.🔧 Suggested fix
- const handleAddOption = useCallback(() => { - if (options.length < MAX_OPTIONS) { - setOptions((prev) => [...prev, buildOption()]); - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - } - }, [buildOption, options.length]); + const handleAddOption = useCallback(() => { + let didAdd = false; + setOptions((prev) => { + if (prev.length >= MAX_OPTIONS) return prev; + didAdd = true; + return [...prev, buildOption()]; + }); + if (didAdd) { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + }, [buildOption]); - const handleRemoveOption = useCallback((optionId: string) => { - if (options.length > MIN_OPTIONS) { - setOptions((prev) => prev.filter((option) => option.id !== optionId)); - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - } - }, [options.length]); + const handleRemoveOption = useCallback((optionId: string) => { + let didRemove = false; + setOptions((prev) => { + if (prev.length <= MIN_OPTIONS) return prev; + didRemove = true; + return prev.filter((option) => option.id !== optionId); + }); + if (didRemove) { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/CreatePollSheet.tsx` around lines 60 - 72, The add/remove handlers (handleAddOption, handleRemoveOption) are using options.length from render which can be stale; update them to use the functional updater's previous state: inside setOptions(prev => { if (prev.length >= MAX_OPTIONS) return prev; return [...prev, buildOption()]; }) for add, and for remove do setOptions(prev => { if (prev.length <= MIN_OPTIONS) return prev; return prev.filter(o => o.id !== optionId); }); only call Haptics.impactAsync when the updater actually changes the array so rapid taps cannot bypass min/max limits.
🧹 Nitpick comments (19)
screens/SettingsScreen.tsx (2)
10-20: Remove unused imports.
TextInput(line 12) andApiService(line 20) are imported but not used anywhere in this component.🧹 Suggested cleanup
import { Alert, Linking, ScrollView, StatusBar, StyleSheet, Switch as RNSwitch, Text, - TextInput, TouchableOpacity, View, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { NotificationService } from '../services/NotificationService'; import { StorageService } from '../services/StorageService'; import { UpdateService } from '../services/UpdateService'; -import { ApiService } from '../services/ApiService';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/SettingsScreen.tsx` around lines 10 - 20, The SettingsScreen import block contains unused symbols—remove TextInput from the React Native import list and remove the ApiService import to clean up unused imports; specifically edit the import that currently reads "Switch as RNSwitch, Text, TextInput, TouchableOpacity, View" to drop TextInput and delete the separate "import { ApiService } from '../services/ApiService';" line, then run a quick lint/TypeScript check to ensure no references remain to TextInput or ApiService.
298-301: Unused style definition.
integrationsSectionis defined but never used in the component. Consider removing it.🧹 Remove dead style
- integrationsSection: { - paddingHorizontal: spacing.lg, - paddingBottom: spacing.lg, - },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/SettingsScreen.tsx` around lines 298 - 301, Remove the dead style key integrationsSection from the styles object in SettingsScreen (the StyleSheet used by the SettingsScreen component); locate the styles declaration (variable named styles or StyleSheet.create in SettingsScreen.tsx), delete the integrationsSection property and its values (paddingHorizontal/paddingBottom), and run lint/format to ensure no references remain—if it was meant to be used instead of another style, replace usages to reference the correct style name rather than reintroducing integrationsSection.ios/syncre.xcodeproj/xcshareddata/xcschemes/syncre.xcscheme (1)
18-20: Scheme content updates look consistent, but scheme filename may need renaming.All
BuildableReferenceentries are consistently updated to use the capitalizedSyncrenaming. However, the scheme file itself remainssyncre.xcschemewhile referencingSyncre.appandcontainer:Syncre.xcodeproj. For full consistency, consider renaming the scheme file toSyncre.xcscheme.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/syncre.xcodeproj/xcshareddata/xcschemes/syncre.xcscheme` around lines 18 - 20, The scheme file name should match the capitalized project references; rename the scheme file from syncre.xcscheme to Syncre.xcscheme and update any references to the scheme in project metadata or CI configs accordingly so they point to Syncre.xcscheme; ensure this aligns with the BuildableReference entries like BuildableName "Syncre.app", BlueprintName "Syncre", and ReferencedContainer "container:Syncre.xcodeproj".ios/syncre.xcodeproj/project.pbxproj (1)
1-603: Runpod installafter merging to regenerate Pod support files.The project file references
Pods-Syncredirectories and scripts that need to be regenerated. After merging, ensurepod installis run in theios/directory to create the updated Pod support files matching the newSyncrenaming.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/syncre.xcodeproj/project.pbxproj` around lines 1 - 603, Summary: The Xcode project references Pod-generated files (e.g., Pods-Syncre entries, ExpoModulesProvider.swift, Pods-Syncre-frameworks.sh, Pods-Syncre-resources.sh, expo-configure-project.sh) that must be regenerated; run pod install after merging to update them. Fix: after merging, run pod install in the iOS workspace to regenerate Pod support files (which will update ExpoModulesProvider.swift, Pods-Syncre-*.sh scripts and related resources referenced by the PBX project), verify the generated files (including Manifest.lock/Podfile.lock and the scripts referenced by "Bundle React Native code and images", "[Expo] Configure project", "[CP] Embed Pods Frameworks", and "[CP] Copy Pods Resources") are present and correct, then add and commit those regenerated Pod support files to the branch before pushing.screens/MaintenanceScreen.tsx (1)
70-79: Consider usingradiitoken for border radius.The card styling uses a hardcoded
borderRadius: 20instead of a design system token. For consistency with other components, consider usingradii.lg(which equals 20):Suggested change
+ import { font, palette, radii, spacing } from '../theme/designSystem'; - import { font, palette, spacing } from '../theme/designSystem'; card: { alignSelf: 'center', width: '100%', maxWidth: 420, backgroundColor: 'rgba(255, 255, 255, 0.06)', - borderRadius: 20, + borderRadius: radii.lg, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.1)', padding: spacing.lg, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/MaintenanceScreen.tsx` around lines 70 - 79, The card style object in MaintenanceScreen.tsx uses a hardcoded borderRadius: 20; replace this with the design-system token radii.lg to keep styling consistent—update the card style (the card property in the StyleSheet) to use radii.lg instead of the literal 20 and ensure radii is imported/available in the file where the StyleSheet is defined.ios/syncre/Info.plist (1)
61-62: Inconsistent placeholder usage in permission string.
NSLocationWhenInUseUsageDescriptionuses a hardcoded "Syncre" while other permission strings (lines 55-66) use$(PRODUCT_NAME). Consider using the placeholder for consistency:Suggested change
<key>NSLocationWhenInUseUsageDescription</key> -<string>Syncre uses your approximate location to keep message timestamps accurate.</string> +<string>$(PRODUCT_NAME) uses your approximate location to keep message timestamps accurate.</string>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ios/syncre/Info.plist` around lines 61 - 62, Replace the hardcoded app name in the NSLocationWhenInUseUsageDescription value with the product name placeholder to match other permission strings; locate the Info.plist entry for the key NSLocationWhenInUseUsageDescription and change "Syncre" to $(PRODUCT_NAME) so the permission string uses the same placeholder pattern used by the other permission keys.screens/EditProfileScreen.tsx (2)
221-223: Misleading comment - this code path is used for all platforms.The comment suggests this is Android-specific fallback, but the code below is the only render path and is used for both iOS and Android. Consider updating or removing the comment to avoid confusion.
Suggested change
const displayImage = selectedImage?.uri || profilePicture; - // ═══════════════════════════════════════════════════════════════ - // Android / Fallback: React Native components - // ═══════════════════════════════════════════════════════════════ - const renderSettingItem = (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/EditProfileScreen.tsx` around lines 221 - 223, The comment block labeled "Android / Fallback: React Native components" above the render path in EditProfileScreen.tsx is misleading because that render path is used on all platforms; update or remove that comment to accurately reflect its cross-platform nature—either change the text to something like "Cross-platform: React Native components" or delete the block; locate the comment near the main render return in the EditProfileScreen component (the section demarcated with the ═════════ line markers) and apply the change there.
410-418: Unused style definition.The
avatarSectionstyle is defined but never referenced in the component. Consider removing it to reduce dead code.Suggested removal
- avatarSection: { - marginHorizontal: spacing.lg, - marginBottom: spacing.md, - backgroundColor: 'rgba(255, 255, 255, 0.04)', - borderRadius: radii.xl, - borderWidth: 1, - borderColor: 'rgba(255, 255, 255, 0.08)', - overflow: 'hidden', - }, section: {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/EditProfileScreen.tsx` around lines 410 - 418, Remove the dead style object "avatarSection" from the styles in EditProfileScreen.tsx: locate the styles definition containing avatarSection and delete that property (and any trailing commas) since it's never referenced in the component; if you intend to use it later, instead add a TODO comment and reference it from the appropriate component (e.g., <Avatar /> container) to avoid unused-code warnings.app/wrap/[date].tsx (1)
353-358: Consider usingradiitoken for consistency.The
borderRadius: 20is hardcoded here, while other GlassCard replacements in this PR useradii.xlorradii.lgfrom the design system. Using the token would maintain consistency.Suggested change
card: { width: '100%', maxWidth: layout.maxContentWidth, gap: spacing.md, backgroundColor: 'rgba(255, 255, 255, 0.06)', - borderRadius: 20, + borderRadius: radii.lg, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.1)', padding: spacing.md, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/wrap/`[date].tsx around lines 353 - 358, The hardcoded borderRadius: 20 in the style object (the block containing backgroundColor, borderWidth, borderColor, padding) should be replaced with the design-system token (e.g., radii.xl or radii.lg) for consistency with other GlassCard replacements; locate the style object in app/wrap/[date].tsx (the object that also defines backgroundColor and padding) and swap borderRadius: 20 for the appropriate token (radii.xl or radii.lg) and ensure the radii token is imported where needed.app/_layout.tsx (1)
105-115: Consider removing or gating debug console.log statements for production.These
console.logstatements are useful during development but may clutter production logs and expose internal routing decisions. Consider using a debug utility or environment-gated logging.♻️ Suggested approach
+const DEBUG = __DEV__; +const log = (...args: any[]) => DEBUG && console.log(...args); + const responseSub = Notifications.addNotificationResponseReceivedListener((response) => { - console.log('📱 Notification tap received:', JSON.stringify(response?.notification?.request?.content?.data)); + log('📱 Notification tap received:', JSON.stringify(response?.notification?.request?.content?.data)); const chatId = extractChatIdFromNotification(response); const wrapDate = extractWrapDateFromNotification(response); - console.log('📱 Extracted - chatId:', chatId, 'wrapDate:', wrapDate); + log('📱 Extracted - chatId:', chatId, 'wrapDate:', wrapDate); if (wrapDate) { - console.log('📱 Navigating to wrap:', `/wrap/${wrapDate}`); + log('📱 Navigating to wrap:', `/wrap/${wrapDate}`); router.push(`/wrap/${wrapDate}` as any); } else if (chatId) { - console.log('📱 Navigating to chat:', `/chat/${chatId}`); + log('📱 Navigating to chat:', `/chat/${chatId}`); router.push(`/chat/${chatId}`); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/_layout.tsx` around lines 105 - 115, Remove or gate the console.log debug output in the notification tap handler: replace the direct console.log calls around extractChatIdFromNotification, extractWrapDateFromNotification and the router.push branches with a controlled logger (e.g., debug/info) or wrap them behind an environment check (NODE_ENV !== 'production' or a DEBUG flag). Ensure logs still preserve the same messages when enabled but are suppressed in production so routing decisions (router.push(`/wrap/${wrapDate}`) and router.push(`/chat/${chatId}`)) remain unchanged except for logging.utils/pollPayload.ts (1)
13-44: Consider extracting shared validation logic to reduce duplication.Both
decodePollPayloadanddecodePollPayloadFromJsonshare identical validation and object construction logic (lines 18-24 and 34-40). Extracting a shared validator would improve maintainability.♻️ Proposed refactor
+const validateAndParsePollPayload = (parsed: any): PollPayload | null => { + if (!parsed || typeof parsed.question !== 'string' || !Array.isArray(parsed.options)) { + return null; + } + return { + question: parsed.question, + options: parsed.options.map((opt: any) => String(opt)), + }; +}; + export const decodePollPayload = (encoded: string): PollPayload | null => { if (!encoded) return null; try { const json = Buffer.from(encoded, 'base64').toString('utf8'); const parsed = JSON.parse(json); - if (!parsed || typeof parsed.question !== 'string' || !Array.isArray(parsed.options)) { - return null; - } - return { - question: parsed.question, - options: parsed.options.map((opt: any) => String(opt)), - }; + return validateAndParsePollPayload(parsed); } catch (error) { return null; } }; export const decodePollPayloadFromJson = (raw: string): PollPayload | null => { if (!raw) return null; try { const parsed = JSON.parse(raw); - if (!parsed || typeof parsed.question !== 'string' || !Array.isArray(parsed.options)) { - return null; - } - return { - question: parsed.question, - options: parsed.options.map((opt: any) => String(opt)), - }; + return validateAndParsePollPayload(parsed); } catch (error) { return null; } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@utils/pollPayload.ts` around lines 13 - 44, Both decodePollPayload and decodePollPayloadFromJson duplicate the same validation/construct logic; extract that shared logic into a single helper (e.g., parsePollPayload or validateAndBuildPollPayload) that accepts a parsed object and returns PollPayload | null after checking typeof question === 'string' and Array.isArray(options') and mapping options to String; then have decodePollPayload decode base64 to JSON and call the helper, and have decodePollPayloadFromJson JSON.parse the raw string and call the same helper to eliminate duplication while preserving current behavior and error handling.screens/VerifyScreen.tsx (1)
229-238: Consider using theme tokens for the hardcoded card colors.The
backgroundColorandborderColoruse hardcoded rgba values. For consistency with the design system and easier theme adjustments, consider extracting these topaletteor a shared card style.♻️ Example using theme tokens
card: { width: '100%', maxWidth: 420, alignSelf: 'center', padding: spacing.lg, - backgroundColor: 'rgba(255, 255, 255, 0.04)', + backgroundColor: palette.surface, // or add a new token like palette.cardBackground borderRadius: radii.xl, borderWidth: 1, - borderColor: 'rgba(255, 255, 255, 0.08)', + borderColor: palette.border, // or a new token },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@screens/VerifyScreen.tsx` around lines 229 - 238, The card style in VerifyScreen (the `card` object) uses hardcoded rgba values for `backgroundColor` and `borderColor`; replace these with theme tokens (e.g., from your `palette` or shared `card` style) so colors come from the design system. Update the `card` style to reference the appropriate tokens (like `palette.cardBackground` / `palette.cardBorder` or a shared `styles.card` token) and ensure the VerifyScreen imports the theme/palette or shared style module and uses those tokens instead of the rgba literals.components/SpotifyConnection.tsx (1)
147-162: Consider gating debug console.log statements for production.Similar to
app/_layout.tsx, these debug logs are helpful during development but should be gated to avoid cluttering production logs.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/SpotifyConnection.tsx` around lines 147 - 162, The debug console.log calls around the OAuth flow (the logs before/after calling WebBrowser.openAuthSessionAsync, the log of result.type, and the Success URL log when checking result.type === 'success' in SpotifyConnection.tsx) should be gated for non-production; wrap these console.log statements behind a development-only flag (e.g. __DEV__ or an existing environment/logger utility) or replace them with a logger that respects log-levels so they don't run in production. Locate the block that calls WebBrowser.openAuthSessionAsync and the subsequent `console.log('[Spotify] Auth session result:'...` and `console.log('[Spotify] Success URL:'...)` and update them to be conditional on the dev flag or routed through the centralized logger used elsewhere (matching the pattern used in app/_layout.tsx).components/GlassCard.tsx (1)
17-17: Unused import:paddingmodifier is imported but never used.The
paddingmodifier is imported from@expo/ui/swift-ui/modifiers(line 27) but is not used anywhere in the component. The padding is applied via React Native'sstyleprop instead.♻️ Remove unused import
if (isIOS) { try { const swiftUI = require('@expo/ui/swift-ui'); SwiftUIHost = swiftUI.Host; SwiftUIRoundedRectangle = swiftUI.RoundedRectangle; GlassEffectContainer = swiftUI.GlassEffectContainer; const modifiers = require('@expo/ui/swift-ui/modifiers'); glassEffect = modifiers.glassEffect; - padding = modifiers.padding; } catch (e) { console.warn('SwiftUI components not available:', e); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/GlassCard.tsx` at line 17, Remove the unused padding modifier: delete the unused declaration "let padding: any = null;" and remove the unused imported "padding" from the modifiers import in GlassCard (the import from `@expo/ui/swift-ui/modifiers` and the local variable), ensuring padding is only applied via the component's style prop and no references to the padding symbol remain in GlassCard.tsx.app/chat/[id].tsx (1)
641-642: BounddecryptionCacheto avoid unbounded memory growth.
A size cap (or chat-scoped eviction) prevents long-lived sessions from accumulating decrypted payloads indefinitely.♻️ Suggested size cap for the decryption cache
-const decryptionCache = new Map<string, string>(); +const DECRYPTION_CACHE_LIMIT = 500; +const decryptionCache = new Map<string, string>();- if (decrypted) { - content = decrypted; - decryptionCache.set(cacheKey, decrypted); - } else { + if (decrypted) { + content = decrypted; + if (decryptionCache.size >= DECRYPTION_CACHE_LIMIT) { + const [oldestKey] = decryptionCache.keys(); + if (oldestKey) { + decryptionCache.delete(oldestKey); + } + } + decryptionCache.set(cacheKey, decrypted); + } else {Also applies to: 2378-2409
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/chat/`[id].tsx around lines 641 - 642, The global decryptionCache is unbounded; replace it with a bounded cache (e.g., an LRU) or make it chat-scoped and evict on chat change to prevent memory growth: implement a small LRUMap wrapper (or use an existing LRU util) and instantiate it instead of new Map in the declaration referenced by decryptionCache, enforce a reasonable maxEntries (e.g., 250–1000) and evict the oldest entry on put when size > maxEntries, or alternatively tie the cache lifecycle to the current chat id (clear or recreate decryptionCache when the chat id changes) so decrypted payloads don’t accumulate indefinitely.app.json (1)
9-11: Verify New Architecture compatibility before release.Enabling
newArchEnabled: truecan break native modules that aren’t compatible yet. Please validate builds and runtime on both platforms with your current dependency set.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app.json` around lines 9 - 11, The PR enables the new React Native architecture via the JSON key newArchEnabled which may break native modules; validate by building and running the app on both iOS and Android with newArchEnabled set to true and false, exercise features that use native modules (modules referenced by userInterfaceStyle and jsEngine changes may be impacted), and if any native dependency fails, either revert newArchEnabled to false in app.json or update/patch the incompatible native modules before merging; report which platform(s), build errors or runtime crashes you observed and include reproduction steps and the specific failing module names.components/PollMessage.tsx (2)
30-44: Consider typing the new encrypted payload fields instead ofany.These fields are part of a public interface;
anyremoves type safety and makes integration harder. If you already have payload/backup-envelope types (e.g., in poll payload utilities), wire them in here to catch mismatches at compile time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/PollMessage.tsx` around lines 30 - 44, The PollData interface currently uses `any` for encryptedPayload and backupEnvelopes; replace those with concrete types (e.g., the encrypted poll payload and envelope types used elsewhere) to regain type safety: import the appropriate types from the poll payload utilities (or the module that defines poll payload/envelope types) and update `encryptedPayload?: <EncryptedPollPayloadType>` and `backupEnvelopes?: <BackupEnvelopeType[]>` (and tighten `payloadVersion?: number`/`senderDeviceId?: string | null` if needed) in the PollData declaration so the compiler enforces structure instead of `any`.
115-148: AvoidindexOfinsidemapfor fallback option labels.Using
indexOfmakes this O(n²) and relies on reference equality. Prefer the map index instead.♻️ Suggested change
- {poll.options.map((option) => { + {poll.options.map((option, index) => { const optionLabel = typeof option.text === 'string' && option.text.trim().length > 0 ? option.text - : `Option ${option.id ?? poll.options.indexOf(option) + 1}`; + : `Option ${option.id ?? index + 1}`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/PollMessage.tsx` around lines 115 - 148, The fallback label computation for optionLabel currently uses poll.options.indexOf(option) which is O(n²) and relies on reference equality; inside the map that renders options (where optionLabel, isVoted, voteCount, percentage, voters are computed) replace the indexOf usage by using the map callback's index parameter to compute the fallback: use `Option ${option.id ?? (index + 1)}` (ensure you capture the index in the map signature). Update the occurrence in the optionLabel expression so it no longer calls poll.options.indexOf(option)..github/workflows/android-build.yml (1)
41-49: Gradle cache key should include nested Gradle files.Current patterns don’t match
android/app/build.gradle, so cache keys won’t update when app Gradle changes.♻️ Suggested change
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/Mobile/android/*.gradle*', '**/Mobile/android/gradle-wrapper.properties') }} + key: ${{ runner.os }}-gradle-${{ hashFiles('**/Mobile/android/**/*.gradle*', '**/Mobile/android/**/gradle-wrapper.properties') }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/android-build.yml around lines 41 - 49, The "Cache Gradle" step's key currently hashes only top-level patterns and misses nested files like android/app/build.gradle; update the key expression used in that step (the key: ${{ runner.os }}-gradle-${{ hashFiles(...) }}) to include recursive patterns such as '**/Mobile/android/**/*.gradle*' and '**/Mobile/android/**/gradle-wrapper.properties' (or alternatively '**/android/**/*.gradle*' if repo layout varies) so changes to nested Gradle files update the cache key and invalidate the cache as expected.
| const [showEphemeralSheet, setShowEphemeralSheet] = useState(false); | ||
| const [isCreatingPoll, setIsCreatingPoll] = useState(false); | ||
| const [pollsData, setPollsData] = useState<Map<string, { poll: PollData; userVotes: number[] }>>(new Map()); | ||
| const pollDecryptAttemptedRef = useRef<Set<string>>(new Set()); |
There was a problem hiding this comment.
Poll decryption can get stuck after a failed attempt.
The attempt key omits chatId and is recorded before a successful decrypt/parse, so a failed attempt (or another chat with the same messageId) can permanently block retries—even after re-encrypt.
🛠️ Suggested fix: scope the key to chatId and mark only on success
- const attemptKey = `${messageId}:${poll.payloadVersion || 1}:${Array.isArray(poll.encryptedPayload) ? poll.encryptedPayload.length : 0}`;
- if (pollDecryptAttemptedRef.current.has(attemptKey)) {
- return;
- }
- pollDecryptAttemptedRef.current.add(attemptKey);
+ const attemptKey = `${chatId}:${messageId}:${poll.payloadVersion || 1}:${
+ Array.isArray(poll.encryptedPayload) ? poll.encryptedPayload.length : 0
+ }`;
+ if (pollDecryptAttemptedRef.current.has(attemptKey)) {
+ return;
+ }
...
- if (!decoded) {
- return;
- }
+ if (!decoded) {
+ return;
+ }
+ pollDecryptAttemptedRef.current.add(attemptKey);If you still want to avoid tight retry loops, consider a short cooldown or clearing the set when new envelopes are appended.
Also applies to: 4999-5017, 5073-5078
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/chat/`[id].tsx at line 1731, The poll decryption retry logic uses
pollDecryptAttemptedRef (a Set of keys) but builds keys without chatId and marks
attempts before successful decrypt/parse, causing permanent blocks across chats
or on failures; change the key to include chatId (e.g.,
`${chatId}:${messageId}`) and only add the key to pollDecryptAttemptedRef after
a successful decrypt/parse in the decrypt/parse success path (not before), and
optionally implement a short cooldown or clear the set when new envelopes are
appended to avoid permanent blocks.
This pull request introduces significant updates to the Syncre mobile project, focusing on a major feature and documentation update, build automation for Android releases, and several improvements to configuration and branding. The changes include a comprehensive rewrite and expansion of the
README.md, the addition of a GitHub Actions workflow for automated Android APK builds and releases, updates to the app version and branding, and enhancements to the app's configuration for modern Android and Expo features.The most important changes are:
1. Documentation and Developer Experience
README.mdto include detailed feature descriptions, updated technology stack, API integration guides, security architecture, project structure, development instructions, and contribution guidelines. This makes onboarding and development much easier for new contributors. [1] [2]2. Build and Release Automation
.github/workflows/android-build.yml) to automate Android APK building, signing (with dynamic keystore generation), and release uploading to GitHub on pushes tomainthat affect theMobiledirectory.3. App Versioning and Branding
1.1.4to2.0.3in bothapp.jsonandandroid/app/build.gradle, and changed the app name fromsyncretoSyncreacross configuration files for consistent branding. [1] [2] [3] [4]4. Configuration and Feature Enhancements
newArchEnabled: true) and set the user interface style to dark mode by default in bothapp.jsonandandroid/gradle.properties. [1] [2]expo-secure-store,expo-localization,@react-native-community/datetimepicker) for richer functionality. [1] [2]5. Cleanup and Minor Changes
android/app/src/main/res/xml/file_paths.xmlfile.android/app/src/main/res/values/strings.xmlto match new branding and dark mode preference.These changes collectively modernize the Syncre mobile project, improve its developer experience, and automate the release process for Android.
Summary by CodeRabbit
New Features
Improvements
Updates