Skip to content

RR-T45 Fixes#122

Merged
ucswift merged 2 commits into
masterfrom
develop
Jul 19, 2026
Merged

RR-T45 Fixes#122
ucswift merged 2 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 19, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added POI detail screens with map previews, directions, location details, and destination actions.
    • Added full-screen interactive maps from map previews.
    • Added contact detail loading for more complete information.
    • Added persistent message read-status tracking.
  • Bug Fixes
    • Improved scrolling for call notes and content.
    • Improved image gallery sizing and navigation.
    • Corrected date and time parsing and display.
    • Improved notification selection and delete confirmation behavior.
  • Tests
    • Expanded coverage for maps, dates, headers, and notifications.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ucswift, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f09c366-7f7b-4b59-8a82-6a53a45653d2

📥 Commits

Reviewing files that changed from the base of the PR and between 1ec9f3e and 71fefac.

📒 Files selected for processing (4)
  • src/components/notifications/NotificationInbox.tsx
  • src/lib/__tests__/utils-date.test.ts
  • src/lib/utils.ts
  • src/stores/contacts/store.ts
📝 Walkthrough

Walkthrough

Adds a routed POI detail screen and full-screen map modal, updates call and gallery interactions, fixes fixed-size header controls, adds contact and message state handling, replaces notification confirmation UI, and revises date parsing and formatting.

Changes

POI and map flows

