Skip to content

Build messaging & inbox UI & message seller button#52

Merged
jaydonkc merged 10 commits intodevfrom
feature/POLY-39-build-messaging-UI
Mar 11, 2026
Merged

Build messaging & inbox UI & message seller button#52
jaydonkc merged 10 commits intodevfrom
feature/POLY-39-build-messaging-UI

Conversation

@lheutchy
Copy link
Copy Markdown
Collaborator

@lheutchy lheutchy commented Mar 6, 2026

Linked Issues

Closes #39
Linear: POLY-39

Summary

  • Created the Message Seller entry point on listing detail pages.
  • Created the inbox/conversation list screen with real-time data.
  • Created the full chat detail screen for individual conversations.

Connected the messaging navigation flow:

  • Listing detail → chat

  • Inbox → chat

  • Added unread indicators/badge support for messaging UI.

  • Added message read status display in chat (Sent / Read).

How to Test

Steps to verify locally:

  • npm run lint
  • npm run typecheck
  • npm test
  • Manual flow:
    1. npm run dev:backend (in terminal A)
    2. npm run dev (in terminal B)
    3. Verify the change: <describe expected behavior/screens>

Checklist

  • Tests added/updated (if applicable)
  • Lint/tests pass locally (npm run lint)
  • Docs updated (README/ADR/changelog if needed)
  • Follows conventional commit format
  • No merge conflicts with dev

Screenshots / Demos

Screenshot 2026-03-06 at 3 48 26 PM

Screenshot 2026-03-06 at 3 48 44 PM

e06da6658eda" /> Screenshot 2026-03-06 at 3 48 53 PM

Summary by CodeRabbit

  • New Features

    • Unread badges on Inbox (web & mobile), redesigned Inbox list, full Conversation screen, and streamlined "Message Seller" flow with direct conversation navigation.
  • Bug Fixes

    • Other participant names resolve correctly when aliases exist.
    • Marking messages as read no longer alters conversation "last updated" timestamps.
    • Message history, unread marking, and listing thumbnails now aggregate and display across related sibling conversations and handle remote/local URLs.
  • Tests

    • Added tests for alias name resolution, whitespace-only message rejection, sibling-conversation aggregation, and read-mark timestamp behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

Warning

Rate limit exceeded

@evan-taylor has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 33 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4c99c1ef-1bd0-4fb5-ba8f-210e019d9a5a

📥 Commits

Reviewing files that changed from the base of the PR and between 2943a12 and 611259a.

📒 Files selected for processing (8)
  • backend/convex/__tests__/messages.test.ts
  • backend/convex/messages.ts
  • frontend/app/(tabs)/_layout.tsx
  • frontend/app/(tabs)/inbox.tsx
  • frontend/app/_layout.tsx
  • frontend/app/conversations/[id].tsx
  • frontend/app/listings/[id].tsx
  • frontend/app/messages/[id].tsx
📝 Walkthrough

Walkthrough

Adds alias-aware participant handling and richer messaging flows backend-side, new messaging tests, and frontend inbox/conversation UI with unread badges, conversation routing, and message composer/mark-read behaviors.

Changes

Cohort / File(s) Summary
Backend Messaging Core
backend/convex/messages.ts
Add canonical/alias participant utilities, getParticipantKeys/requireConversationScope, internalSendMessage, alias-aware sendMessage/getOrCreateConversation/listUserConversations/markMessagesAsRead, siblingConversationIds support, moderation integration, and listing thumbnail resolution.
Backend Tests
backend/convex/__tests__/messages.test.ts, backend/convex/testUtils.ts
Import/export createTestProfile; add tests for whitespace-only rejection, siblingConversationIds aggregation, alias name resolution, generic user labels, read-marking not updating conversation.updatedAt, and cross-sibling read marking.
Frontend Tabs & Unread Badge
frontend/app/(tabs)/_layout.tsx
Wire unreadCount prop into WebTabsHeaderLayout and native tab badge; fetch listUserConversations when authenticated and compute total unread count; add styles.
Frontend Inbox Screen
frontend/app/(tabs)/inbox.tsx
Replace placeholder with FlatList-driven conversations, ConversationRow, timestamp formatting, auth-aware loading/sign-in flow, unread indicators, and empty-state handling.
Frontend Conversation Detail
frontend/app/conversations/[id].tsx, frontend/app/messages/[id].tsx
Add ConversationDetailScreen (chat UI, message list, composer, auto-scroll, mark-as-read, optimistic send); re-export it from old messages route.
Routing / Listing Integration
frontend/app/_layout.tsx, frontend/app/listings/[id].tsx
Add conversations/[id] route; update listing "Message Seller" flow to use getOrCreateConversation and navigate to /conversations/[id]; prevent concurrent starts and guard authentication.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (UI)
    participant Frontend as Frontend Router/UI
    participant API as Convex Backend
    participant DB as Database
    participant Storage as Storage Service

    Client->>Frontend: Open Inbox
    Frontend->>API: listUserConversations(authSubject)
    activate API
    API->>DB: Query conversations for participant keys (including aliases)
    DB-->>API: Conversations
    API->>DB: Fetch partner profiles & listings
    DB-->>API: Profiles & listings
    API->>Storage: Resolve local listing thumbnails
    Storage-->>API: Resolved URLs
    API-->>Frontend: Enriched conversation list (unread counts, thumbnails)
    deactivate API
    Frontend->>Client: Render Inbox rows with badges
Loading
sequenceDiagram
    participant User as User (UI)
    participant Frontend as Conversation Screen
    participant API as Convex Backend
    participant Moderation as Moderation Service
    participant DB as Database

    User->>Frontend: Send message(content)
    Frontend->>Frontend: Optimistic UI update
    Frontend->>API: sendMessage(content, senderKey, recipientKey)
    activate API
    API->>Moderation: Moderate content
    Moderation-->>API: result
    alt allowed
        API->>DB: internalSendMessage -> persist message, update conversation metadata
        DB-->>API: persisted message
    else blocked
        API-->>Frontend: moderation error
    end
    deactivate API
    API-->>Frontend: confirm message persisted
    Frontend->>User: Update success / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jaydonkc

🐇
I hopped through aliases, found every name,
Made badges shimmer and threads feel the same,
Thumbnails fetched, unread dots aglow,
Messages hop where conversations go!

🚥 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.
Linked Issues check ❓ Inconclusive This PR addresses messaging UI components and navigation for issue #39, but #39 is a broad issue requiring auth refactor, schema updates, and extensive test coverage expansion, most of which is not present in these changes. Clarify whether this PR is a partial implementation of #39 or should close a different sub-issue; ensure all issue requirements are met or documented.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: building messaging UI components (inbox, conversation detail), and the message seller button, which are the primary deliverables.
Description check ✅ Passed The PR description covers the main objectives (message seller, inbox screen, chat detail, navigation flow, unread indicators) and includes how-to-test steps, though the test checklist is incomplete and screenshots are partially cut off.
Out of Scope Changes check ✅ Passed Changes are focused on messaging UI (inbox, conversation detail, message seller button) and related backend message handling, which aligns with the PR objectives for messaging functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/POLY-39-build-messaging-UI

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/convex/messages.ts (1)

439-443: ⚠️ Potential issue | 🟠 Major

Don't update updatedAt when only the read cursor changes.

listUserConversations sorts by updatedAt, so reading a thread currently moves it to the top even when no new message arrived.

🛠️ Suggested fix
-    if (isBuyer) {
-      await ctx.db.patch(convo._id, { buyerLastReadAt: now, updatedAt: now });
-    } else if (isSeller) {
-      await ctx.db.patch(convo._id, { sellerLastReadAt: now, updatedAt: now });
-    }
+    if (isBuyer) {
+      await ctx.db.patch(convo._id, { buyerLastReadAt: now });
+    } else if (isSeller) {
+      await ctx.db.patch(convo._id, { sellerLastReadAt: now });
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/messages.ts` around lines 439 - 443, The current read-cursor
update in messages.ts is incorrectly updating updatedAt (via ctx.db.patch on
convo._id), which causes listUserConversations to reorder threads when a user
simply reads them; change the ctx.db.patch calls that set buyerLastReadAt and
sellerLastReadAt to only update those fields (buyerLastReadAt or
sellerLastReadAt) and omit updatedAt so reads don't modify the conversation's
updatedAt timestamp. Ensure any other paths that should bump updatedAt (e.g.,
message creation) remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/convex/messages.ts`:
- Around line 246-259: The current dedup logic in the loop that builds
dedupedConversationMap (iterating participantConversations and keying by
`${conversation.listingId}:${canonicalParticipantId(otherUserId)}`) drops older
sibling threads and thus loses messages/unread state; instead, collect all
conversations per dedupeKey (e.g., map to arrays of Doc<'conversations'>), then
for each group merge or aggregate the needed fields before returning: compute a
combined updatedAt = max(...updatedAt), aggregate unread/preview data (sum or
choose per UX rules), and backfill or stitch message history/lastMessage from
all siblings so you don't discard older messages; update the code paths that
consume conversations to use the merged/aggregated conversation objects rather
than a single newest document.
- Around line 236-244: listUserConversations currently scans all conversations
and profiles using getParticipantKeys and matchesAnyParticipantId; change it to
query the conversations table by the buyer and seller indexes (use
ctx.db.query('conversations').index('byBuyerId', ...) and .index('bySellerId',
...) or your equivalent index names) to fetch only conversations for the current
participant keys, dedupe the returned conversation IDs, then enrich those IDs
(fetch conversations and associated profiles) rather than filtering the full
table; also stop running a per-conversation "latest message" query by using the
conversation.lastMessageId field to fetch the latest message in batch for the
deduped conversations. Ensure you update any related code in the same function
block (and the similar logic around lines 261-299) to use these indexed queries,
dedup ids, and batch message fetches.

In `@frontend/app/`(tabs)/inbox.tsx:
- Around line 53-60: ConversationRow currently does an unnecessary per-row fetch
via useQuery(api.listings.getListing) and calls useResolvedImageUrls, creating
an N+1 query; remove that fetch and resolution and use the data already returned
by listUserConversations (item.listing.title and item.listing.thumbnailUrl).
Replace references to listingId, listing, listingTitle, thumbnailSource,
useQuery(...getListing...), and useResolvedImageUrls so listingTitle =
item.listing?.title ?? 'Listing unavailable' and thumbnail =
item.listing?.thumbnailUrl ?? null (use thumbnail directly where mappedUrls[0]
was used), and delete the useQuery and mappedUrls usage from ConversationRow.
Ensure types still accept null and no new API calls are introduced.

---

Outside diff comments:
In `@backend/convex/messages.ts`:
- Around line 439-443: The current read-cursor update in messages.ts is
incorrectly updating updatedAt (via ctx.db.patch on convo._id), which causes
listUserConversations to reorder threads when a user simply reads them; change
the ctx.db.patch calls that set buyerLastReadAt and sellerLastReadAt to only
update those fields (buyerLastReadAt or sellerLastReadAt) and omit updatedAt so
reads don't modify the conversation's updatedAt timestamp. Ensure any other
paths that should bump updatedAt (e.g., message creation) remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68edb253-51fa-4244-abd5-2846e06aa94a

📥 Commits

Reviewing files that changed from the base of the PR and between 24a1d97 and 4e9afa9.

📒 Files selected for processing (8)
  • backend/convex/__tests__/messages.test.ts
  • backend/convex/messages.ts
  • frontend/app/(tabs)/_layout.tsx
  • frontend/app/(tabs)/inbox.tsx
  • frontend/app/_layout.tsx
  • frontend/app/conversations/[id].tsx
  • frontend/app/listings/[id].tsx
  • frontend/app/messages/[id].tsx

Comment thread backend/convex/messages.ts
Comment thread backend/convex/messages.ts Outdated
Comment thread frontend/app/(tabs)/inbox.tsx Outdated
Copy link
Copy Markdown
Collaborator

@SamanSP1386 SamanSP1386 left a comment

Choose a reason for hiding this comment

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

Check out the code Rabbit, there is some issues.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/convex/messages.ts (1)

126-132: ⚠️ Potential issue | 🟡 Minor

Reject whitespace-only messages.

args.body.length === 0 still allows " " and "\n" through, so this endpoint can persist visually empty chat messages. Trim before the emptiness check.

Suggested change
+    const trimmedBody = args.body.trim();
-    if (args.body.length === 0) {
+    if (trimmedBody.length === 0) {
       throw new ConvexError('Message cannot be empty');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/messages.ts` around lines 126 - 132, The current emptiness
check uses args.body.length which allows whitespace-only strings; update the
validation in the message handler (backend/convex/messages.ts) to compute a
trimmedBody = args.body.trim() and use trimmedBody.length === 0 to throw the
ConvexError('Message cannot be empty'), while keeping the maximum-length check
against PAYLOAD_BOUNDS.MESSAGE_MAX (e.g., use args.body.length or
trimmedBody.length per your intended policy) so whitespace-only content is
rejected before persisting.
♻️ Duplicate comments (1)
backend/convex/messages.ts (1)

305-325: ⚠️ Potential issue | 🟠 Major

Grouped alias threads are still only addressable by one conversation ID.

Line 487 spreads primaryConversation, so the inbox row still carries a single _id even though Line 489 exposes merged sibling IDs and the preview/unread state is aggregated across them. The downstream history/read APIs in this module still operate on a single conversationId, so opening a grouped row can miss older sibling messages and leave their unread state behind. Either merge/backfill these sibling documents into one canonical conversation or add merged history/read operations keyed by the full sibling set.

Also applies to: 486-505

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/messages.ts` around lines 305 - 325, The groupedConversations
builder currently returns a primaryConversation (spread into inbox rows) but
keeps siblingConversations separate, so downstream handlers (history/read APIs)
that use primaryConversation._id miss messages/unread state from others; update
this code path to surface the full sibling ID set and a canonical merged ID:
include siblingConversationIds (e.g., siblingConversations.map(c=>c._id)) and a
mergedConversationId or canonicalOtherUserId in the returned object instead of
relying solely on primaryConversation._id, then update the history/read
functions (the modules that consume groupedConversations) to accept and operate
on either the mergedConversationId or the array siblingConversationIds
(fetch/merge/backfill messages and unread counts across all IDs) so opening a
grouped row aggregates messages/unread state from all siblings.
🧹 Nitpick comments (2)
frontend/app/(tabs)/inbox.tsx (2)

200-200: Extract ItemSeparatorComponent to a stable reference.

Inline arrow functions for ItemSeparatorComponent create a new component reference on every render, which can affect FlatList optimization.

♻️ Suggested fix

Define outside the component or use a stable reference:

+const ItemSeparator = () => <View style={styles.separator} />;
+
 export default function InboxScreen() {
   // ...
 }

Then use it:

-      ItemSeparatorComponent={() => <View style={styles.separator} />}
+      ItemSeparatorComponent={ItemSeparator}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/inbox.tsx at line 200, The inline arrow passed to
ItemSeparatorComponent creates a new component each render; extract it to a
stable reference by defining a named component or memoized callback (e.g.,
create a function/component ItemSeparator that returns <View
style={styles.separator} /> outside the main component or use
useCallback/useMemo to return that element) and then pass ItemSeparator (or the
memoized reference) to the FlatList's ItemSeparatorComponent prop to avoid
recreating the component on every render.

123-137: Wrap formatTimestamp in useCallback to stabilize reference.

The function is passed as a prop to ConversationRow but recreated on every render, potentially causing unnecessary child re-renders. The formatters it depends on are already memoized.

♻️ Suggested fix
-  const formatTimestamp = (timestamp: number) => {
+  const formatTimestamp = useCallback((timestamp: number) => {
     const value = new Date(timestamp);
     const now = new Date();
     const sameDay = value.toDateString() === now.toDateString();
     if (sameDay) {
       return formatter.format(value);
     }
 
     const diff = now.getTime() - value.getTime();
     if (diff < 1000 * 60 * 60 * 24 * 7) {
       return weekdayFormatter.format(value);
     }
 
     return shortDateFormatter.format(value);
-  };
+  }, [formatter, weekdayFormatter, shortDateFormatter]);

Also add useCallback to the import on line 1:

-import { useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/inbox.tsx around lines 123 - 137, The formatTimestamp
function is recreated each render and passed to ConversationRow; wrap it in
React's useCallback and add useCallback to the import list so its reference is
stable. Change formatTimestamp declaration to useCallback(() => { ... },
[formatter, weekdayFormatter, shortDateFormatter]) (include timestamp as
parameter in the callback signature) so the dependency array references the
existing memoized formatters, and then pass that stable callback to
ConversationRow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/convex/messages.ts`:
- Around line 29-63: The displayNameFromProfile function currently derives names
from emails and fallbackUserId which can leak private identifiers; update
displayNameFromProfile to only use explicit public display fields (user?.name
and profile?.name after trimming) and never return parts of user.email,
profile.email, or fallbackUserId; if neither name is present return the generic
'User'. Ensure you remove or disable the branches that inspect email or
fallbackUserId and keep trimming/length checks for user?.name and profile?.name
(references: displayNameFromProfile, profile.name, user.name, user.email,
profile.email, fallbackUserId).

---

Outside diff comments:
In `@backend/convex/messages.ts`:
- Around line 126-132: The current emptiness check uses args.body.length which
allows whitespace-only strings; update the validation in the message handler
(backend/convex/messages.ts) to compute a trimmedBody = args.body.trim() and use
trimmedBody.length === 0 to throw the ConvexError('Message cannot be empty'),
while keeping the maximum-length check against PAYLOAD_BOUNDS.MESSAGE_MAX (e.g.,
use args.body.length or trimmedBody.length per your intended policy) so
whitespace-only content is rejected before persisting.

---

Duplicate comments:
In `@backend/convex/messages.ts`:
- Around line 305-325: The groupedConversations builder currently returns a
primaryConversation (spread into inbox rows) but keeps siblingConversations
separate, so downstream handlers (history/read APIs) that use
primaryConversation._id miss messages/unread state from others; update this code
path to surface the full sibling ID set and a canonical merged ID: include
siblingConversationIds (e.g., siblingConversations.map(c=>c._id)) and a
mergedConversationId or canonicalOtherUserId in the returned object instead of
relying solely on primaryConversation._id, then update the history/read
functions (the modules that consume groupedConversations) to accept and operate
on either the mergedConversationId or the array siblingConversationIds
(fetch/merge/backfill messages and unread counts across all IDs) so opening a
grouped row aggregates messages/unread state from all siblings.

---

Nitpick comments:
In `@frontend/app/`(tabs)/inbox.tsx:
- Line 200: The inline arrow passed to ItemSeparatorComponent creates a new
component each render; extract it to a stable reference by defining a named
component or memoized callback (e.g., create a function/component ItemSeparator
that returns <View style={styles.separator} /> outside the main component or use
useCallback/useMemo to return that element) and then pass ItemSeparator (or the
memoized reference) to the FlatList's ItemSeparatorComponent prop to avoid
recreating the component on every render.
- Around line 123-137: The formatTimestamp function is recreated each render and
passed to ConversationRow; wrap it in React's useCallback and add useCallback to
the import list so its reference is stable. Change formatTimestamp declaration
to useCallback(() => { ... }, [formatter, weekdayFormatter, shortDateFormatter])
(include timestamp as parameter in the callback signature) so the dependency
array references the existing memoized formatters, and then pass that stable
callback to ConversationRow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8556db8a-8e53-47e1-8f23-2aa8fea8df8c

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9afa9 and d92f354.

📒 Files selected for processing (3)
  • backend/convex/__tests__/messages.test.ts
  • backend/convex/messages.ts
  • frontend/app/(tabs)/inbox.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/convex/tests/messages.test.ts

Comment thread backend/convex/messages.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/app/conversations/[id].tsx (1)

24-42: Consider extracting shared participant ID utilities.

The canonicalParticipantId, isSameParticipantId, and matchesAnyParticipantId functions duplicate the backend implementations in backend/convex/messages.ts. Consider extracting these to a shared package to ensure consistency and reduce maintenance burden.

Additionally, formatMessageTimestamp creates a new Intl.DateTimeFormat instance on each call. While not critical for message rendering frequency, memoizing the formatter (as done in inbox.tsx) would be more efficient.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/conversations/`[id].tsx around lines 24 - 42, The local
participant-ID helpers (canonicalParticipantId, isSameParticipantId,
matchesAnyParticipantId) duplicate backend logic—extract them into a shared
utility module and replace the local implementations with imports from that
shared module (create and export
canonicalParticipantId/isSameParticipantId/matchesAnyParticipantId there so both
backend and frontend reuse the same logic). Also avoid recreating
Intl.DateTimeFormat on each call in formatMessageTimestamp: create a
module-level memoized/singleton formatter (e.g., messageTimestampFormatter = new
Intl.DateTimeFormat(...)) like inbox.tsx, and have formatMessageTimestamp call
that formatter.format(new Date(timestamp)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/app/conversations/`[id].tsx:
- Around line 24-42: The local participant-ID helpers (canonicalParticipantId,
isSameParticipantId, matchesAnyParticipantId) duplicate backend logic—extract
them into a shared utility module and replace the local implementations with
imports from that shared module (create and export
canonicalParticipantId/isSameParticipantId/matchesAnyParticipantId there so both
backend and frontend reuse the same logic). Also avoid recreating
Intl.DateTimeFormat on each call in formatMessageTimestamp: create a
module-level memoized/singleton formatter (e.g., messageTimestampFormatter = new
Intl.DateTimeFormat(...)) like inbox.tsx, and have formatMessageTimestamp call
that formatter.format(new Date(timestamp)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 755782ae-6b3e-4d6e-87e5-9b2cf0550166

📥 Commits

Reviewing files that changed from the base of the PR and between d92f354 and 2943a12.

📒 Files selected for processing (4)
  • backend/convex/__tests__/messages.test.ts
  • backend/convex/messages.ts
  • frontend/app/(tabs)/inbox.tsx
  • frontend/app/conversations/[id].tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/convex/tests/messages.test.ts

@evan-taylor evan-taylor force-pushed the feature/POLY-39-build-messaging-UI branch from 2943a12 to 611259a Compare March 11, 2026 01:14
@jaydonkc jaydonkc merged commit 15cdd0c into dev Mar 11, 2026
3 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.

3 participants