-
Notifications
You must be signed in to change notification settings - Fork 5
Develop #77
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
Conversation
WalkthroughReplaces many FlatList usages with FlashList, adds a Jest FlashList mock, enhances dispatch store fetch/categorization with a refresh API, localizes NotificationInbox with new translation keys and docs, bumps dependencies, tweaks CI to include Countly secrets, and applies miscellaneous UI and editor setting changes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Modal as DispatchSelectionModal
participant Store as Dispatch Store
participant API as Recipients API
User->>Modal: open()
activate Modal
Modal->>Store: fetchDispatchData()
activate Store
Store->>API: GET /recipients
API-->>Store: recipients data / error
alt success
Store->>Store: validate & categorize (users, groups, roles, units)
Store-->>Modal: data (loading:false, error:null)
Modal->>Modal: derive filtered lists (useMemo)
Modal-->>User: render recipients / empty / no-results
else error/invalid
Store-->>Modal: set error state
Modal-->>User: show error + Retry
User->>Modal: Retry
Modal->>Store: refreshDispatchData()
Store->>API: GET /recipients (retry)
end
deactivate Store
deactivate Modal
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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. 🧪 Early access (Sonnet 4.5): enabledWe are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience. Note:
Comment |
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/components/notifications/NotificationInbox.tsx (1)
232-233: Localize all user-facing strings with t()Several literals bypass i18n. Wrap with useTranslation to meet guidelines and l10n goals.
Example minimal changes:
- const renderEmpty = () => ( + const { t } = useTranslation(); + const renderEmpty = () => ( <View style={styles.emptyContainer}> - <Text>No updates available</Text> + <Text>{t('notifications.empty', 'No updates available')}</Text> </View> );Header/actions and modal:
- <Text style={styles.headerTitle}>Notifications</Text> + <Text style={styles.headerTitle}>{t('notifications.title', 'Notifications')}</Text> - <Text>Unable to load notifications</Text> + <Text>{t('notifications.unableToLoad', 'Unable to load notifications')}</Text> - <Text className="text-lg font-semibold">Confirm Delete</Text> + <Text className="text-lg font-semibold">{t('notifications.confirmDeleteTitle', 'Confirm Delete')}</Text> - Are you sure you want to delete {selectedNotificationIds.size} notification{s}? This action cannot be undone. + {t('notifications.confirmDeleteBody', { + defaultValue: 'Are you sure you want to delete {{count}} notification{{suffix}}? This action cannot be undone.', + count: selectedNotificationIds.size, + suffix: selectedNotificationIds.size > 1 ? 's' : '', + })} - <Text>Cancel</Text> + <Text>{t('common.cancel', 'Cancel')}</Text> - <Text className="text-white">Delete</Text> + <Text className="text-white">{t('common.delete', 'Delete')}</Text>I can open a follow-up PR adding these keys to the locale files.
Also applies to: 279-281, 298-299, 323-329, 332-336
src/app/(app)/home/personnel.tsx (1)
49-58: Null-safety: guard toLowerCase() on optional fieldsDirect calls can throw if any field is null/undefined (e.g., FirstName, LastName, Roles entries).
- return personnel.filter( - (person) => - person.FirstName.toLowerCase().includes(query) || - person.LastName.toLowerCase().includes(query) || - person.EmailAddress?.toLowerCase().includes(query) || - person.GroupName?.toLowerCase().includes(query) || - person.Status?.toLowerCase().includes(query) || - person.Staffing?.toLowerCase().includes(query) || - person.IdentificationNumber?.toLowerCase().includes(query) || - person.Roles?.some((role) => role.toLowerCase().includes(query)) - ); + return personnel.filter((person) => + (person.FirstName?.toLowerCase().includes(query) ?? false) || + (person.LastName?.toLowerCase().includes(query) ?? false) || + (person.EmailAddress?.toLowerCase().includes(query) ?? false) || + (person.GroupName?.toLowerCase().includes(query) ?? false) || + (person.Status?.toLowerCase().includes(query) ?? false) || + (person.Staffing?.toLowerCase().includes(query) ?? false) || + (person.IdentificationNumber?.toLowerCase().includes(query) ?? false) || + (person.Roles?.some((role) => role?.toLowerCase().includes(query)) ?? false) + );src/app/(app)/home/units.tsx (1)
55-60: Null-safety: guard toLowerCase() on optional fieldsProtect against undefined values to avoid crashes on malformed data.
- return units.filter( - (unit) => - unit.Name.toLowerCase().includes(query) || - unit.Type.toLowerCase().includes(query) || - unit.PlateNumber?.toLowerCase().includes(query) || - unit.Vin?.toLowerCase().includes(query) || - unit.GroupName?.toLowerCase().includes(query) - ); + return units.filter((unit) => + (unit.Name?.toLowerCase().includes(query) ?? false) || + (unit.Type?.toLowerCase().includes(query) ?? false) || + (unit.PlateNumber?.toLowerCase().includes(query) ?? false) || + (unit.Vin?.toLowerCase().includes(query) ?? false) || + (unit.GroupName?.toLowerCase().includes(query) ?? false) + );
🧹 Nitpick comments (34)
src/api/messaging/messages.ts (3)
16-17: Remove commented-out duplicate and use a config toggle instead.Leaving an alternate
recipientsApideclaration commented out adds noise and invites accidental shadowing if uncommented. Prefer a single declaration with a config-driven switch.Example diff to remove the commented code and make caching toggleable:
-//const recipientsApi = createApiEndpoint('/Messages/GetRecipients'); +// To disable caching for troubleshooting, flip this flag via env/config instead of swapping implementations. +const CACHE_RECIPIENTS_ENABLED = true; // e.g., Boolean(process.env.EXPO_PUBLIC_CACHE_RECIPIENTS)
11-14: Confirm 1‑day TTL is acceptable for recipients freshness.A 24h cache can stale membership/permissions after roster/unit changes. If freshness is critical, consider a shorter TTL (e.g., 5–15m) or explicit invalidation after mutating actions (user joins/leaves unit, role changes).
8-14: Align caching with react-query where possible.Guidelines call for react-query for data fetching/caching. If feasible, expose this endpoint as a typed query (with staleTime, cache keys, and invalidation) and keep this API layer thin. This centralizes cache policy and avoids bespoke TTL logic.
As per coding guidelines
src/translations/en.json (1)
355-356: Approve additions; correct verification script and confirm usage.
- Remove the erroneous
searchkey from the locale‐check and adjust the ripgrep call to include.tsx(e.g. drop--typeflags or add--type-add 'tsx:*.tsx').- Run a broader search such as
to locate UI references and ensure the new keys are actually consumed.rg -n "no_data_available|no_results_found" -C1.vscode/settings.json (3)
40-45: Unify formatter configuration to avoid churn on save (ESLint vs Prettier vs TS extension).You’re setting ESLint as the formatter for JS/TS/TSX (lines 11-18) but overriding TS/TSX to different formatters here. This often causes inconsistent edits or flicker on save. Suggest picking one strategy.
Option A (common): Prettier formats; ESLint fixes via code actions.
- Set defaultFormatter to Prettier for JS/TS/TSX.
- Keep "source.fixAll.eslint" on save.
- Disable ESLint-as-formatter.
Apply these diffs:
- "[javascript][typescript][typescriptreact]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.codeActionsOnSave": [ - "source.addMissingImports", - "source.fixAll.eslint" - ] - }, + "[javascript][typescript][typescriptreact]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": [ + "source.addMissingImports", + "source.fixAll.eslint" + ] + },- "eslint.format.enable": true, + "eslint.format.enable": false,- "[typescriptreact]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + },Option B: ESLint formats everything (if using eslint-plugin-prettier).
- Keep ESLint as default formatter for JS/TS/TSX and remove the TS/TSX overrides above.
- Ensure prettier rules flow through ESLint.
Pick one path to prevent formatter tug-of-war.Also applies to: 11-18
46-65: Workspace theme overrides may be intrusive; confirm team intent.Adding workbench.colorCustomizations forces UI colors for all contributors. If that’s intentional (branding/Peacock workflow), fine. Otherwise consider relying on Peacock’s single color or moving these to user settings to avoid overriding personal themes.
If you prefer to keep just Peacock:
- "workbench.colorCustomizations": { - "activityBar.activeBackground": "#8804a5", - "activityBar.background": "#8804a5", - "activityBar.foreground": "#e7e7e7", - "activityBar.inactiveForeground": "#e7e7e799", - "activityBarBadge.background": "#9c8004", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#e7e7e799", - "sash.hoverBorder": "#8804a5", - "statusBar.background": "#5f0373", - "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#8804a5", - "statusBarItem.remoteBackground": "#5f0373", - "statusBarItem.remoteForeground": "#e7e7e7", - "titleBar.activeBackground": "#5f0373", - "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveBackground": "#5f037399", - "titleBar.inactiveForeground": "#e7e7e799" - }, + // Keep Peacock for easy workspace identification; avoid overriding contributors' themes.
27-33: cSpell: add “FlashList” proper case to avoid flags.You have “Flashlist”; adding “FlashList” ensures both variants are whitelisted and avoids noise during the migration.
"cSpell.words": [ - "Flashlist", + "Flashlist", + "FlashList", "Gluestack", "Lato", "nativewind", "Resgrid" ],src/api/calls/calls.ts (2)
81-104: DRY up duplicate dispatch list construction
createCallandupdateCallduplicate the dispatch list logic. Extract a small helper to reduce drift and ease testing.Example helper (placed in this module or a small util):
function computeDispatchList(input: { dispatchEveryone?: boolean; dispatchUsers?: string[]; dispatchGroups?: string[]; dispatchRoles?: string[]; dispatchUnits?: string[]; }): string { if (input.dispatchEveryone) return '0'; const entries: string[] = []; if (input.dispatchUsers?.length) entries.push(...input.dispatchUsers.map(u => `U:${u}`)); if (input.dispatchGroups?.length) entries.push(...input.dispatchGroups.map(g => `G:${g}`)); if (input.dispatchRoles?.length) entries.push(...input.dispatchRoles.map(r => `R:${r}`)); if (input.dispatchUnits?.length) entries.push(...input.dispatchUnits.map(unit => `U:${unit}`)); // verify prefix per API return entries.join('|'); }Then in both functions:
const dispatchList = computeDispatchList(callData);Please verify the
'U:'prefix for both users and units is intentional per API.Also applies to: 124-147
21-23: Use explicit return types and property shorthand; axios.get params handles encodingAxios
api.getwraps your{ callId }in itsparamsoption, which is URL-encoded by default—no manualencodeURIComponentneeded. Refactor exported functions:-export const getCallExtraData = async (callId: string) => { +export const getCallExtraData = async (callId: string): Promise<CallExtraDataResult> => { const response = await getCallExtraDataApi.get<CallExtraDataResult>({ - callId: callId, + callId, }); return response.data; }; -export const getCall = async (callId: string) => { +export const getCall = async (callId: string): Promise<CallResult> => { const response = await getCallApi.get<CallResult>({ - callId: callId, + callId, }); return response.data; };src/components/ui/bottomsheet/index.tsx (1)
190-195: Prefer ternary over && for conditional renderingAlign with repo guideline to use ?: instead of && in TSX. Apply:
- {visible && ( - <FocusScope contain={visible} autoFocus={true} restoreFocus={true}> - {props.children} - </FocusScope> - )} + {visible ? ( + <FocusScope contain={visible} autoFocus={true} restoreFocus={true}> + {props.children} + </FocusScope> + ) : null}As per coding guidelines
src/app/(app)/contacts.tsx (1)
84-92: Stabilize renderItem to reduce re-renders with FlashListAvoid inline functions in renderItem; hoist into a memoized callback.
Apply within the list:
- renderItem={({ item }) => <ContactCard contact={item} onPress={selectContact} />} + renderItem={renderContactItem}Add near other hooks:
const renderContactItem = React.useCallback( ({ item }: { item: typeof filteredContacts[number] }) => ( <ContactCard contact={item} onPress={selectContact} /> ), [selectContact] );jest-setup.ts (1)
683-694: Harden FlashList Jest mock exportsExpose __esModule and default to avoid ESM/CJS interop hiccups and future imports.
-jest.mock('@shopify/flash-list', () => { +jest.mock('@shopify/flash-list', () => { const React = require('react'); const { FlatList } = require('react-native'); - return { - FlashList: React.forwardRef((props: any, ref: any) => { - return React.createElement(FlatList, { ...props, ref }); - }), - }; -}); + const FlashList = React.forwardRef((props, ref) => React.createElement(FlatList, { ...props, ref })); + return { + __esModule: true, + FlashList, + default: FlashList, + }; +});src/components/notifications/NotificationInbox.tsx (2)
301-311: Type FlashList and memoize renderItemProvide list generics and stable renderItem to minimize re-renders and improve DX.
- <FlashList - data={notifications} - renderItem={renderItem} - keyExtractor={(item: any) => item.id} + <FlashList<NotificationPayload> + data={notifications as unknown as NotificationPayload[]} + renderItem={renderItem} + keyExtractor={(item) => item.id}Also wrap renderItem in useCallback:
- const renderItem = ({ item }: { item: any }) => { + const renderItem = React.useCallback(({ item }: { item: any }) => { // ... - }; + }, [isSelectionMode, selectedNotificationIds]);
359-366: Avoid baking color scheme into static StyleSheetcolorScheme.get() runs once; styles won’t update on theme change. Prefer dynamic values from useColorScheme() or tailwind classes.
Light-touch option:
const isDark = colorScheme.get() === 'dark'; // or nativewind/useColorScheme()Then move affected values to inline style arrays or conditional className (e.g., className="bg-white dark:bg-black"). This keeps the sidebar responsive to runtime theme toggles.
Also applies to: 417-425, 433-433, 445-445, 449-449, 452-452
src/app/(app)/home/personnel.tsx (1)
93-101: Hoist renderItem into a memoized callbackAvoid anonymous functions in renderItem; improves stability and perf with FlashList.
- <FlashList + <FlashList data={filteredPersonnel} keyExtractor={(item, index) => item.UserId || `personnel-${index}`} - renderItem={({ item }) => <PersonnelCard personnel={item} onPress={selectPersonnel} />} + renderItem={renderPersonnelItem}Add near hooks:
const renderPersonnelItem = React.useCallback( ({ item }: { item: typeof filteredPersonnel[number] }) => ( <PersonnelCard personnel={item} onPress={selectPersonnel} /> ), [selectPersonnel] );src/app/(app)/home/units.tsx (1)
95-102: Hoist renderItem into a memoized callbackAvoid anonymous functions in renderItem; stabilize identity for FlashList.
- <FlashList + <FlashList data={filteredUnits} keyExtractor={(item, index) => item.UnitId || `unit-${index}`} - renderItem={({ item }) => <UnitCard unit={item as any} onPress={selectUnit} />} + renderItem={renderUnitItem}Add near hooks:
const renderUnitItem = React.useCallback( ({ item }: { item: typeof filteredUnits[number] }) => <UnitCard unit={item as any} onPress={selectUnit} />, [selectUnit] );src/components/ui/actionsheet/index.tsx (1)
59-59: Map FlatList to FlashList with a web-safe fallback.Since this Actionsheet is used across platforms and this file has explicit web handling, guard FlashList on web.
- FlatList: FlashList, + FlatList: Platform.OS === 'web' ? VirtualizedList : FlashList,Also sanity‑check that cssInterop keys used for FlatList (e.g., columnWrapperClassName, indicatorClassName) are honored by FlashList in your target versions.
src/app/(app)/protocols.tsx (1)
77-85: Avoid inline renderItem/keyExtractor to reduce unnecessary re-renders.Extract and memoize the callbacks.
@@ - {filteredProtocols.length > 0 ? ( - <FlashList + {filteredProtocols.length > 0 ? ( + <FlashList testID="protocols-list" - data={filteredProtocols} - keyExtractor={(item, index) => item.Id || `protocol-${index}`} - renderItem={({ item }) => <ProtocolCard protocol={item} onPress={selectProtocol} />} + data={filteredProtocols} + keyExtractor={keyExtractor} + renderItem={renderItem} showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 100 }} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />} /> ) : (Add near the filtered list computation:
+ const keyExtractor = React.useCallback((item: { Id?: string }, index: number) => item.Id ?? `protocol-${index}`, []); + const renderItem = React.useCallback( + ({ item }: { item: any }) => <ProtocolCard protocol={item} onPress={selectProtocol} />, + [selectProtocol] + );src/app/onboarding.tsx (2)
143-156: Add overrideItemLayout and memoize item callbacks for smoother paging and stable scrollToIndex.With uniform, full‑width pages, provide the size and avoid inline callbacks.
@@ - <FlashList + <FlashList ref={flatListRef} data={onboardingData} - renderItem={({ item }: { item: OnboardingItemProps }) => <OnboardingItem {...item} />} + renderItem={renderItem} horizontal showsHorizontalScrollIndicator={false} pagingEnabled bounces={false} - keyExtractor={(item: OnboardingItemProps) => item.title} + keyExtractor={keyExtractor} onScroll={handleScroll} scrollEventThrottle={16} testID="onboarding-flatlist" + overrideItemLayout={(layout) => { + layout.size = width; // width for horizontal lists + layout.span = 1; + }} />Add above the return:
+ const renderItem = React.useCallback( + ({ item }: { item: OnboardingItemProps }) => <OnboardingItem {...item} />, + [] + ); + const keyExtractor = React.useCallback((item: OnboardingItemProps) => item.title, []);
175-181: Localize visible strings.Wrap “Skip”, “Next”, and “Let’s Get Started” with t(...) per guidelines.
Also applies to: 203-204
src/components/ui/flat-list/index.tsx (1)
2-2: Also re-export the types to preserve the public typing surface.Downstream code expecting FlatListProps/Ref will benefit from aliased type exports.
-'use client'; -export { FlashList as FlatList } from '@shopify/flash-list'; +'use client'; +export { FlashList as FlatList } from '@shopify/flash-list'; +export type { + FlashListProps as FlatListProps, + FlashListRef as FlatListRef, + ListRenderItem, + ListRenderItemInfo, +} from '@shopify/flash-list';src/app/(app)/notes.tsx (1)
111-118: Extract renderItem/keyExtractor to stable callbacks.Reduces re-renders and keeps item identities stable.
- <FlashList - data={filteredNotes} - keyExtractor={(item) => item.NoteId} - renderItem={({ item }) => <NoteCard note={item} onPress={handleNoteSelect} />} + <FlashList + data={filteredNotes} + keyExtractor={keyExtractor} + renderItem={renderItem} showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 100 }} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />} />Add above the return:
+ const keyExtractor = React.useCallback((item: { NoteId: string }) => item.NoteId, []); + const renderItem = React.useCallback( + ({ item }: { item: any }) => <NoteCard note={item} onPress={handleNoteSelect} />, + [handleNoteSelect] + );src/components/calls/call-images-modal.tsx (3)
270-275: Prefer FlashList’s onVisibleIndicesChanged over onViewableItemsChangedFlashList exposes onVisibleIndicesChanged for lighter, reliable index tracking. It also avoids viewabilityConfig complexity. Update to keep activeIndex in sync.
- const handleViewableItemsChanged = useRef(({ viewableItems }: any) => { - if (viewableItems.length > 0) { - setActiveIndex(viewableItems[0].index || 0); - } - }).current; + const onVisibleIndicesChanged = useCallback((indices: number[]) => { + if (indices.length > 0) { + setActiveIndex(indices[0]); + } + }, []); @@ - onViewableItemsChanged={handleViewableItemsChanged} - viewabilityConfig={{ - itemVisiblePercentThreshold: 50, - minimumViewTime: 100, - }} + onVisibleIndicesChanged={onVisibleIndicesChanged}Also applies to: 398-402
36-36: Make snap width responsive to orientationDimensions.get('window') is static here. Use useWindowDimensions so snapToInterval tracks rotation/resizes.
-import { Alert, Dimensions, type ImageSourcePropType, Platform, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { Alert, useWindowDimensions, type ImageSourcePropType, Platform, StyleSheet, TouchableOpacity, View } from 'react-native'; @@ -const { width } = Dimensions.get('window'); +// remove this line; use the hook inside the component @@ -const CallImagesModal: React.FC<CallImagesModalProps> = ({ isOpen, onClose, callId }) => { +const CallImagesModal: React.FC<CallImagesModalProps> = ({ isOpen, onClose, callId }) => { + const { width } = useWindowDimensions(); @@ - snapToInterval={width} + snapToInterval={width}Also applies to: 51-53, 403-405
196-269: Memoize renderItem to reduce re-rendersAvoid recreating renderImageItem every render. Also, the explicit key on the Image isn’t needed since keyExtractor is provided.
- const renderImageItem = ({ item, index }: { item: CallFileResultData; index: number }) => { + const renderImageItem = useCallback(({ item, index }: { item: CallFileResultData; index: number }) => { if (!item) return null; @@ - <Image - key={`${item.Id}-${index}`} + <Image source={imageSource} style={styles.galleryImage} contentFit="contain" transition={200} pointerEvents="none" @@ - }; + }, [imageErrors, t]);As per coding guidelines (avoid anonymous functions in renderItem).
src/components/calls/dispatch-selection-modal.tsx (3)
35-49: De-duplicate filtering logic; use the store’s selectorThe component reimplements getFilteredData. Use the store source of truth to prevent drift and reduce code.
- // Calculate filtered data directly in component to ensure reactivity - const filteredData = useMemo(() => { - if (!searchQuery.trim()) { - return data; - } - const query = searchQuery.toLowerCase(); - return { - users: data.users.filter((user) => user.Name.toLowerCase().includes(query)), - groups: data.groups.filter((group) => group.Name.toLowerCase().includes(query)), - roles: data.roles.filter((role) => role.Name.toLowerCase().includes(query)), - units: data.units.filter((unit) => unit.Name.toLowerCase().includes(query)), - }; - }, [data, searchQuery]); + // Use store-provided computed data to avoid duplication + const filteredData = useDispatchStore((s) => s.getFilteredData());
285-291: Set icon colors via props (className may not affect lucide icon stroke)lucide-react-native icons reliably take color/size; className color isn’t guaranteed. Pass color explicitly; keep spacing via style.
- <UsersIcon size={24} className={colorScheme === 'dark' ? 'text-white' : 'text-neutral-900'} /> + <UsersIcon size={24} color={colorScheme === 'dark' ? '#ffffff' : '#111827'} /> @@ - <X size={24} className={colorScheme === 'dark' ? 'text-white' : 'text-neutral-900'} /> + <X size={24} color={colorScheme === 'dark' ? '#ffffff' : '#111827'} /> @@ - <SearchIcon size={20} className="ml-3 mr-2 text-neutral-500" /> + <SearchIcon size={20} color="#737373" style={{ marginLeft: 12, marginRight: 8 }} />As per coding guidelines: use lucide-react-native directly.
Also applies to: 297-299
329-435: Potential virtualization need if lists growMapping over many users/groups/roles/units in a ScrollView may stutter. If recipient counts can be high, consider FlashList per section.
src/stores/dispatch/store.ts (1)
145-147: Consider a friendlier user-facing errorerror.message may be technical. Optionally map to a generic message and log details only in dev.
FLATLIST_TO_FLASHLIST_MIGRATION.md (5)
56-69: Jest mock falls back to RN FlatList; may mask FlashList-specific regressions.Document this caveat and consider at least one test path without the mock to exercise FlashList behavior.
Add under “Jest Configuration Updates”:
+ Note: This mock intentionally proxies to RN FlatList to simplify tests. It does not exercise FlashList’s measuring/recycling paths. + We run a small set of integration/component tests with the mock disabled to catch regressions (e.g., sticky headers, onEndReached).Optional opt‑out pattern:
// jest-setup.ts - jest.mock('@shopify/flash-list', () => { ... }) + if (!process.env.DISABLE_FLASHLIST_MOCK) { + jest.mock('@shopify/flash-list', () => { ... }) + }And run one suite with
DISABLE_FLASHLIST_MOCK=1.
14-17: Document exported types alongside the wrapper re-export.Re-exporting only the component can degrade typing/auto‑complete for consumers. Recommend re‑exporting types too.
Add to the snippet:
- export { FlashList as FlatList } from '@shopify/flash-list'; + export { FlashList as FlatList } from '@shopify/flash-list'; + export type { + FlashListProps as FlatListProps, + FlashListRef as FlatListRef, + } from '@shopify/flash-list';
100-106: Add a note about changed defaults (maintainVisibleContentPosition) andinverteddeprecation.Helps teams migrating chat/feeds that relied on
invertedor scroll position behavior.Append:
4. **Drop-in Replacement**: Maintains API compatibility with FlatList for most use cases +5. **Changed defaults**: `maintainVisibleContentPosition` is enabled by default; prefer it over `inverted` patterns for chats.
124-129: Temper “No runtime errors expected” and list hotspots to sanity‑check.A few behaviors commonly differ between FlatList and FlashList; documenting them sets expectations.
Replace:
- 4. ✅ No runtime errors expected (FlashList is API-compatible) + 4. ✅ Known hotspots validated: sticky headers, onEndReached thresholds, ListEmptyComponent sizing, keyboardShouldPersistTaps, maintainVisibleContentPosition behavior on prepend/appends.
46-53: Tighten prop support wording and clarifyestimatedItemSize
Update FLATLIST_TO_FLASHLIST_MIGRATION.md as follows:- Removed FlatList-specific props that are not supported by FlashList: -- `estimatedItemSize` (was attempted but doesn't exist in FlashList) + Removed/ignored props under FlashList v2 (auto-measures items; v1 required `estimatedItemSize`): + - `estimatedItemSize` (removed/ignored in v2) - `getItemLayout` (not supported) - `initialNumToRender` (not supported) - `maxToRenderPerBatch` (not supported) - `windowSize` (not supported) - `removeClippedSubviews` (not supported)Scan confirms no remaining references to these props in TS/TSX files.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (25)
.github/workflows/react-native-cicd.yml(1 hunks).vscode/settings.json(1 hunks)FLATLIST_TO_FLASHLIST_MIGRATION.md(1 hunks)jest-setup.ts(1 hunks)package.json(2 hunks)src/api/calls/calls.ts(1 hunks)src/api/messaging/messages.ts(1 hunks)src/app/(app)/contacts.tsx(2 hunks)src/app/(app)/home/personnel.tsx(2 hunks)src/app/(app)/home/units.tsx(2 hunks)src/app/(app)/notes.tsx(2 hunks)src/app/(app)/protocols.tsx(2 hunks)src/app/(app)/shifts.tsx(0 hunks)src/app/onboarding.tsx(3 hunks)src/components/calls/call-images-modal.tsx(3 hunks)src/components/calls/dispatch-selection-modal.tsx(7 hunks)src/components/notifications/NotificationInbox.tsx(2 hunks)src/components/ui/actionsheet/index.tsx(2 hunks)src/components/ui/bottomsheet/index.tsx(2 hunks)src/components/ui/flat-list/index.tsx(1 hunks)src/components/ui/select/select-actionsheet.tsx(2 hunks)src/stores/dispatch/store.ts(4 hunks)src/translations/ar.json(2 hunks)src/translations/en.json(2 hunks)src/translations/es.json(2 hunks)
💤 Files with no reviewable changes (1)
- src/app/(app)/shifts.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components and favor interfaces for props and state
Avoid using any; use precise types
Use React Navigation for navigation and deep linking following best practices
Handle errors gracefully and provide user feedback
Implement proper offline support (caching, queueing, retries)
Use Expo SecureStore for sensitive data storage
Use zustand for state management
Use react-hook-form for form handling
Use react-query for data fetching and caching
Use react-native-mmkv for local storage
Use axios for API requests
**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Ensure support for dark mode and light mode
Handle errors gracefully and provide user feedback
Use react-query for data fetching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests
Files:
src/components/ui/select/select-actionsheet.tsxsrc/app/(app)/home/units.tsxsrc/stores/dispatch/store.tssrc/components/ui/bottomsheet/index.tsxsrc/api/messaging/messages.tssrc/components/ui/actionsheet/index.tsxsrc/api/calls/calls.tssrc/components/ui/flat-list/index.tsxjest-setup.tssrc/app/(app)/notes.tsxsrc/components/calls/call-images-modal.tsxsrc/components/notifications/NotificationInbox.tsxsrc/app/onboarding.tsxsrc/app/(app)/protocols.tsxsrc/app/(app)/home/personnel.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/app/(app)/contacts.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use functional components and React hooks instead of class components
Use PascalCase for React component names
Use React.FC for defining functional components with props
Minimize useEffect/useState usage and avoid heavy computations during render
Use React.memo for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Provide getItemLayout to FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers; define callbacks with useCallback or outside render
Use gluestack-ui for styling where available from components/ui; otherwise, style via StyleSheet.create or styled-components
Ensure responsive design across screen sizes and orientations
Use react-native-fast-image for image handling instead of the default Image where appropriate
Wrap all user-facing text in t() from react-i18next for translations
Support dark mode and light mode in UI components
Use @rnmapbox/maps for maps or navigation features
Use lucide-react-native for icons directly; do not use the gluestack-ui icon component
Use conditional rendering with the ternary operator (?:) instead of &&
**/*.tsx: Use functional components and hooks over class components
Ensure components are modular, reusable, and maintainable
Ensure all components are mobile-friendly, responsive, and support both iOS and Android
Use PascalCase for component names
Utilize React.FC for defining functional components with props
Minimize useEffect, useState, and heavy computations inside render
Use React.memo for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Use getItemLayout for FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers to prevent re-renders
Ensure responsive design for different screen sizes and orientations
Optimize image handling using rea...
Files:
src/components/ui/select/select-actionsheet.tsxsrc/app/(app)/home/units.tsxsrc/components/ui/bottomsheet/index.tsxsrc/components/ui/actionsheet/index.tsxsrc/components/ui/flat-list/index.tsxsrc/app/(app)/notes.tsxsrc/components/calls/call-images-modal.tsxsrc/components/notifications/NotificationInbox.tsxsrc/app/onboarding.tsxsrc/app/(app)/protocols.tsxsrc/app/(app)/home/personnel.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/app/(app)/contacts.tsx
src/**
📄 CodeRabbit inference engine (.cursorrules)
src/**: Organize files by feature, grouping related components, hooks, and styles
Directory and file names should be lowercase and hyphenated (e.g., user-profile)
Files:
src/components/ui/select/select-actionsheet.tsxsrc/app/(app)/home/units.tsxsrc/translations/en.jsonsrc/translations/es.jsonsrc/stores/dispatch/store.tssrc/components/ui/bottomsheet/index.tsxsrc/translations/ar.jsonsrc/api/messaging/messages.tssrc/components/ui/actionsheet/index.tsxsrc/api/calls/calls.tssrc/components/ui/flat-list/index.tsxsrc/app/(app)/notes.tsxsrc/components/calls/call-images-modal.tsxsrc/components/notifications/NotificationInbox.tsxsrc/app/onboarding.tsxsrc/app/(app)/protocols.tsxsrc/app/(app)/home/personnel.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/app/(app)/contacts.tsx
src/components/ui/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use gluestack-ui components from components/ui; if unavailable, style via StyleSheet.create or styled-components
Files:
src/components/ui/select/select-actionsheet.tsxsrc/components/ui/bottomsheet/index.tsxsrc/components/ui/actionsheet/index.tsxsrc/components/ui/flat-list/index.tsx
src/translations/**/*.json
📄 CodeRabbit inference engine (.cursorrules)
Store translation dictionary files under src/translations as JSON resources
Files:
src/translations/en.jsonsrc/translations/es.jsonsrc/translations/ar.json
src/translations/**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Store translation dictionary files in src/translations
Files:
src/translations/en.jsonsrc/translations/es.jsonsrc/translations/ar.json
🧠 Learnings (10)
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Applied to files:
src/components/ui/select/select-actionsheet.tsxsrc/app/(app)/home/units.tsxsrc/components/ui/actionsheet/index.tsxsrc/components/ui/flat-list/index.tsxsrc/app/(app)/notes.tsxsrc/components/calls/call-images-modal.tsxsrc/app/onboarding.tsxsrc/app/(app)/protocols.tsxsrc/app/(app)/home/personnel.tsxFLATLIST_TO_FLASHLIST_MIGRATION.md
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to src/components/ui/**/*.tsx : Use gluestack-ui components from components/ui; if unavailable, style via StyleSheet.create or styled-components
Applied to files:
src/components/ui/select/select-actionsheet.tsxsrc/components/ui/actionsheet/index.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Use gluestack-ui for styling where available from components/ui; otherwise, style via StyleSheet.create or styled-components
Applied to files:
src/components/ui/select/select-actionsheet.tsxsrc/components/ui/actionsheet/index.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Provide getItemLayout to FlatList when items have consistent size
Applied to files:
src/components/ui/select/select-actionsheet.tsxsrc/components/ui/actionsheet/index.tsxsrc/components/calls/call-images-modal.tsxsrc/app/onboarding.tsx
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly in markup; do not use gluestack-ui icon component
Applied to files:
src/components/ui/actionsheet/index.tsxsrc/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly; do not use the gluestack-ui icon component
Applied to files:
src/components/ui/actionsheet/index.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/app/(app)/contacts.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Use react-native-fast-image for image handling instead of the default Image where appropriate
Applied to files:
src/components/calls/call-images-modal.tsx
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.tsx : Optimize image handling using react-native-fast-image
Applied to files:
src/components/calls/call-images-modal.tsx
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.tsx : Use getItemLayout for FlatList when items have consistent size
Applied to files:
src/components/calls/call-images-modal.tsxsrc/app/onboarding.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.{ts,tsx} : Use React Navigation for navigation and deep linking following best practices
Applied to files:
src/app/onboarding.tsx
🧬 Code graph analysis (12)
src/components/ui/select/select-actionsheet.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/app/(app)/home/units.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/components/ui/actionsheet/index.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
jest-setup.ts (2)
__mocks__/react-native-webview.js (2)
React(1-1)require(2-2)__mocks__/@gorhom/bottom-sheet.js (2)
React(2-2)require(3-3)
src/app/(app)/notes.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/components/calls/call-images-modal.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/components/notifications/NotificationInbox.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/app/onboarding.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/app/(app)/protocols.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/app/(app)/home/personnel.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/components/calls/dispatch-selection-modal.tsx (4)
src/stores/dispatch/store.ts (1)
useDispatchStore(56-260)__mocks__/@/components/ui/button.tsx (2)
Button(4-6)ButtonText(8-10)__mocks__/@/components/ui/box.tsx (1)
Box(4-6)__mocks__/@/components/ui/hstack.tsx (1)
HStack(4-6)
src/app/(app)/contacts.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (18)
src/translations/es.json (1)
355-356: ES translations read well; approve.Wording is natural and consistent. Use this updated verification script to check parity and usage:
#!/bin/bash set -euo pipefail # Parity check (same as EN review) for f in src/translations/en.json src/translations/es.json src/translations/ar.json; do echo "Checking $f" jq -e '.common | has("no_data_available") and has("no_results_found") and has("search")' "$f" >/dev/null || { echo "Missing one or more keys in $f"; exit 1; } done # Usage check across .ts, .tsx, and .js files rg -n -C1 -g '*.ts' -g '*.tsx' -g '*.js' '\bcommon\.(no_data_available|no_results_found|search)\b' || truesrc/translations/ar.json (1)
355-356: Approve AR translationsTerminology fits generic UI usage. Use this updated verification script to check key parity and usage:
#!/usr/bin/env bash set -euo pipefail for f in src/translations/en.json src/translations/es.json src/translations/ar.json; do echo "Checking $f" jq -e '.common | has("no_data_available") and has("no_results_found") and has("search")' "$f" done rg -n -C1 -g '*.ts' -g '*.tsx' -g '*.js' '\bcommon\.(no_data_available|no_results_found|search)\b' || true.github/workflows/react-native-cicd.yml (1)
73-74: Verification needed: confirm RESPOND_COUNTLY_ usage*
Ripgrep didn’t locate any references in the codebase. Please manually verify which jobs or steps actually consume RESPOND_COUNTLY_APP_KEY and RESPOND_COUNTLY_URL before scoping them.src/components/ui/bottomsheet/index.tsx (2)
45-47: No‑op context handlers: LGTMFormatting-only; safe defaults preserved.
169-176: Escape‑to‑close on web: LGTMHandler is memoized and clean; no behavior risk.
package.json (1)
80-91: FlashList v2 upgrade validated
Search confirmed no deprecated FlashList props (getItemLayout, windowSize, etc.) or New Architecture opt-outs; bump to v2.1.0 is safe.src/components/ui/select/select-actionsheet.tsx (1)
10-11: Swap to FlashList: v2 audit passed
- No direct
react-nativeFlatList imports remain (only wrapper imports).- No unsupported v2 props found (
inverted,getItemLayout,windowSize,maxToRenderPerBatch,onScrollToIndexFailed,initialNumToRender).- Expo SDK 53 / RN 0.79 new-architecture prerequisites are satisfied.
src/app/(app)/contacts.tsx (1)
2-2: Confirm RN New Architecture enabled for FlashList v2.1.0
FlashList v2.1.0 is installed and requires React Native’s New Architecture; I didn’t find anynewArchEnabled=true,RN_NEW_ARCH_ENABLED=1, orRCT_NEW_ARCH_ENABLED=1settings in your Android or iOS configs. Please verify and enable New Architecture inandroid/gradle.propertiesand your iOS Podfile/Xcode project.src/components/ui/actionsheet/index.tsx (2)
8-8: Switch to FlashList acknowledged. Verify RN New Architecture is enabled.FlashList v2 targets RN New Architecture. Please confirm Android/iOS new-arch flags are on in this repo and CI. Based on learnings.
11-11: Import changes look fine.
No concerns with the added RN imports in this context.src/app/(app)/protocols.tsx (2)
2-2: Good migration to FlashList. Confirm New Architecture is enabled in builds.
6-6: Import change is fine.src/app/onboarding.tsx (1)
2-2: Type updates look good.Using FlashListRef and updating the ref type aligns with the new component.
Also applies to: 70-70
src/app/(app)/notes.tsx (2)
2-2: FlashList import looks good.
6-6: Import change is fine.src/components/calls/dispatch-selection-modal.tsx (1)
249-261: Confirm expected Cancel behavior (clears selection)Cancel clears selection before closing. If users expect to keep their current selection when dismissing, this will surprise them.
src/components/calls/call-images-modal.tsx (1)
1-1: Remove suggested change; FlashListRef is exported in v2.1.0. FlashListRef is available in"dist/index.d.ts", so you can keepimport { FlashList, type FlashListRef } from '@shopify/flash-list'; const flatListRef = useRef<FlashListRef<CallFileResultData>>(null);Likely an incorrect or invalid review comment.
FLATLIST_TO_FLASHLIST_MIGRATION.md (1)
116-121: Documentation version matches actual package version
The version "~2.1.0" is declared in package.json, locked in lockfiles, and published on npm as 2.1.0. No changes required.
FLATLIST_TO_FLASHLIST_MIGRATION.md
Outdated
| ## Compatibility Notes | ||
|
|
||
| - FlashList doesn't support some FlatList optimization props (`getItemLayout`, `initialNumToRender`, etc.) | ||
| - These props were removed as FlashList handles optimization internally | ||
| - All existing functionality is preserved | ||
| - Performance should be improved, especially for large lists | ||
|
|
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 | 🟠 Major
🧩 Analysis chain
Call out RN New Architecture prerequisite (Fabric) and guardrails.
FlashList v2 requires RN New Architecture. Please add an explicit note and verification steps; otherwise runtime will break on old-arch builds.
Add to “Compatibility Notes”:
- All existing functionality is preserved
- Performance should be improved, especially for large lists
+ - Prerequisite: React Native New Architecture (Fabric + TurboModules) must be enabled for iOS and Android.
+ - Android: `gradle.properties` → `newArchEnabled=true`
+ - iOS: `Podfile` `use_react_native!` opts include `:fabric_enabled => true` (or `RCT_NEW_ARCH_ENABLED=1`)
+ - If you ship old-arch builds, keep FlatList or gate FlashList by build flag.To verify in-repo:
🏁 Script executed:
#!/bin/bash
rg -nP 'newArchEnabled\s*=\s*true' -g '!**/node_modules/**'
rg -nP 'use_react_native!\s*\(.*:fabric_enabled\s*=>\s*true' ios -g '!**/node_modules/**' -S -C2
rg -nP 'RCT_NEW_ARCH_ENABLED\s*=\s*1|RCT_NEW_ARCH_ENABLED=YES' -g '!**/node_modules/**'Length of output: 341
🏁 Script executed:
#!/bin/bash
# List all gradle.properties files
echo "Gradle properties files:"
fd --type f gradle.properties
# Search for newArchEnabled in gradle.properties
echo -e "\nSearching for newArchEnabled in gradle.properties:"
rg -n 'newArchEnabled' $(fd --type f gradle.properties)
# List all Podfile files
echo -e "\nPodfile files:"
fd --type f Podfile
# Search for fabric_enabled in Podfile files
echo -e "\nSearching for fabric_enabled in Podfile:"
rg -n 'fabric_enabled' $(fd --type f Podfile)
# Search for RCT_NEW_ARCH_ENABLED in entire repository
echo -e "\nSearching for RCT_NEW_ARCH_ENABLED in repo:"
rg -n 'RCT_NEW_ARCH_ENABLED' .Length of output: 220
Add React Native New Architecture prerequisite to Compatibility Notes
FlashList v2 relies on Fabric + TurboModules. Ensure this is clearly documented:
- For Expo-managed apps: verify
newArchEnabled: trueinapp.config.ts. - For bare React Native apps:
- Android: set
newArchEnabled=trueinandroid/gradle.properties. - iOS: enable Fabric in your Podfile (
:fabric_enabled => trueorRCT_NEW_ARCH_ENABLED=1).
- Android: set
- If you need to ship non–New Architecture (old-arch) builds, continue using FlatList or gate FlashList behind a feature flag.
🤖 Prompt for AI Agents
In FLATLIST_TO_FLASHLIST_MIGRATION.md around lines 107 to 113, add a
compatibility prerequisite noting FlashList v2 requires React Native's New
Architecture (Fabric + TurboModules). Update the Compatibility Notes to instruct
Expo-managed apps to set newArchEnabled: true in app.config.ts, for bare Android
to set newArchEnabled=true in android/gradle.properties, and for bare iOS to
enable Fabric in the Podfile (e.g., :fabric_enabled => true or
RCT_NEW_ARCH_ENABLED=1); also mention that apps needing non-New-Architecture
builds should keep using FlatList or gate FlashList behind a feature flag.
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: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/onboarding.tsx (1)
26-42: Localize user-facing strings (titles, descriptions, buttons)Wrap onboarding copy and button labels with
t(). Consider memoizingonboardingDatafromtto update on locale change.+import { useTranslation } from 'react-i18next'; ... export default function Onboarding() { + const { t } = useTranslation(); ... -const onboardingData: OnboardingItemProps[] = [ +const onboardingData: OnboardingItemProps[] = [ { - title: 'Resgrid Responder', - description: 'Manage your status, staffing, and interact with your organization in real-time', + title: t('onboarding.title_app'), + description: t('onboarding.desc_app'), icon: <MapPin size={80} color="#FF7B1A" />, }, { - title: 'Instant Notifications', - description: 'Receive immediate alerts for emergencies and important updates from your department', + title: t('onboarding.title_notifications'), + description: t('onboarding.desc_notifications'), icon: <Bell size={80} color="#FF7B1A" />, }, { - title: 'Interact with Calls', - description: 'Seamlessly view call information and interact with your team members for efficient emergency response', + title: t('onboarding.title_calls'), + description: t('onboarding.desc_calls'), icon: <Users size={80} color="#FF7B1A" />, }, ]; ... -<Text className="text-gray-500">Skip</Text> +<Text className="text-gray-500">{t('common.skip')}</Text> ... -<ButtonText>Next </ButtonText> +<ButtonText>{t('common.next')}</ButtonText> ... -<ButtonText>Let's Get Started</ButtonText> +<ButtonText>{t('onboarding.get_started')}</ButtonText>If keys don’t exist yet, add them to your locales. As per coding guidelines.
Also applies to: 175-181, 203-204
src/components/calls/dispatch-selection-modal.tsx (1)
173-187: Trim analytics PII: don’t send raw search queriesSending the full searchQuery may be PII. You already send length; drop the raw string.
- trackEvent('dispatch_selection_search', { + trackEvent('dispatch_selection_search', { timestamp: new Date().toISOString(), - searchQuery: query, searchLength: query.length, });
🧹 Nitpick comments (20)
src/app/(app)/home/units.tsx (2)
95-102: FlashList: add estimatedItemSize and memoize renderItem; remove any cast
- Provide estimatedItemSize to help layout/recycling.
- Avoid inline renderItem; memoize with useCallback.
- Replace
anywith a concrete Unit type.Apply within this block:
<FlashList data={filteredUnits} keyExtractor={(item, index) => item.UnitId || `unit-${index}`} - renderItem={({ item }) => <UnitCard unit={item as any} onPress={selectUnit} />} + renderItem={renderUnit} showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 100 }} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />} + estimatedItemSize={120} />Add outside the block (near other hooks):
// Prefer importing a real Unit type from your models interface Unit { UnitId?: string; Name?: string; Type?: string; PlateNumber?: string; Vin?: string; GroupName?: string; } const renderUnit = React.useCallback( ({ item }: { item: Unit }) => <UnitCard unit={item} onPress={selectUnit} />, [selectUnit] );Based on learnings.
2-2: Prefer centralized UI alias for list componentIf
src/components/ui/flat-listre-exports FlashList, import from there for consistency and easier future swaps/config defaults.-import { FlashList } from '@shopify/flash-list'; +import { FlatList as FlashList } from '@/components/ui/flat-list';src/app/onboarding.tsx (1)
143-155: FlashList: set estimatedItemSize and memoize renderItemFor a horizontal pager where each item is screen-width, hint the size and avoid inline lambdas.
<FlashList ref={flatListRef} data={onboardingData} - renderItem={({ item }: { item: OnboardingItemProps }) => <OnboardingItem {...item} />} + renderItem={renderOnboardingItem} horizontal showsHorizontalScrollIndicator={false} pagingEnabled bounces={false} keyExtractor={(item: OnboardingItemProps) => item.title} onScroll={handleScroll} scrollEventThrottle={16} testID="onboarding-flatlist" + estimatedItemSize={width} />Add outside the block:
const renderOnboardingItem = React.useCallback( ({ item }: { item: OnboardingItemProps }) => <OnboardingItem {...item} />, [] );Based on learnings.
src/components/calls/call-images-modal.tsx (1)
390-413: Horizontal FlashList: add estimatedItemSize and handle scrollToIndex failures
- For width-paged galleries, set
estimatedItemSize={width}to help layout.- Add
onScrollToIndexFailedto recover when the target cell isn’t measured yet.<FlashList ref={flatListRef} data={validImages} renderItem={renderImageItem} keyExtractor={(item: CallFileResultData, index: number) => item?.Id || `image-${index}-${item?.Name || 'unknown'}`} horizontal pagingEnabled showsHorizontalScrollIndicator={false} onViewableItemsChanged={handleViewableItemsChanged} viewabilityConfig={{ itemVisiblePercentThreshold: 50, minimumViewTime: 100, }} + onScrollToIndexFailed={(info) => { + // Fallback: scroll close to the desired offset then retry + flatListRef.current?.scrollToOffset({ + offset: Math.max(0, info.averageItemLength * info.index), + animated: false, + }); + requestAnimationFrame(() => + flatListRef.current?.scrollToIndex({ index: info.index, animated: true }) + ); + }} snapToInterval={width} snapToAlignment="start" decelerationRate="fast" className="w-full" contentContainerStyle={{ paddingHorizontal: 0 }} ListEmptyComponent={() => (Based on learnings.
src/components/notifications/__tests__/NotificationInbox.test.tsx (2)
174-193: Avoid reimplementing pluralization in mock; derive from resources or return keysCurrent mock hardcodes English strings and plural rules, which can drift from real translations and doesn’t reflect Arabic/Spanish forms.
Minimal improvement example:
+import en from '@/translations/en.json'; + +const getByPath = (obj: any, path: string) => + path.split('.').reduce((o, k) => (o ? o[k] : undefined), obj); + -const mockT = jest.fn((key: string, options?: any) => { - const translations: Record<string, string> = { /* ...hardcoded... */ }; - return translations[key] || key; -}); +const mockT = jest.fn((key: string, options?: any) => { + // Try exact key, then _plural fallback when count !== 1 + const base = getByPath(en, key); + const plural = getByPath(en, `${key}_plural`); + const template = options?.count !== undefined && options.count !== 1 && plural ? plural : base; + if (typeof template === 'string') { + return template.replace(/\{\{(\w+)\}\}/g, (_, p) => options?.[p] ?? ''); + } + return key; +});This keeps tests aligned with actual resources. You can extract this into a shared test util.
503-573: Assert rendered UI for i18n where possible, not only t() invocationsSeveral tests only check that t() was called. Prefer asserting visible text after triggering the relevant UI (e.g., open the delete modal, enter selection mode) to reduce brittleness.
src/components/notifications/NotificationInbox.tsx (3)
303-312: Add estimatedItemSize to FlashList for better perfFlashList benefits from an estimatedItemSize to stabilize virtualization.
<FlashList data={notifications} renderItem={renderItem} - keyExtractor={(item: any) => item.id} + keyExtractor={(item: NotificationPayload) => item.id} onEndReached={fetchMore} onEndReachedThreshold={0.5} ListFooterComponent={renderFooter} ListEmptyComponent={renderEmpty} refreshControl={<RefreshControl refreshing={isLoading} onRefresh={refetch} colors={['#2196F3']} />} + estimatedItemSize={72} />Consider also getItemType if items are heterogeneous.
165-165: Tighten types for renderItemAvoid any; this also improves keyExtractor typing.
-const renderItem = ({ item }: { item: any }) => { +const renderItem = ({ item }: { item: NotificationPayload }) => {Additionally (outside this hunk), change:
-const allIds = notifications?.map((item: any) => item.id) || []; +const allIds = notifications?.map((item: NotificationPayload) => item.id) || [];
181-191: Optional a11y: add accessibilityRole and labels to interactivesImprove screen reader support on list items and icon-only buttons.
Example:
<Pressable + accessibilityRole="button" + accessibilityLabel={notification.title ?? t('notifications.title')} onPress={() => handleNotificationPress(notification)} onLongPress={() => { /* ... */ }} style={[/* ... */]} >Also consider accessibilityLabel for the More/Close buttons.
docs/notification-inbox-localization-implementation.md (1)
54-60: Docs: replace ICU plural examples with i18next plural keys (or document ICU plugin)Current examples use ICU; align with i18next defaults for consistency.
- bulkDeleteSuccess: "{{count}} notification{{count, plural, one {} other {s}}} removed", + bulkDeleteSuccess: "{{count}} notification removed", + bulkDeleteSuccess_plural: "{{count}} notifications removed", confirmDelete: { title: "Confirm Delete", - message: "Are you sure you want to delete {{count}} notification{{count, plural, one {} other {s}}}? This action cannot be undone." + message: "Are you sure you want to delete {{count}} notification? This action cannot be undone.", + message_plural: "Are you sure you want to delete {{count}} notifications? This action cannot be undone." }If you intend to use ICU, add a note and link to enabling i18next-icu in app i18n setup.
Also applies to: 116-118
src/stores/dispatch/store.ts (3)
95-103: Centralize type normalization to reduce branching and driftThe flexible matching works; consider a small helper to normalize once and switch on a union. This reduces copy/paste and future drift.
- // Categorize recipients based on Type field with both exact and flexible matching - recipients.Data.forEach((recipient) => { + // Categorize recipients based on normalized Type + const normalizeType = (t?: string) => { + const type = (t || '').toLowerCase().trim(); + if (type === 'personnel' || type === 'user' || type === 'users') return 'users'; + if (type === 'groups' || type === 'group') return 'groups'; + if (type === 'roles' || type === 'role') return 'roles'; + if (type === 'unit' || type === 'units') return 'units'; + return ''; + }; + recipients.Data.forEach((recipient) => { if (!recipient || !recipient.Type || !recipient.Name || !recipient.Id) { if (__DEV__) { console.warn('Skipping invalid recipient - missing required fields'); } return; } - - // First try exact matching (as per the test data) - if (recipient.Type === 'Personnel') { - categorizedUsers.push(recipient); - } else if (recipient.Type === 'Groups') { - categorizedGroups.push(recipient); - } else if (recipient.Type === 'Roles') { - categorizedRoles.push(recipient); - } else if (recipient.Type === 'Unit') { - categorizedUnits.push(recipient); - } else { - // Fallback to case-insensitive matching - const type = recipient.Type.toLowerCase().trim(); - if (type === 'personnel' || type === 'user' || type === 'users') { - categorizedUsers.push(recipient); - } else if (type === 'groups' || type === 'group') { - categorizedGroups.push(recipient); - } else if (type === 'roles' || type === 'role') { - categorizedRoles.push(recipient); - } else if (type === 'unit' || type === 'units') { - categorizedUnits.push(recipient); - } else { - // Log unknown types for debugging - if (__DEV__) { - console.warn(`Unknown recipient type: '${recipient.Type}'`); - } - } - } + const t = recipient.Type; + switch (t) { + case 'Personnel': categorizedUsers.push(recipient); break; + case 'Groups': categorizedGroups.push(recipient); break; + case 'Roles': categorizedRoles.push(recipient); break; + case 'Unit': categorizedUnits.push(recipient); break; + default: + switch (normalizeType(t)) { + case 'users': categorizedUsers.push(recipient); break; + case 'groups': categorizedGroups.push(recipient); break; + case 'roles': categorizedRoles.push(recipient); break; + case 'units': categorizedUnits.push(recipient); break; + default: if (__DEV__) console.warn(`Unknown recipient type: '${t}'`); + } + } });Also applies to: 113-129
133-145: Wrap the “no recipients categorized” check in DEV to avoid prod computetotalCategorized and available types are only for logging; skip the work in prod.
- // Only log if we have issues with categorization - const totalCategorized = categorizedUsers.length + categorizedGroups.length + categorizedRoles.length + categorizedUnits.length; - if (totalCategorized === 0 && recipients.Data.length > 0) { - if (__DEV__) { - console.warn('No recipients were successfully categorized!'); - console.warn('Available recipient types:', [...new Set(recipients.Data.map((r) => r.Type))]); - } - } + // Dev-only: log categorization issues + if (__DEV__) { + const totalCategorized = categorizedUsers.length + categorizedGroups.length + categorizedRoles.length + categorizedUnits.length; + if (totalCategorized === 0 && recipients.Data.length > 0) { + console.warn('No recipients were successfully categorized!'); + console.warn('Available recipient types:', [...new Set(recipients.Data.map((r) => r.Type))]); + } + }
156-161: Prefer generic user-facing errors; keep details in DEV logsExposing raw error.message to UI may leak internal details. Keep the console message in DEV but show a generic string to users.
- if (__DEV__) { - console.error('Error fetching dispatch data:', error instanceof Error ? error.message : 'Unknown error'); - } + if (__DEV__) console.error('Error fetching dispatch data:', error); set({ - error: error instanceof Error ? error.message : 'Failed to fetch dispatch data', + error: 'Failed to fetch dispatch data', isLoading: false, });src/components/calls/dispatch-selection-modal.tsx (7)
3-3: Remove unused importuseState isn’t used.
-import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react';
71-73: Gate fetch on open to avoid redundant callsOnly fetch when lists are empty (or add a TTL later).
- // Always fetch data when modal opens - fetchDispatchData(); + // Fetch on first open or when empty + if ( + data.users.length === 0 && + data.groups.length === 0 && + data.roles.length === 0 && + data.units.length === 0 + ) { + fetchDispatchData(); + }
247-249: Add accessibility labels to the close buttonImprove screen-reader support.
[As per coding guidelines]- <TouchableOpacity onPress={handleCancel}> + <TouchableOpacity + onPress={handleCancel} + accessibilityRole="button" + accessibilityLabel={t('common.close')} + >
288-311: Use FlashList for virtualization; replace && rendering with ternariesLong recipient lists will jank in a ScrollView. Prefer a single FlashList with interleaved section headers (or one per section if you keep them separate). Also, per guidelines, use ?: instead of &&.
[As per coding guidelines]Minimal example for Users section (illustrative):
- {/* Users Section */} - {filteredData.users.length > 0 && ( + {/* Users Section */} + {filteredData.users.length > 0 ? ( <VStack className="mb-6"> <Text className="mb-3 text-lg font-semibold"> {t('calls.users')} ({filteredData.users.length}) </Text> - {filteredData.users.map((user) => ( - <Card key={`user-${user.Id}`} className={`mb-2 rounded-lg border p-3 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}> - <TouchableOpacity onPress={() => handleToggleUser(user.Id)}> - ... - </TouchableOpacity> - </Card> - ))} + {/* Consider FlashList here */} + {/* <FlashList + data={filteredData.users} + keyExtractor={(u) => `user-${u.Id}`} + estimatedItemSize={56} + renderItem={({ item: user }) => ( + <Card className={`mb-2 rounded-lg border p-3 ${colorScheme === 'dark' ? 'border-neutral-800 bg-neutral-900' : 'border-neutral-200 bg-white'}`}> + <TouchableOpacity onPress={() => handleToggleUser(user.Id)}> + ...row... + </TouchableOpacity> + </Card> + )} + /> */} </VStack> - )} + ) : null}If you consolidate to one FlashList, build a flat data model of headers and items with getItemType and render by type; set estimatedItemSize and, if applicable, getItemType for better recycling. Based on learnings.
Also applies to: 314-337, 340-363, 366-389
391-397: Replace && with ternaries for conditional blocksMatches the project’s TSX guidelines.
[As per coding guidelines]- {!isLoading && !error && searchQuery && filteredData.users.length === 0 && filteredData.groups.length === 0 && filteredData.roles.length === 0 && filteredData.units.length === 0 && ( + {!isLoading && !error && searchQuery && filteredData.users.length === 0 && filteredData.groups.length === 0 && filteredData.roles.length === 0 && filteredData.units.length === 0 ? ( <Box className="items-center justify-center py-8"> <Text className="text-center text-neutral-500">{t('common.no_results_found')}</Text> </Box> - )} + ) : null}- {!isLoading && !error && !searchQuery && data.users.length === 0 && data.groups.length === 0 && data.roles.length === 0 && data.units.length === 0 && ( + {!isLoading && !error && !searchQuery && data.users.length === 0 && data.groups.length === 0 && data.roles.length === 0 && data.units.length === 0 ? ( <Box className="items-center justify-center py-8"> <Text className="text-center text-neutral-500">{t('common.no_data_available', { defaultValue: 'No recipients available' })}</Text> <Button variant="outline" className="mt-4" onPress={() => refreshDispatchData()}> <ButtonText>{t('common.refresh', { defaultValue: 'Refresh' })}</ButtonText> </Button> </Box> - )} + ) : null}Also applies to: 398-406
97-114: Inline handlers per row can churn; extract memoized row componentsFor large lists, per-item closures cause extra allocations. If keeping ScrollView maps, consider a small memoized Row component that receives onToggle and selected to minimize re-renders.
Also applies to: 116-133, 135-152, 154-171
34-47: Reuse dispatch store’sgetFilteredDataselectorThis component duplicates the exact filtering logic already implemented in the dispatch store. Replace the local
useMemoblock with the store selector to keep a single source of truth:- // Calculate filtered data directly in component to ensure reactivity - const filteredData = useMemo(() => { - if (!searchQuery.trim()) { - return data; - } - - const query = searchQuery.toLowerCase(); - return { - users: data.users.filter((user) => user.Name.toLowerCase().includes(query)), - groups: data.groups.filter((group) => group.Name.toLowerCase().includes(query)), - roles: data.roles.filter((role) => role.Name.toLowerCase().includes(query)), - units: data.units.filter((unit) => unit.Name.toLowerCase().includes(query)), - }; - }, [data, searchQuery]); + // Reuse the store selector to avoid logic drift + const filteredData = useDispatchStore(state => state.getFilteredData());Ensure you import the hook from your dispatch store (e.g.
import { useDispatchStore } from 'src/stores/dispatch/store').
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (14)
docs/notification-inbox-localization-implementation.md(1 hunks)docs/notification-inbox-translation-keys-verification.md(1 hunks)package.json(6 hunks)src/app/(app)/home/personnel.tsx(3 hunks)src/app/(app)/home/units.tsx(3 hunks)src/app/onboarding.tsx(3 hunks)src/components/calls/call-images-modal.tsx(3 hunks)src/components/calls/dispatch-selection-modal.tsx(9 hunks)src/components/notifications/NotificationInbox.tsx(7 hunks)src/components/notifications/__tests__/NotificationInbox.test.tsx(6 hunks)src/stores/dispatch/store.ts(4 hunks)src/translations/ar.json(3 hunks)src/translations/en.json(3 hunks)src/translations/es.json(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs/notification-inbox-translation-keys-verification.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/app/(app)/home/personnel.tsx
- src/translations/es.json
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components and favor interfaces for props and state
Avoid using any; use precise types
Use React Navigation for navigation and deep linking following best practices
Handle errors gracefully and provide user feedback
Implement proper offline support (caching, queueing, retries)
Use Expo SecureStore for sensitive data storage
Use zustand for state management
Use react-hook-form for form handling
Use react-query for data fetching and caching
Use react-native-mmkv for local storage
Use axios for API requests
**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Ensure support for dark mode and light mode
Handle errors gracefully and provide user feedback
Use react-query for data fetching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests
Files:
src/app/(app)/home/units.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/components/calls/call-images-modal.tsxsrc/stores/dispatch/store.tssrc/components/notifications/__tests__/NotificationInbox.test.tsxsrc/components/notifications/NotificationInbox.tsxsrc/app/onboarding.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use functional components and React hooks instead of class components
Use PascalCase for React component names
Use React.FC for defining functional components with props
Minimize useEffect/useState usage and avoid heavy computations during render
Use React.memo for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Provide getItemLayout to FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers; define callbacks with useCallback or outside render
Use gluestack-ui for styling where available from components/ui; otherwise, style via StyleSheet.create or styled-components
Ensure responsive design across screen sizes and orientations
Use react-native-fast-image for image handling instead of the default Image where appropriate
Wrap all user-facing text in t() from react-i18next for translations
Support dark mode and light mode in UI components
Use @rnmapbox/maps for maps or navigation features
Use lucide-react-native for icons directly; do not use the gluestack-ui icon component
Use conditional rendering with the ternary operator (?:) instead of &&
**/*.tsx: Use functional components and hooks over class components
Ensure components are modular, reusable, and maintainable
Ensure all components are mobile-friendly, responsive, and support both iOS and Android
Use PascalCase for component names
Utilize React.FC for defining functional components with props
Minimize useEffect, useState, and heavy computations inside render
Use React.memo for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Use getItemLayout for FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers to prevent re-renders
Ensure responsive design for different screen sizes and orientations
Optimize image handling using rea...
Files:
src/app/(app)/home/units.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/components/calls/call-images-modal.tsxsrc/components/notifications/__tests__/NotificationInbox.test.tsxsrc/components/notifications/NotificationInbox.tsxsrc/app/onboarding.tsx
src/**
📄 CodeRabbit inference engine (.cursorrules)
src/**: Organize files by feature, grouping related components, hooks, and styles
Directory and file names should be lowercase and hyphenated (e.g., user-profile)
Files:
src/app/(app)/home/units.tsxsrc/components/calls/dispatch-selection-modal.tsxsrc/components/calls/call-images-modal.tsxsrc/stores/dispatch/store.tssrc/translations/ar.jsonsrc/components/notifications/__tests__/NotificationInbox.test.tsxsrc/components/notifications/NotificationInbox.tsxsrc/app/onboarding.tsxsrc/translations/en.json
src/translations/**/*.json
📄 CodeRabbit inference engine (.cursorrules)
Store translation dictionary files under src/translations as JSON resources
Files:
src/translations/ar.jsonsrc/translations/en.json
src/translations/**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Store translation dictionary files in src/translations
Files:
src/translations/ar.jsonsrc/translations/en.json
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{test,spec}.{ts,tsx}: Create Jest tests for all generated components, services, and logic
Ensure tests run without errors and fix failing tests
Files:
src/components/notifications/__tests__/NotificationInbox.test.tsx
src/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
src/**/*.test.{ts,tsx}: Create and use Jest tests to validate all generated components
Generate tests for all components, services, and logic; ensure tests run without errors
Files:
src/components/notifications/__tests__/NotificationInbox.test.tsx
🧠 Learnings (9)
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Applied to files:
src/app/(app)/home/units.tsxsrc/components/calls/call-images-modal.tsxsrc/app/onboarding.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly; do not use the gluestack-ui icon component
Applied to files:
src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly in markup; do not use gluestack-ui icon component
Applied to files:
src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.tsx : Optimize image handling using react-native-fast-image
Applied to files:
src/components/calls/call-images-modal.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Provide getItemLayout to FlatList when items have consistent size
Applied to files:
src/components/calls/call-images-modal.tsxsrc/app/onboarding.tsx
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.tsx : Use getItemLayout for FlatList when items have consistent size
Applied to files:
src/components/calls/call-images-modal.tsxsrc/app/onboarding.tsx
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.tsx : Wrap all user-facing text in t() from react-i18next for translations
Applied to files:
src/components/notifications/__tests__/NotificationInbox.test.tsxdocs/notification-inbox-localization-implementation.md
📚 Learning: 2025-08-21T02:47:24.081Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-21T02:47:24.081Z
Learning: Applies to **/*.{ts,tsx} : Use react-i18next for internationalization
Applied to files:
src/components/notifications/__tests__/NotificationInbox.test.tsxdocs/notification-inbox-localization-implementation.md
📚 Learning: 2025-08-21T02:46:42.743Z
Learnt from: CR
PR: Resgrid/Responder#0
File: .cursorrules:0-0
Timestamp: 2025-08-21T02:46:42.743Z
Learning: Applies to **/*.{ts,tsx} : Use React Navigation for navigation and deep linking following best practices
Applied to files:
src/app/onboarding.tsx
🧬 Code graph analysis (6)
src/app/(app)/home/units.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/components/calls/dispatch-selection-modal.tsx (4)
src/stores/dispatch/store.ts (1)
useDispatchStore(56-274)__mocks__/@/components/ui/button.tsx (2)
Button(4-6)ButtonText(8-10)__mocks__/@/components/ui/box.tsx (1)
Box(4-6)__mocks__/@/components/ui/hstack.tsx (1)
HStack(4-6)
src/components/calls/call-images-modal.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/components/notifications/__tests__/NotificationInbox.test.tsx (1)
src/components/notifications/NotificationInbox.tsx (1)
NotificationInbox(29-341)
src/components/notifications/NotificationInbox.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
src/app/onboarding.tsx (1)
src/components/ui/flat-list/index.tsx (1)
FlashList(2-2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (10)
src/app/(app)/home/units.tsx (1)
55-56: Nice null-safety on search fieldsUsing optional chaining on
NameandTypeguards undefined entries cleanly.src/app/onboarding.tsx (1)
70-71: Ref typing looks good
useRef<FlashList<OnboardingItemProps>>(null)aligns with FlashList’s generic API.src/components/calls/call-images-modal.tsx (1)
64-66: Ref and types are correct
useRef<FlashList<CallFileResultData>>(null)is the right typing for imperativescrollToIndexusage.src/translations/en.json (1)
355-366: Common keys addition looks goodKeys are consistent and reusable across features.
src/translations/ar.json (2)
355-366: Common keys addition looks good (Arabic)Consistent with English; helpful for shared UI.
641-656: Verify i18next-icu configuration
- No reference to
i18next-icuwas found in your dependencies or i18n initialization, so ICU plural syntax in your JSON won’t be applied.- Either install and configure
i18next-icu(e.g.import Icu from 'i18next-icu'; i18n.use(Icu)…) or replace ICU plurals with standard i18next plural keys (key/key_plural).src/components/notifications/NotificationInbox.tsx (1)
136-145: Good i18n integration in toasts, headers, empty/error states, and modalt is correctly used and included in callback deps. Nice coverage.
If ICU isn’t configured (see translation comments), ensure pluralization strings are updated to i18next plural keys.
Also applies to: 150-157, 232-236, 265-275, 281-281, 300-300, 324-335
src/stores/dispatch/store.ts (2)
36-36: New refresh API looks goodAdding refreshDispatchData and delegating to fetchDispatchData is clean and idempotent. No concerns.
Also applies to: 166-170
73-88: Good: strict data-shape guard + DEV‑only logs (resolves prior PII concerns)Validates recipients.Data and gates logs under DEV, avoiding payload/name leakage in prod. Nice.
src/components/calls/dispatch-selection-modal.tsx (1)
260-269: Retry UX looks goodError message + retry button via refreshDispatchData is clear.
| "expo": "~53.0.23", | ||
| "expo-application": "~6.1.5", |
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.
Expo 53 upgrade breaks builds with LiveKit Expo plugin 1.0.1
Expo SDK 53 ships with React Native 0.79 and turns the New Architecture on by default. (expo.dev) The LiveKit Expo config plugin you still have at 1.0.1 is known to crash iOS builds under this combo with Cannot find type 'RCTBridge' in scope. (github.com) LiveKit merged a fix in May 2025, but they have not cut a new npm release yet—1.0.1 remains the latest published version—so the patched code isn’t available through a version bump. (github.com) In practice this upgrade will block delivery until you either (a) hold the Expo bump, (b) point the plugin at a commit containing the fix, or (c) vendor/patch the plugin locally. Please resolve before landing.
🤖 Prompt for AI Agents
package.json around lines 99-100: upgrading Expo to SDK 53 breaks iOS builds
because the LiveKit Expo plugin v1.0.1 is incompatible with RN 0.79 / New
Architecture; to fix, either (A) pin Expo back to the previous working SDK
version by reverting the "expo" value to the prior release in package.json and
run yarn/npm install, (B) point the livekit expo plugin dependency to the
upstream commit that includes the May 2025 fix (replace the npm version with the
github repo@<commit-hash> in package.json and reinstall), or (C) vendor/patch
the plugin locally by copying the patched plugin source into your repo, update
package.json to reference the local path, run install, and verify the iOS build;
after making the change, reinstall node modules, clear caches (expo start -c /
pod install in ios), and run an iOS build to confirm the crash is resolved.
| "notifications": { | ||
| "bulkDeleteError": "Failed to remove notifications", | ||
| "bulkDeleteSuccess": "{{count}} notification{{count, plural, one {} other {s}}} removed", | ||
| "confirmDelete": { | ||
| "message": "Are you sure you want to delete {{count}} notification{{count, plural, one {} other {s}}}? This action cannot be undone.", | ||
| "title": "Confirm Delete" | ||
| }, | ||
| "deleteError": "Failed to remove notification", | ||
| "deleteSuccess": "Notification removed", | ||
| "deselectAll": "Deselect All", | ||
| "empty": "No updates available", | ||
| "loadError": "Unable to load notifications", | ||
| "selectAll": "Select All", | ||
| "selectedCount": "{{count}} selected", | ||
| "title": "Notifications" | ||
| }, |
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.
🧩 Analysis chain
Pluralization format assumes ICU; switch to i18next plural keys or confirm i18next-icu
The strings use ICU syntax ({{count, plural,...}}). If i18next-icu isn’t configured, these won’t pluralize and will render literally. Prefer native i18next pluralization keys.
Apply this diff for safe i18next pluralization:
"notifications": {
- "bulkDeleteSuccess": "{{count}} notification{{count, plural, one {} other {s}}} removed",
+ "bulkDeleteSuccess": "{{count}} notification removed",
+ "bulkDeleteSuccess_plural": "{{count}} notifications removed",
"confirmDelete": {
- "message": "Are you sure you want to delete {{count}} notification{{count, plural, one {} other {s}}}? This action cannot be undone.",
+ "message": "Are you sure you want to delete {{count}} notification? This action cannot be undone.",
+ "message_plural": "Are you sure you want to delete {{count}} notifications? This action cannot be undone.",
"title": "Confirm Delete"
},
"deleteError": "Failed to remove notification",
"deleteSuccess": "Notification removed",
"deselectAll": "Deselect All",
"empty": "No updates available",
"loadError": "Unable to load notifications",
"selectAll": "Select All",
"selectedCount": "{{count}} selected",
"title": "Notifications"
},Run this to verify ICU usage and plugin presence:
🏁 Script executed:
#!/bin/bash
# Find ICU-style pluralization patterns in translations
rg -nP "plural\s*,\s*one" src/translations
# Check if i18next-icu (or any ICU plugin) is configured
rg -nP "i18next-icu|ICU|@formatjs|i18next\.use\(.+ICU" -g "!**/node_modules/**"Length of output: 1062
🏁 Script executed:
#!/bin/bash
# Search for i18next-icu plugin imports and usage
rg -nP "i18next-icu" -g "!**/node_modules/**"
rg -nP "import\s+.*i18next-icu" -g "!**/node_modules/**"
rg -nP "require\(['\"]i18next-icu['\"]\)" -g "!**/node_modules/**"
rg -nP "i18next\.use\s*\(" -g "!**/node_modules/**"Length of output: 595
🏁 Script executed:
#!/bin/bash
# Check dependencies in package.json
grep -R "\"i18next-icu\"" -n .
# Search for plugin registration in code
grep -R "i18next.use" -n .
grep -R "import.*i18next-icu" -n .
grep -R "require.*i18next-icu" -n .Length of output: 100769
ICU plural syntax won’t work without a plugin—switch keys or install i18next-icu
No i18next-icu (or equivalent) plugin is present—ICU patterns like {{count, plural, one {} other {s}}} in src/translations/en.json will render literally. You must either:
- Migrate to native i18next plurals (
key/key_plural), or - Add and initialize
i18next-icu(install the package and calli18next.use(require('i18next-icu'))in your i18n setup)
🤖 Prompt for AI Agents
In src/translations/en.json around lines 641-656, the ICU plural patterns (e.g.
"{{count, plural, one {} other {s}}}") will render literally because i18next-icu
is not configured; fix by either (A) replacing ICU patterns with i18next native
plural keys (e.g. split keys into "bulkDeleteSuccess" and
"bulkDeleteSuccess_plural" and use singular/plural forms with the count
variable), or (B) install and initialize the ICU plugin (add the i18next-icu
package to dependencies and call i18next.use(require('i18next-icu')) during your
i18n setup before init) so the ICU syntax is parsed; choose one approach and
update translations or i18n bootstrap accordingly.
|
Approve |
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.
This PR is approved.
Summary by CodeRabbit
New Features
Refactor
Documentation
Tests
Chores