Layer / File(s) Summary
POI detail route
src/app/(app)/poi/[id].tsx, src/app/(app)/poi/_layout.tsx
Adds POI route validation, fetching, analytics, navigation actions, loading/error states, and detail rendering.
Full-screen map integration
src/components/maps/*, src/app/call/[id]/index.tsx
Adds a Mapbox full-screen modal, tappable static maps, call-detail integration, and modal tests.

Call and shared UI behavior

Layer / File(s) Summary
Call content and tab presentation
src/app/call/[id]/index.tsx, src/components/calls/call-card.tsx
Enables scrolling for call content, removes tab icons, and disables landscape-dependent tab scrolling.
Measured call image gallery
src/components/calls/call-images-modal.tsx
Uses measured viewport width for gallery sizing, navigation offsets, momentum tracking, and rendering distance.
Fixed header action frames
src/components/calls/call-detail-menu.tsx, src/components/common/header-back-button.tsx, src/components/common/__tests__/header-back-button.test.tsx
Adds fixed non-collapsable wrappers around header controls and updates the corresponding test.

State-backed UI flows

Layer / File(s) Summary
Contact detail loading
src/stores/contacts/store.ts, src/components/contacts/contact-details-sheet.tsx
Fetches and stores full contact details when the details sheet opens and uses them in rendering.
Persisted message read state
src/stores/messages/store.ts, src/components/messages/message-card.tsx
Persists read message IDs, marks selected messages as read, and renders read status from store state.
Notification selection and confirmation
src/components/notifications/NotificationInbox.tsx, src/components/notifications/__tests__/NotificationInbox.test.tsx
Uses a native delete confirmation modal, refreshes list items from selection state, and updates translation timing tests.

Date utility handling

Layer / File(s) Summary
Timezone parsing and display formatting
src/lib/utils.ts, src/lib/__tests__/utils-date.test.ts
Preserves explicit timezone offsets and updates hour-token formatting with expanded timezone and formatting tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CallDetail
  participant StaticMap
  participant FullScreenMapModal
  participant Mapbox
  CallDetail->>StaticMap: render map preview with onPress
  StaticMap->>CallDetail: report tap
  CallDetail->>FullScreenMapModal: open with coordinates
  FullScreenMapModal->>Mapbox: render interactive map
Loading

Possibly related PRs

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague and only references a ticket number, so it doesn't clearly describe the main change. Rename it to summarize the primary update, such as the new POI detail screen and related UI/store fixes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
src/stores/messages/store.ts (1)

228-237: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle the floating promise from setItem.

setItem is an async function. Since markMessageRead is synchronous and returns void, the promise returned by setItem is unhandled. If the underlying storage or serialization fails, it will lead to an unhandled promise rejection which could crash the app.

Consider appending .catch() to log or swallow the error safely.

♻️ Proposed refactor
     markMessageRead: (messageId: string) => {
       const { readMessageIds } = get();
       if (readMessageIds.has(messageId)) {
         return;
       }
       const next = new Set(readMessageIds);
       next.add(messageId);
       set({ readMessageIds: next });
-      setItem<string[]>('messages_read_ids', Array.from(next));
+      setItem<string[]>('messages_read_ids', Array.from(next)).catch(console.error);
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/messages/store.ts` around lines 228 - 237, Update markMessageRead
so the promise returned by setItem is explicitly handled without changing the
method’s synchronous void contract. Append a catch path that safely logs or
swallows persistence errors, while preserving the existing readMessageIds update
behavior.
src/app/(app)/poi/[id].tsx (1)

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

Make the static map preview tappable.

Because StaticMap has all map gestures disabled to allow for smooth page scrolling, tapping the map itself currently does nothing. Passing the onPress handler will add a transparent overlay that makes the map intuitively trigger the map view.

👆 Proposed fix
-            <StaticMap latitude={poi.Latitude} longitude={poi.Longitude} address={poi.Address || title} zoom={15} height={220} showUserLocation={true} />
+            <StaticMap latitude={poi.Latitude} longitude={poi.Longitude} address={poi.Address || title} zoom={15} height={220} showUserLocation={true} onPress={handleViewOnMap} />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(app)/poi/[id].tsx at line 161, Update the StaticMap usage in the
POI detail view to pass an onPress handler that opens the map view for the
current POI, while preserving the existing map properties and gesture-disabled
behavior.
src/components/notifications/NotificationInbox.tsx (1)

321-321: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize extraData to prevent unnecessary re-renders.

Passing an inline object literal to extraData creates a new reference on every render, causing FlashList to bypass its PureComponent optimizations and re-render all items unnecessarily whenever the parent component renders (e.g., when toggling the confirmation modal).

As per coding guidelines, "Minimize useEffect/useState usage and avoid heavy computations during render" and "Optimize FlatList".

Define the object using useMemo in the component body:

const listExtraData = React.useMemo(
  () => ({ isSelectionMode, selectedNotificationIds }),
  [isSelectionMode, selectedNotificationIds]
);
⚡ Proposed fix for `extraData`
-                extraData={{ isSelectionMode, selectedNotificationIds }}
+                extraData={listExtraData}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/notifications/NotificationInbox.tsx` at line 321, Memoize the
FlashList extraData object in NotificationInbox using useMemo, with
isSelectionMode and selectedNotificationIds as dependencies, then pass the
stable listExtraData value to extraData instead of an inline object.

Source: Coding guidelines

src/components/calls/call-images-modal.tsx (1)

607-607: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use useCallback for event handlers.

As per coding guidelines, avoid anonymous functions and define callbacks with useCallback for event handlers to prevent unnecessary re-renders.

  • src/components/calls/call-images-modal.tsx#L607-L607: Extract the inline anonymous function into a named callback using useCallback (e.g., const handleLayout = useCallback((e) => setListWidth(e.nativeEvent.layout.width), []);).
  • src/components/calls/call-images-modal.tsx#L514-L519: Wrap handleMomentumScrollEnd in useCallback and include its dependencies (e.g., [listWidth, validImages.length]).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/calls/call-images-modal.tsx` at line 607, The event handlers
in src/components/calls/call-images-modal.tsx at lines 607-607 and 514-519
should use stable callbacks: extract the inline onLayout handler near the Box
into a named useCallback callback, and wrap handleMomentumScrollEnd in
useCallback with listWidth and validImages.length as dependencies.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`(app)/poi/[id].tsx:
- Around line 35-42: Update PoiDetailHeader to use useSafeAreaInsets from
react-native-safe-area-context, and apply the returned top inset as additional
top padding on the header while preserving its existing background and content
layout.

In `@src/app/call/`[id]/index.tsx:
- Around line 757-759: Update the FullScreenMapModal render condition to depend
only on non-null latitude and longitude, removing isMapModalOpen from the outer
check. Keep passing isMapModalOpen through isOpen so the modal remains mounted
and can perform its exit animation.

In `@src/components/notifications/NotificationInbox.tsx`:
- Around line 341-361: Extract the inline modal-close callbacks in the
NotificationInbox component into a memoized handleCloseConfirmModal useCallback
that sets showDeleteConfirmModal to false. Reuse this callback for RNModal’s
onRequestClose and the cancel Button’s onPress, while leaving confirmBulkDelete
unchanged.

In `@src/lib/utils.ts`:
- Around line 155-163: Update the timezone suffix regex in the timestamp parsing
block to allow hour-only offsets such as +04 or -04, while continuing to accept
offsets with minutes in both +0400 and +04:00 forms. Keep valid timezone-bearing
timestamps routed through the native Date parser before the manual parsing path.

In `@src/stores/contacts/store.ts`:
- Around line 79-88: Update fetchContactDetails so its success and error state
updates only apply when the requested contactId is still the active contact ID.
Recheck the store’s current selected contact before setting
selectedContactDetails or isDetailsLoading, preventing stale requests from
overwriting the newer selection while preserving the existing fallback behavior.

---

Nitpick comments:
In `@src/app/`(app)/poi/[id].tsx:
- Line 161: Update the StaticMap usage in the POI detail view to pass an onPress
handler that opens the map view for the current POI, while preserving the
existing map properties and gesture-disabled behavior.

In `@src/components/calls/call-images-modal.tsx`:
- Line 607: The event handlers in src/components/calls/call-images-modal.tsx at
lines 607-607 and 514-519 should use stable callbacks: extract the inline
onLayout handler near the Box into a named useCallback callback, and wrap
handleMomentumScrollEnd in useCallback with listWidth and validImages.length as
dependencies.

In `@src/components/notifications/NotificationInbox.tsx`:
- Line 321: Memoize the FlashList extraData object in NotificationInbox using
useMemo, with isSelectionMode and selectedNotificationIds as dependencies, then
pass the stable listExtraData value to extraData instead of an inline object.

In `@src/stores/messages/store.ts`:
- Around line 228-237: Update markMessageRead so the promise returned by setItem
is explicitly handled without changing the method’s synchronous void contract.
Append a catch path that safely logs or swallows persistence errors, while
preserving the existing readMessageIds update behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac8d884a-e1c3-4e1f-986e-8e34ed8d8052

📥 Commits

Reviewing files that changed from the base of the PR and between 98892c3 and 1ec9f3e.

📒 Files selected for processing (20)
  • src/app/(app)/poi/[id].tsx
  • src/app/(app)/poi/_layout.tsx
  • src/app/call/[id]/index.tsx
  • src/app/poi/[id].tsx
  • src/components/calls/call-card.tsx
  • src/components/calls/call-detail-menu.tsx
  • src/components/calls/call-images-modal.tsx
  • src/components/common/__tests__/header-back-button.test.tsx
  • src/components/common/header-back-button.tsx
  • src/components/contacts/contact-details-sheet.tsx
  • src/components/maps/__tests__/full-screen-map-modal.test.tsx
  • src/components/maps/full-screen-map-modal.tsx
  • src/components/maps/static-map.tsx
  • src/components/messages/message-card.tsx
  • src/components/notifications/NotificationInbox.tsx
  • src/components/notifications/__tests__/NotificationInbox.test.tsx
  • src/lib/__tests__/utils-date.test.ts
  • src/lib/utils.ts
  • src/stores/contacts/store.ts
  • src/stores/messages/store.ts
💤 Files with no reviewable changes (1)
  • src/app/poi/[id].tsx

Comment on lines +35 to +42
const PoiDetailHeader: React.FC<{ title: string; onBack: () => void }> = ({ title, onBack }) => (
<HStack className="items-center bg-white px-2 pb-2 pt-1 dark:bg-gray-900" space="sm">
<HeaderBackButton onPress={onBack} />
<Text className="flex-1 text-lg font-bold text-gray-900 dark:text-white" numberOfLines={1}>
{title}
</Text>
</HStack>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add safe area padding to prevent the header from overlapping the device status bar.

Because this screen runs in a layout with headerShown: false, the custom PoiDetailHeader will sit flush against the top edge of the screen, causing its content to overlap with the iOS notch or Android status bar.

Use useSafeAreaInsets to apply top padding so the header's background color extends to the edge while the content is safely pushed down.

📱 Proposed fix

First, add the import at the top of the file:

import { useSafeAreaInsets } from 'react-native-safe-area-context';

Then, apply the insets to the header:

-const PoiDetailHeader: React.FC<{ title: string; onBack: () => void }> = ({ title, onBack }) => (
-  <HStack className="items-center bg-white px-2 pb-2 pt-1 dark:bg-gray-900" space="sm">
-    <HeaderBackButton onPress={onBack} />
-    <Text className="flex-1 text-lg font-bold text-gray-900 dark:text-white" numberOfLines={1}>
-      {title}
-    </Text>
-  </HStack>
-);
+const PoiDetailHeader: React.FC<{ title: string; onBack: () => void }> = ({ title, onBack }) => {
+  const insets = useSafeAreaInsets();
+  return (
+    <HStack className="items-center bg-white px-2 pb-2 dark:bg-gray-900" space="sm" style={{ paddingTop: insets.top + 4 }}>
+      <HeaderBackButton onPress={onBack} />
+      <Text className="flex-1 text-lg font-bold text-gray-900 dark:text-white" numberOfLines={1}>
+        {title}
+      </Text>
+    </HStack>
+  );
+};
📝 Committable suggestion

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

Suggested change
const PoiDetailHeader: React.FC<{ title: string; onBack: () => void }> = ({ title, onBack }) => (
<HStack className="items-center bg-white px-2 pb-2 pt-1 dark:bg-gray-900" space="sm">
<HeaderBackButton onPress={onBack} />
<Text className="flex-1 text-lg font-bold text-gray-900 dark:text-white" numberOfLines={1}>
{title}
</Text>
</HStack>
);
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const PoiDetailHeader: React.FC<{ title: string; onBack: () => void }> = ({ title, onBack }) => {
const insets = useSafeAreaInsets();
return (
<HStack className="items-center bg-white px-2 pb-2 dark:bg-gray-900" space="sm" style={{ paddingTop: insets.top + 4 }}>
<HeaderBackButton onPress={onBack} />
<Text className="flex-1 text-lg font-bold text-gray-900 dark:text-white" numberOfLines={1}>
{title}
</Text>
</HStack>
);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(app)/poi/[id].tsx around lines 35 - 42, Update PoiDetailHeader to
use useSafeAreaInsets from react-native-safe-area-context, and apply the
returned top inset as additional top padding on the header while preserving its
existing background and content layout.

Comment on lines +757 to +759
{isMapModalOpen && coordinates.latitude != null && coordinates.longitude != null ? (
<FullScreenMapModal isOpen={isMapModalOpen} onClose={() => setIsMapModalOpen(false)} latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} showUserLocation={true} />
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep FullScreenMapModal mounted to allow its exit animation.

When isMapModalOpen evaluates to false, the short-circuit instantly unmounts the <FullScreenMapModal> component. This abruptly destroys the native Modal inside it, skipping its closing slide animation entirely.

By removing isMapModalOpen from this outer condition, the component remains mounted and delegates visibility control to the isOpen prop, enabling smooth exit transitions.

🎞️ Proposed fix
-      {isMapModalOpen && coordinates.latitude != null && coordinates.longitude != null ? (
+      {coordinates.latitude != null && coordinates.longitude != null ? (
         <FullScreenMapModal isOpen={isMapModalOpen} onClose={() => setIsMapModalOpen(false)} latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} showUserLocation={true} />
       ) : null}
📝 Committable suggestion

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

Suggested change
{isMapModalOpen && coordinates.latitude != null && coordinates.longitude != null ? (
<FullScreenMapModal isOpen={isMapModalOpen} onClose={() => setIsMapModalOpen(false)} latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} showUserLocation={true} />
) : null}
{coordinates.latitude != null && coordinates.longitude != null ? (
<FullScreenMapModal isOpen={isMapModalOpen} onClose={() => setIsMapModalOpen(false)} latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} showUserLocation={true} />
) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/call/`[id]/index.tsx around lines 757 - 759, Update the
FullScreenMapModal render condition to depend only on non-null latitude and
longitude, removing isMapModalOpen from the outer check. Keep passing
isMapModalOpen through isOpen so the modal remains mounted and can perform its
exit animation.

Comment thread src/components/notifications/NotificationInbox.tsx
Comment thread src/lib/utils.ts
Comment thread src/stores/contacts/store.ts
@ucswift

ucswift commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift
ucswift merged commit 84db88e into master Jul 19, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant