Skip to content

fix: show & sort conversations by latest message time #946

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

notcodesid
Copy link

@notcodesid notcodesid commented Aug 17, 2025

fix: show & sort conversations by latest message time (closes #899)

Summary
Use the latest message time for both display and ordering in conversation lists.
UI uses latestMessageAt ?? createdAt. Closed conversations still sort by closedAt.

Demo

demo.mov

User impact

  • Replying to any thread immediately bumps it to the top.
  • The timestamp chip reflects the last message, not the thread’s creation time.
  • Closed view behavior is unchanged.

How to test

  1. In All → Newest, reply to an older conversation → it should jump to the top and show “just now/1m”.
  2. Create a brand-new conversation with no replies → it shows a sensible time from createdAt.
  3. Switch to Closed → ordering remains by closedAt.

Notes

  • If local DB predates the new column, run once: pnpm db:backfill-last-message-at.

Closes #899

Summary by CodeRabbit

  • New Features
    • Conversations now sort by most recent activity with improved fallback logic so freshest threads appear first.
    • Timestamps and previews show the latest message time when available (falls back sensibly to thread creation).
    • Conversation previews/widgets and API responses include a latest-message timestamp.
    • Label updated from “Last email received on” to “Last activity on” for clearer context.

Copy link

vercel bot commented Aug 17, 2025

@notcodesid is attempting to deploy a commit to the Antiwork Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Aug 17, 2025

Walkthrough

Updates conversation timestamp handling across DB search, serialization, API mapping, and UI so lists use the latest message/activity time. Adds a lateral join to fetch recent message data, exposes latestMessageAt in payloads, updates ordering fallbacks, and adjusts UI labels to "Last activity".

Changes

Cohort / File(s) Summary
Dashboard List Item UI
app/(dashboard)/[category]/list/conversationListItem.tsx
Label changed to "Last activity on". Timestamp selection now prefers latestMessageAt → recentMessageAt → lastUserEmailCreatedAt → createdAt; added inline comments and a type assertion for recentMessageAt.
Widget Conversations UI
components/widget/PreviousConversations.tsx
Conversation type extended with optional `latestMessageAt?: string
API Mapping
app/api/chat/conversations/route.ts
Per-conversation latestMessageAt computed as lastMessageAt ?? recentMessageAt ?? createdAt (ISO string) when available; mapping expanded to use an explicit return block.
Serialization
lib/data/conversation.ts
serializeConversation now includes latestMessageAt: conversation.lastMessageAt ?? null in returned object.
Search / Listing / Ordering
lib/data/conversation/search.ts
Added lateral join recent_message to fetch most-recent message text and time. Ordering for open conversations uses COALESCE(lastMessageAt, recent_message.created_at, createdAt). Exposes recentMessageText and recentMessageAt; comments document fallback order.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI (List/Widget)
  participant API as GET /api/chat/conversations
  participant Search as searchConversations()
  participant DB as Database

  UI->>API: Request conversations
  API->>Search: Fetch conversations with recent_message lateral join
  Search->>DB: Query (COALESCE lastMessageAt, recent_message.created_at, createdAt)
  DB-->>Search: Rows including lastMessageAt, recent_message.*
  Search-->>API: Conversations with recentMessageAt/Text
  API->>API: Compute latestMessageAt = lastMessageAt ?? recentMessageAt ?? createdAt
  API-->>UI: Serialized conversations (latestMessageAt)
  UI->>UI: Render timestamp from latestMessageAt (fallbacks)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Change displayed conversation date to last message date; ensure ordering/selection uses last activity time [#899]

Out-of-scope changes

No out-of-scope changes found.

Possibly related PRs

Suggested reviewers

  • binary-koan

Poem

I hop through threads and sniff the time,
Freshest hops first, in perfect rhyme.
Not when it started, but newest beat—
I mark the chat where pulses meet. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 21fda64 and c2ea6ab.

📒 Files selected for processing (1)
  • app/(dashboard)/[category]/list/conversationListItem.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/(dashboard)/[category]/list/conversationListItem.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 0

🧹 Nitpick comments (5)
lib/data/conversation.ts (1)

237-239: Align latestMessageAt semantics with the rest of the PR (fallback to createdAt).

Elsewhere (search query + widget API), latestMessageAt falls back to createdAt. Serializing only lastMessageAt here can leave latestMessageAt null for legacy rows (pre-backfill) and during transitional states. Consider aligning to avoid nulls and keep UI logic simpler.

-    latestMessageAt: conversation.lastMessageAt ?? null,
+    // Keep consistent with list/search and widget API semantics
+    latestMessageAt: conversation.lastMessageAt ?? conversation.createdAt,
app/(dashboard)/[category]/list/conversationListItem.tsx (2)

17-17: Remove the any-cast; add a typed recentMessageAt to ListItem.

Avoids unsafe casts and improves maintainability. The list query already supplies recentMessageAt; reflect it in the type and use it directly.

-type ListItem = ConversationListItemType & { isNew?: boolean };
+type ListItem = ConversationListItemType & {
+  isNew?: boolean;
+  // Provided by the lateral join in the list query (Date on server-rendered lists)
+  recentMessageAt?: Date | string | null;
+};

136-141: Follow-up to typing fix: use the typed recentMessageAt instead of any.

After adding recentMessageAt to ListItem, remove the any-cast.

-                          // recentMessageAt comes from the lateral join in the list query
-                          (conversation as any).recentMessageAt ??
+                          // recentMessageAt comes from the lateral join in the list query
+                          conversation.recentMessageAt ??
lib/data/conversation/search.ts (2)

151-160: Ordering by last activity: logic looks right; mind the performance of the lateral join.

The COALESCE chain matches the desired behavior. Since this relies on a LATERAL subquery, ensure indexes exist to keep it fast at scale:

  • conversation_messages (conversation_id, created_at DESC) covering index to speed up ORDER BY created_at LIMIT 1
  • conversations (last_message_at) index already exists (good)
  • conversations (created_at) index already exists (good)

If the first index is missing, consider adding:

CREATE INDEX CONCURRENTLY IF NOT EXISTS conversation_messages_conversation_id_created_at_idx
  ON conversation_messages (conversation_id, created_at DESC);

204-215: Be robust to driver-returned types when parsing recent_message_createdAt.

Depending on the PG driver config, recent_message_createdAt may already be a Date. Guard against double-parsing/locale strings.

-              recentMessageAt: recent_message_createdAt ? new Date(recent_message_createdAt) : null,
+              recentMessageAt: recent_message_createdAt
+                ? recent_message_createdAt instanceof Date
+                  ? recent_message_createdAt
+                  : new Date(recent_message_createdAt)
+                : null,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8e09ef8 and c46f785.

📒 Files selected for processing (5)
  • app/(dashboard)/[category]/list/conversationListItem.tsx (1 hunks)
  • app/api/chat/conversations/route.ts (1 hunks)
  • components/widget/PreviousConversations.tsx (2 hunks)
  • lib/data/conversation.ts (1 hunks)
  • lib/data/conversation/search.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx

📄 CodeRabbit Inference Engine (.cursorrules)

Name component files in lowerCamelCase, e.g. conversationList.tsx

Files:

  • app/(dashboard)/[category]/list/conversationListItem.tsx
  • components/widget/PreviousConversations.tsx
🧬 Code Graph Analysis (1)
lib/data/conversation/search.ts (1)
db/schema/conversations.ts (1)
  • conversations (12-78)
🔇 Additional comments (6)
app/(dashboard)/[category]/list/conversationListItem.tsx (1)

134-143: Good fallback chain for “Last activity” and label update.

The priority order and copy change match the PR intent. No functional concerns here.

app/api/chat/conversations/route.ts (1)

56-68: Confirm ConversationsResult includes latestMessageAt.

You’re returning latestMessageAt in each conversation. Ensure @helperai/client’s ConversationsResult type has this field; otherwise TypeScript will flag excess properties on the nested conversation literals.

Would you confirm the published client type includes:

  • latestMessageAt?: string | null

If not, either update the client package type or introduce a local response type that augments the client one for widget use.

components/widget/PreviousConversations.tsx (2)

13-15: Type extension looks correct and backward-compatible.

Adding latestMessageAt as optional cleanly supports the new server field while preserving compatibility if absent.


131-131: Good fallback to createdAt when latestMessageAt is missing.

This matches the documented behavior for brand-new threads (no replies yet).

lib/data/conversation/search.ts (2)

171-179: Expose recent_message.created_at/text via lateral join: solid approach.

Pulling fields via a lateral join is a good fit here and keeps the main list query cohesive.


182-193: Confirm role filter for “recent_message” excludes AI messages by design.

You’ve limited the recent_message lateral join to ["user", "staff"]. That means:

  • Ordering prefers lastMessageAt (any role) first
  • Else falls back to last human/staff message time

This could bump threads due to AI assistant messages (via lastMessageAt) even when no human/staff activity occurred. If that’s intended, great. If not, consider aligning both sources to the same role set.

Would you like to exclude AI assistant activity from lastMessageAt updates and/or from ordering consistently?

…add typed recentMessageAt on list item to avoid any
Copy link
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

🧹 Nitpick comments (2)
app/(dashboard)/[category]/list/conversationListItem.tsx (2)

138-138: Avoid any-cast; use typed recentMessageAt from the local ListItem type

You already augmented the local ListItem type with recentMessageAt; there’s no need to cast to any here. This preserves type safety and avoids masking future regressions.

Apply this diff:

-                          // recentMessageAt comes from the lateral join in the list query
-                          (conversation as any).recentMessageAt ??
+                          // recentMessageAt comes from the lateral join in the list query
+                          conversation.recentMessageAt ??

17-17: Widen recentMessageAt type to allow both ISO string and Date; aligns with upstream payloads

Data from the API/join often serializes as ISO strings. Make this string | Date | null to match reality and keep the HumanizedTime input flexible.

Apply this diff:

-type ListItem = ConversationListItemType & { isNew?: boolean; recentMessageAt?: Date | null };
+type ListItem = ConversationListItemType & { isNew?: boolean; recentMessageAt?: string | Date | null };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c46f785 and 21fda64.

📒 Files selected for processing (1)
  • app/(dashboard)/[category]/list/conversationListItem.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx

📄 CodeRabbit Inference Engine (.cursorrules)

Name component files in lowerCamelCase, e.g. conversationList.tsx

Files:

  • app/(dashboard)/[category]/list/conversationListItem.tsx
🔇 Additional comments (2)
app/(dashboard)/[category]/list/conversationListItem.tsx (2)

142-142: Label change to “Last activity on” — LGTM

Matches the product copy and the PR’s goal to emphasize recent activity.


134-143: No action required: HumanizedTime supports string | Date inputs

HumanizedTimeProps declares time: string | Date, so it safely handles both ISO strings and Date objects. All current usages (ISO strings via .toISOString(), Date instances from tRPC deserialization) are compatible.

…ast; widen recentMessageAt type to string | Date | null
@notcodesid
Copy link
Author

@slavingia @binary-koan

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.

Change date of conversation in list to last message date
1 participant