Skip to content

Comments

Staging#719

Merged
nizzyabi merged 20 commits intomainfrom
staging
Apr 19, 2025
Merged

Staging#719
nizzyabi merged 20 commits intomainfrom
staging

Conversation

@MrgSub
Copy link
Collaborator

@MrgSub MrgSub commented Apr 18, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a reusable recipient input component for email fields, streamlining the "To", "Cc", and "Bcc" input experience.
    • Enhanced search bar with new filters (cc, bcc, has, file name, delivered to, unicorn) and improved natural language parsing for queries and dates.
    • Added semantic search highlighting and display of email body snippets in mail list previews.
    • Implemented keyboard shortcuts for mail list navigation and email archiving.
  • Bug Fixes

    • Improved handling of empty and loading states in the mail list, with clearer feedback and prevention of rendering invalid items.
  • Documentation

    • Added new English localization strings for empty inbox, no search results, and clearing searches.
  • Refactor

    • Centralized recipient input logic into a dedicated component, reducing duplication and improving maintainability.
    • Replaced AI-based search enhancement with local parsing utilities for better reliability and user control.
  • Chores

    • Enhanced search query normalization to preserve user query when filtering by folder.
    • Minor code cleanups and improved keyboard navigation behavior in mail list.
    • Added logging for email fetching parameters to aid debugging.
    • Updated navigation logic to consistently append category query parameter in main navigation links.

@vercel
Copy link

vercel bot commented Apr 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
0 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 19, 2025 4:39am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 18, 2025

Walkthrough

This update refactors the recipient input functionality in the mail application. Inline recipient input logic and UI for "To", "Cc", and "Bcc" fields in the email creation and reply components are removed and replaced with a new reusable EmailInput component. The new component centralizes the management of recipient emails, input values, autocomplete dropdowns, and keyboard navigation. Related state, effects, and handlers are extracted from the main components and consolidated. Additional improvements include enhancements to the mail list's semantic search, search bar parsing, localization strings, navigation hooks, and hotkey handling.

Changes

File(s) Change Summary
apps/mail/components/create/create-email.tsx
apps/mail/components/mail/reply-composer.tsx
Refactored recipient input UI and logic by removing inline JSX, state, and handlers for "to", "cc", and "bcc" fields. Integrated new reusable EmailInput component for recipient management. Updated toggle button logic for Cc/Bcc.
apps/mail/components/create/email-input.tsx Introduced new exported EmailInput component. Handles recipient input, tags, autocomplete dropdown, keyboard navigation, and email addition/removal. Uses props for configuration and state management.
apps/mail/components/mail/mail-list.tsx Enhanced mail list with semantic search parsing and main search term highlighting. Improved empty/loading state handling and UI feedback. Refactored rendering logic for robustness.
apps/mail/components/mail/search-bar.tsx Replaced AI-based query enhancement with local natural language parsing. Added new search filter fields (cc, bcc, has, fileName, deliveredTo, unicorn). Improved handling of quoted phrases and updated UI/state accordingly.
apps/mail/lib/utils.ts Added three new exported utility functions: getMainSearchTerm, parseNaturalLanguageSearch, and parseNaturalLanguageDate for enhanced search query parsing and highlighting.
apps/mail/locales/en.json Added new localization strings: noEmails, noSearchResults, and clearSearch under the "mail" section.
apps/mail/hooks/use-mail-navigation.ts Updated navigation logic for threads, improved keyboard navigation, and added new useMailListHotkeys hook for managing removal state.
apps/mail/lib/hotkeys/mail-list-hotkeys.tsx Added archiveEmail callback to archive hovered emails, update local cache, and display success toast. Integrated handler into mail list hotkeys.
apps/mail/app/api/driver/google.ts Modified normalizeSearch to append original query string to folder-specific Gmail search queries instead of replacing it, preserving additional search criteria.
apps/mail/hooks/use-search-value.ts Changed default values in configAtom from double-quoted empty strings to single-quoted empty strings for value, highlight, and folder.
apps/mail/hooks/use-threads.ts Modified SWR fetcher to ignore some destructured key elements and added console logging of email fetch request parameters before API call.
apps/mail/components/ui/nav-main.tsx Switched to using useQueryState hook for reactive category query parameter. Simplified getHref logic to append category parameter unconditionally when present.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CreateEmail/ReplyCompose
    participant EmailInput
    participant Contacts (future)
    
    User->>CreateEmail/ReplyCompose: Focus recipient field ("to", "cc", "bcc")
    CreateEmail/ReplyCompose->>EmailInput: Render with props (emails, input value, handlers)
    User->>EmailInput: Type or paste email / select from dropdown
    EmailInput->>EmailInput: Handle input, show dropdown, navigate with keyboard
    User->>EmailInput: Press Enter/Tab/Space or click contact
    EmailInput->>CreateEmail/ReplyCompose: Call addEmail handler
    CreateEmail/ReplyCompose->>EmailInput: Update emails prop
    EmailInput->>User: Display updated email tags
Loading

Possibly related PRs

  • New create email #684: Implements inline recipient input UI with autocomplete and dropdowns in CreateEmail; this PR modularizes and replaces that implementation.
  • Release 0.92 #631: Updates sendEmail function and recipient array handling, related to recipient management changes in mail creation flow.
  • Staging #692: Refactors CreateEmail recipient input UI with detailed contact filtering and keyboard navigation, which this PR replaces with reusable EmailInput components.

Suggested labels

design

Suggested reviewers

  • ahmetskilinc
  • nizzyabi

Poem

A bunny hopped through fields of code,
Tidying up where emails flowed.
From scattered forms to one neat place,
Recipients now share a single face.
Search grew wise, with terms that gleam,
And archiving's now a seamless dream.
🐇✨ Hooray for code that's clean and bright—
The mailbox feels just right!

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 132f897 and d2b7120.

📒 Files selected for processing (1)
  • apps/mail/components/mail/reply-composer.tsx (14 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/components/mail/reply-composer.tsx

🪧 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.
    • Generate unit testing code for this file.
    • 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. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 4

🧹 Nitpick comments (4)
apps/mail/components/create/email-input.tsx (2)

31-33: dropdownRef is never read – remove or use it.

The ref is attached to the DOM node but there are no reads (dropdownRef.current) or side‑effects that depend on it.
Stale refs add noise and hint at dead code; consider deleting the ref or using it for imperative scroll/focus logic.


110-126: Add ARIA roles for better accessibility.

The suggestion buttons behave like listbox options. Expose this to screen‑readers:

-  <button
+  <button
+    role="option"

Consider wrapping the dropdown in a container with role="listbox" and wiring aria-selected to the highlighted item.

apps/mail/components/mail/reply-composer.tsx (2)

219-229: Duplicate filterContacts implementation – extract to a shared util.

CreateEmail and ReplyCompose now hold identical filtering logic. Centralise it (e.g. lib/mail/filterContacts.ts) to:

  1. Avoid divergence.
  2. Reduce bundle size.
  3. Simplify future changes (e.g., fuzzy‑matching).

352-358: composerState loading guard works, but awaiting handleSendEmail hides submission errors.

If handleSendEmail throws, .finally still clears the loading flag.
Consider:

-  await handleSendEmail(data).finally(() => {
-    composerDispatch({ type: 'SET_LOADING', payload: false });
-  });
+  try {
+    await handleSendEmail(data);
+  } finally {
+    composerDispatch({ type: 'SET_LOADING', payload: false });
+  }

This pattern preserves the same behaviour yet is easier to read/debug.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 618300a and f841e24.

📒 Files selected for processing (3)
  • apps/mail/components/create/create-email.tsx (2 hunks)
  • apps/mail/components/create/email-input.tsx (1 hunks)
  • apps/mail/components/mail/reply-composer.tsx (12 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
apps/mail/components/create/create-email.tsx (1)
apps/mail/components/create/email-input.tsx (1)
  • EmailInput (18-132)
apps/mail/components/create/email-input.tsx (1)
apps/mail/components/create/editor-autocomplete.ts (1)
  • handleKeyDown (166-212)
apps/mail/components/mail/reply-composer.tsx (4)
apps/mail/components/mail/data.tsx (1)
  • contacts (219-300)
apps/mail/hooks/use-contacts.ts (1)
  • useContacts (6-35)
apps/mail/types/index.ts (1)
  • Sender (32-35)
apps/mail/components/create/email-input.tsx (1)
  • EmailInput (18-132)
🔇 Additional comments (1)
apps/mail/components/mail/reply-composer.tsx (1)

1190-1191: Button uses type="button" + manual handleSubmit – switch to type="submit.

With the enclosing <form> already wired to onSubmit={handleSubmit(onSubmit)}, you can rely on native form submission:

-  onClick={handleSubmit(onSubmit)}
-  type="button"
+  type="submit"

This removes an extra function allocation per render and avoids double‑calling handleSubmit if the user presses Enter inside a field.

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: 4

♻️ Duplicate comments (1)
apps/mail/components/create/create-email.tsx (1)

471-748: Remove unused state and effects mentioned in previous review.

As noted in a previous review, you should also remove the now-unused state variables like selectedContactIndex*, *DropdownRef, and the three useEffect scroll handlers that were used for the previous implementation.

🧰 Tools
🪛 Biome (1.9.4)

[error] 722-722: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed33a1 and 85ea705.

📒 Files selected for processing (1)
  • apps/mail/components/create/create-email.tsx (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/mail/components/create/create-email.tsx (1)
apps/mail/components/create/email-input.tsx (1)
  • EmailInput (18-132)
🔇 Additional comments (1)
apps/mail/components/create/create-email.tsx (1)

28-28: LGTM: EmailInput component imported correctly.

The new EmailInput component is properly imported from the local module.

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: 3

🧹 Nitpick comments (6)
apps/mail/lib/utils.ts (2)

433-437: Prefer optional chaining to satisfy linter and simplify the condition

match && match[1] can be replaced with match?.[1] which is terser and addresses the Biome useOptionalChain hint.

-    if (match && match[1]) {
+    if (match?.[1]) {
       return match[1];
     }
🧰 Tools
🪛 Biome (1.9.4)

[error] 436-437: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


505-527: Duplicate monthNames arrays – extract a shared constant

Both date patterns rebuild the monthNames list. Moving it outside the patterns array avoids repeated allocation and keeps the code DRY.

+const MONTH_NAMES = ['january','february','march','april','may','june',
+  'july','august','september','october','november','december'];
 ...
-        const monthNames = ['january', ...];
-        const monthIndex = monthNames.findIndex(...)
+        const monthIndex = MONTH_NAMES.findIndex(...)
apps/mail/components/mail/search-bar.tsx (2)

235-241: New form fields (cc, bcc, …) never reach the final query

cc, bcc, and several others are collected in state but never pushed into searchTerms.
If this is intentional, remove them from SearchForm; otherwise add the missing filters:

+if (data.cc)  searchTerms.push(`cc:${data.cc.toLowerCase()}`);
+if (data.bcc) searchTerms.push(`bcc:${data.bcc.toLowerCase()}`);

697-697: select-none prevents users from copy/pasting inside the search box

Applying select-none to the <Input> element stops text selection entirely, which degrades usability (e.g. copying part of the query).
Unless this was a deliberate design choice, consider removing the class.

apps/mail/components/mail/mail-list.tsx (2)

118-126: Effect may fire unnecessarily & cause double renders

setSearchValue inside the effect depends on searchValue but it’s not listed in the deps array, while semanticSearchQuery is.
When the hook updates the parent atom, searchValue.value changes and semanticSearchQuery becomes identical, but the effect won’t re‑run. Include the setter’s stable reference and the value to avoid stale closures:

-  }, [semanticSearchQuery]);
+  }, [semanticSearchQuery, searchValue.value, setSearchValue]);

379-390: Duplicate snippet / badge markup – extract into a small component

The highlighted body preview and “main term” badge are rendered twice (demo vs. real thread). To keep both branches in sync and cut duplication, move lines 377‑390 into a ThreadPreview child component.

This also makes it easier to adjust styling in one place.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d7ded10 and 4a53617.

📒 Files selected for processing (4)
  • apps/mail/components/mail/mail-list.tsx (8 hunks)
  • apps/mail/components/mail/search-bar.tsx (7 hunks)
  • apps/mail/lib/utils.ts (1 hunks)
  • apps/mail/locales/en.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/mail/locales/en.json
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/mail/components/mail/search-bar.tsx (2)
apps/mail/hooks/use-search-value.ts (1)
  • useSearchValue (19-21)
apps/mail/lib/utils.ts (2)
  • parseNaturalLanguageDate (496-544)
  • parseNaturalLanguageSearch (455-494)
🪛 Biome (1.9.4)
apps/mail/lib/utils.ts

[error] 436-437: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Comment on lines +458 to +472
// From pattern
{
regex: /^from\s+([^:\s]+)/i,
transform: (match: string[]) => `from:${match[1]}`
},
// To pattern
{
regex: /^to\s+([^:\s]+)/i,
transform: (match: string[]) => `to:${match[1]}`
},
// Subject pattern
{
regex: /^subject\s+([^:\s]+)/i,
transform: (match: string[]) => `subject:${match[1]}`
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Natural‑language patterns only capture a single word – multi‑word queries will be truncated

The current regexes stop matching at the first whitespace, so a query like
"subject quarterly earnings" becomes subject:quarterly instead of subject:"quarterly earnings".

Consider capturing everything to the end of the string (optionally trimming quotes) so the intent isn’t lost:

-      regex: /^subject\s+([^:\s]+)/i,
-      transform: (m) => `subject:${m[1]}`
+      regex: /^subject\s+(.+)/i,
+      transform: (m) => `subject:"${m[1].trim()}"`   // quote to keep spaces

Apply the same idea to the from and to patterns or add a second pattern that is not anchored with ^ for in‑sentence matches.

📝 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
// From pattern
{
regex: /^from\s+([^:\s]+)/i,
transform: (match: string[]) => `from:${match[1]}`
},
// To pattern
{
regex: /^to\s+([^:\s]+)/i,
transform: (match: string[]) => `to:${match[1]}`
},
// Subject pattern
{
regex: /^subject\s+([^:\s]+)/i,
transform: (match: string[]) => `subject:${match[1]}`
},
// From pattern
{
regex: /^from\s+([^:\s]+)/i,
transform: (match: string[]) => `from:${match[1]}`
},
// To pattern
{
regex: /^to\s+([^:\s]+)/i,
transform: (match: string[]) => `to:${match[1]}`
},
// Subject pattern
{
regex: /^subject\s+(.+)/i,
transform: (m) => `subject:"${m[1].trim()}"` // quote to keep spaces
},

Comment on lines +80 to 89
bcc: string;
q: string;
dateRange: DateRange;
category: string;
folder: string;
has: any;
fileName: any;
deliveredTo: string;
unicorn: string;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid any in the form schema – use explicit types

has and fileName are declared as any, defeating type‑safety. They appear to hold simple strings.

-  has: any;
-  fileName: any;
+  has: string;
+  fileName: string;

Update the corresponding default values ('') to maintain consistency.

📝 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
bcc: string;
q: string;
dateRange: DateRange;
category: string;
folder: string;
has: any;
fileName: any;
deliveredTo: string;
unicorn: string;
};
bcc: string;
q: string;
dateRange: DateRange;
category: string;
folder: string;
- has: any;
- fileName: any;
+ has: string;
+ fileName: string;
deliveredTo: string;
unicorn: string;
};

import { preloadThread, useThread, useThreads } from '@/hooks/use-threads';
import { ThreadContextMenu } from '@/components/context/thread-context';
import { cn, FOLDERS, formatDate, getEmailLogo } from '@/lib/utils';
import { cn, FOLDERS, formatDate, getEmailLogo, getMainSearchTerm, parseNaturalLanguageSearch, parseNaturalLanguageDate } from '@/lib/utils';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused import will break noUnusedLocals builds

parseNaturalLanguageDate is imported but never used in this file – TypeScript’s noUnusedLocals/eslint no-unused-vars rules will fail CI.

-import { cn, FOLDERS, formatDate, getEmailLogo, getMainSearchTerm, parseNaturalLanguageSearch, parseNaturalLanguageDate } from '@/lib/utils';
+import { cn, FOLDERS, formatDate, getEmailLogo, getMainSearchTerm, parseNaturalLanguageSearch } from '@/lib/utils';
📝 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
import { cn, FOLDERS, formatDate, getEmailLogo, getMainSearchTerm, parseNaturalLanguageSearch, parseNaturalLanguageDate } from '@/lib/utils';
import { cn, FOLDERS, formatDate, getEmailLogo, getMainSearchTerm, parseNaturalLanguageSearch } from '@/lib/utils';

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: 2

🧹 Nitpick comments (2)
apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (1)

24-24: Consider usage of destructured state variables.

You're correctly destructuring [removingEmails, setRemovingEmails] from the hook, but these variables don't appear to be used in the component. If they're intended for future functionality, consider adding a TODO comment explaining the future use case, or remove them if they're not needed.

apps/mail/hooks/use-mail-navigation.ts (1)

164-166: Direct navigation on Enter key.

The direct call to onNavigate bypasses the conditional check in navigateToThread, which allows navigation even when no thread is currently open. This creates inconsistent behavior compared to arrow key navigation.

Consider using the same navigation pattern for consistency:

- const message = items[focusedIndex];
- const threadId = message.threadId ?? message.id;
- onNavigate(threadId);
+ navigateToThread(focusedIndex);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4a53617 and 19a3e19.

📒 Files selected for processing (3)
  • apps/mail/hooks/use-mail-list-hotkeys.ts (1 hunks)
  • apps/mail/hooks/use-mail-navigation.ts (5 hunks)
  • apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (1)
apps/mail/hooks/use-mail-list-hotkeys.ts (1)
  • useMailListHotkeys (3-7)
🔇 Additional comments (7)
apps/mail/hooks/use-mail-list-hotkeys.ts (1)

1-7: Well-structured hook implementation!

This hook follows React best practices with a clean implementation that manages email removal state. The use of a Set for handling unique email IDs and the as const assertion for the return type are good TypeScript practices.

apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (2)

4-4: LGTM! Proper hook import.

The hook is correctly imported from its dedicated file.


98-98: LGTM! Proper integration with handlers.

The archiveEmail function is correctly added to the handlers object.

apps/mail/hooks/use-mail-navigation.ts (4)

70-75: Improved navigation logic.

The conditional navigation is a good improvement. By checking if a thread is already open before navigating, you prevent unexpected UI changes when using keyboard navigation.


101-107: Consistent navigation handling.

Good improvement to call navigateToThread after scrolling the thread into view, ensuring navigation aligns with visual focus.


112-113: LGTM! Consistent navigation pattern.

The pattern of scrolling into view and then navigating is consistently applied.


131-137: LGTM! Consistent navigation pattern.

The pattern of scrolling into view and then navigating is consistently applied in all arrow key handlers.

Comment on lines +83 to +93
const archiveEmail = useCallback(async () => {
const emailId = hoveredEmailId.current;
if (!emailId) return;

// Immediately remove the email from the list
const updatedItems = items.filter((item) => item.id !== emailId);
mutate({ threads: updatedItems, nextPageToken: null }, false);

// Show success message
toast.success(t('common.mail.archived'));
}, [items, mutate, t]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing server-side archive action.

The archiveEmail function updates the local UI state by filtering out the archived email from the list, but there doesn't appear to be a server-side API call to actually archive the email on the backend. This could lead to inconsistency when the user refreshes the page.

Consider adding a server-side archive action similar to how markAsUnreadAction is called:

const archiveEmail = useCallback(async () => {
  const emailId = hoveredEmailId.current;
  if (!emailId) return;

+  // Call server-side archive action
+  toast.promise(archiveEmailAction({ ids: [emailId] }), {
+    loading: t('common.actions.loading'),
+    success: async () => {
      // Immediately remove the email from the list
      const updatedItems = items.filter((item) => item.id !== emailId);
      mutate({ threads: updatedItems, nextPageToken: null }, false);

-      // Show success message
-      toast.success(t('common.mail.archived'));
+      return t('common.mail.archived');
+    },
+    error: t('common.mail.failedToArchive'),
+  });
}, [items, mutate, t]);
📝 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 archiveEmail = useCallback(async () => {
const emailId = hoveredEmailId.current;
if (!emailId) return;
// Immediately remove the email from the list
const updatedItems = items.filter((item) => item.id !== emailId);
mutate({ threads: updatedItems, nextPageToken: null }, false);
// Show success message
toast.success(t('common.mail.archived'));
}, [items, mutate, t]);
const archiveEmail = useCallback(async () => {
const emailId = hoveredEmailId.current
if (!emailId) return
// Call server-side archive action
toast.promise(archiveEmailAction({ ids: [emailId] }), {
loading: t('common.actions.loading'),
success: async () => {
// Immediately remove the email from the list
const updatedItems = items.filter((item) => item.id !== emailId)
mutate({ threads: updatedItems, nextPageToken: null }, false)
return t('common.mail.archived')
},
error: t('common.mail.failedToArchive'),
})
}, [items, mutate, t])

Comment on lines +313 to +317
export default function useMailListHotkeys() {
const [removingEmails, setRemovingEmails] = useState<Set<string>>(new Set());

return [removingEmails, setRemovingEmails] as const;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate hook implementation.

This hook implementation is identical to the one in apps/mail/hooks/use-mail-list-hotkeys.ts. Having two identical implementations creates confusion and maintenance issues.

Remove this duplicate and import the hook from the dedicated file:

- export default function useMailListHotkeys() {
-   const [removingEmails, setRemovingEmails] = useState<Set<string>>(new Set());
-
-   return [removingEmails, setRemovingEmails] as const;
- } 
+ export { useMailListHotkeys as default } from './use-mail-list-hotkeys';

…fic searches, improving search bar state management, and refining search value handling across components.
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

♻️ Duplicate comments (1)
apps/mail/components/mail/search-bar.tsx (1)

79-88: Avoid any in the form schema – use explicit types.

has and fileName are declared as any, defeating type‑safety. They appear to hold simple strings.

-  has: any;
-  fileName: any;
+  has: string;
+  fileName: string;

Update the corresponding default values ('') to maintain consistency.

🧹 Nitpick comments (4)
apps/mail/hooks/use-threads.ts (1)

102-107: Added debug logging for email fetching parameters.

This console log provides visibility into the parameters being used for fetching emails, which is helpful for troubleshooting.

Consider using a conditional debug flag to control these logs in production:

- console.log('Fetching emails with params:', {
+ if (process.env.NODE_ENV !== 'production') {
+   console.log('Fetching emails with params:', {
      q: query,
      folder,
      max: max?.toString() ?? defaultPageSize.toString(),
      pageToken: pageToken ?? '',
+   });
  });
apps/mail/components/mail/search-bar.tsx (3)

249-265: Refactor duplicate search parsing logic.

The error handling path contains very similar logic to the main search path. Consider extracting this to a shared helper function to avoid duplication.

+ const createSearchQuery = (searchTerm: string): string => {
+   const parsedTerm = parseNaturalLanguageSearch(searchTerm);
+   if (parsedTerm !== searchTerm) {
+     return parsedTerm;
+   } else {
+     if (searchTerm.includes('@')) {
+       return `from:${searchTerm}`;
+     } else {
+       return `(from:${searchTerm} OR from:"${searchTerm}" OR subject:"${searchTerm}" OR "${searchTerm}")`;
+     }
+   }
+ };

  // In try block
- const parsedTerm = parseNaturalLanguageSearch(searchTerm);
- if (parsedTerm !== searchTerm) {
-   searchTerms.push(parsedTerm);
- } else {
-   if (searchTerm.includes('@')) {
-     searchTerms.push(`from:${searchTerm}`);
-   } else {
-     searchTerms.push(
-       `(from:${searchTerm} OR from:"${searchTerm}" OR subject:"${searchTerm}" OR "${searchTerm}")`,
-     );
-   }
- }
+ searchTerms.push(createSearchQuery(searchTerm));

  // In catch block
- const parsedTerm = parseNaturalLanguageSearch(searchTerm);
- if (parsedTerm !== searchTerm) {
-   searchTerms.push(parsedTerm);
- } else {
-   if (searchTerm.includes('@')) {
-     searchTerms.push(`from:${searchTerm}`);
-   } else {
-     searchTerms.push(
-       `(from:${searchTerm} OR from:"${searchTerm}" OR subject:"${searchTerm}" OR "${searchTerm}")`,
-     );
-   }
- }
+ searchTerms.push(createSearchQuery(searchTerm));

234-240: Consider removing detailed debug logs before production.

While valuable during development, these detailed logs should be conditional in production environments.

- console.log('Final search query:', {
+ if (process.env.NODE_ENV !== 'production') {
+   console.log('Final search query:', {
      value: searchQuery,
      highlight: data.q,
      folder: data.folder ? data.folder.toUpperCase() : '',
      isLoading: true,
      isAISearching: false,
+   });
  });

727-934: Large commented-out code block should be removed.

There's a substantial block of commented-out code related to the advanced search UI. If this functionality is no longer needed, the code should be removed to improve maintainability. If it might be needed later, consider moving it to a separate file or document.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6afbb9e and 4c7121b.

📒 Files selected for processing (5)
  • apps/mail/app/api/driver/google.ts (1 hunks)
  • apps/mail/components/mail/mail-list.tsx (9 hunks)
  • apps/mail/components/mail/search-bar.tsx (11 hunks)
  • apps/mail/hooks/use-search-value.ts (1 hunks)
  • apps/mail/hooks/use-threads.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • apps/mail/hooks/use-search-value.ts
  • apps/mail/app/api/driver/google.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/components/mail/mail-list.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/mail/hooks/use-threads.ts (1)
apps/mail/lib/utils.ts (1)
  • defaultPageSize (143-143)
apps/mail/components/mail/search-bar.tsx (2)
apps/mail/hooks/use-search-value.ts (1)
  • useSearchValue (19-21)
apps/mail/lib/utils.ts (2)
  • parseNaturalLanguageDate (496-544)
  • parseNaturalLanguageSearch (455-494)
🔇 Additional comments (9)
apps/mail/hooks/use-threads.ts (1)

95-95: Refactored destructuring of the key parameter.

The destructuring now uses placeholder commas to skip the first and fifth elements of the array rather than assigning them to specific variables. This is cleaner and indicates these values aren't needed in this scope.

apps/mail/components/mail/search-bar.tsx (8)

2-2: Switched to local natural language parsing utilities.

Using local parsing functions instead of remote AI enhancement is a good architectural decision that reduces external dependencies and should improve response times.


24-26: Updated search suggestions with more helpful examples.

The new suggestions better reflect the enhanced search capabilities, showing users the types of searches they can perform.


100-111: Added new form fields matching the expanded search capabilities.

The state now tracks more field types, properly initializing them to empty strings and reflecting the SearchForm type definition.


178-221: Implemented local natural language parsing for search queries.

The code now properly handles:

  • Parsing natural language date queries
  • Extracting date ranges and converting them to Gmail query format
  • Cleaning up the query text after date extraction
  • Processing special search patterns

This is a significant improvement that provides better search functionality without relying on external services.


224-229: Added support for additional search filters.

New filters for folder, has, fileName, deliveredTo, and unicorn enrich the search capabilities. The implementation correctly formats each filter according to Gmail's query syntax.


295-307: Improved handling of quoted phrases in input.

The code now detects when the cursor is inside quoted text and suppresses suggestions in this context. This provides a better user experience by not interrupting the user's flow when typing specific phrases.


694-694: Added select-none class to search input.

This prevents accidental text selection when interacting with the search input, improving the user experience.


964-964: Added null check to prevent potential runtime error.

The added check prevents accessing properties of an undefined match result, which could lead to runtime errors.

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 (3)
apps/mail/components/mail/reply-composer.tsx (3)

37-37: Verify if useContacts hook is needed.

The useContacts hook is imported but not actively used in the component. The filtered contact logic (lines 243-256) that would use it is commented out.

If this refactoring intentionally removes the contacts functionality, you can remove this unused import:

-import { useContacts } from '@/hooks/use-contacts';

243-256: Consider removing or uncommenting the filtered contacts logic.

These commented blocks are either leftover code or planned for future implementation. If they're not needed, consider removing them for cleaner code.

If you plan to use this functionality later, consider adding a TODO comment explaining why it's commented out.


949-949: Consider restoring keyboard shortcuts for sending email.

Line 949 comments out the keyboard handler, and line 1188 updates the submit button to use handleSubmit(onSubmit). However, without the keyboard handler, users can't use Cmd/Ctrl+Enter to send emails.

Consider re-implementing the keyboard shortcut:

-// onKeyDown={handleKeyDown}
+onKeyDown={(e) => {
+  if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
+    e.preventDefault();
+    if (!composerState.isLoading) {
+      handleSubmit(onSubmit)();
+    }
+  }
+}}

Also applies to: 1188-1188

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4c7121b and 0893d68.

📒 Files selected for processing (3)
  • apps/mail/components/create/create-email.tsx (4 hunks)
  • apps/mail/components/create/email-input.tsx (1 hunks)
  • apps/mail/components/mail/reply-composer.tsx (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/components/create/email-input.tsx
  • apps/mail/components/create/create-email.tsx
🔇 Additional comments (7)
apps/mail/components/mail/reply-composer.tsx (7)

216-218: LGTM - Form state tracking for input fields.

These watch calls properly track the input field values which are needed for the EmailInput components.


220-229: Nice utility function for contact filtering.

The function is well implemented with proper null checks and case insensitivity.


231-241: Well structured email handling function.

This function properly validates emails, prevents duplicates, and maintains form state. Good practice to trim input and mark unsaved changes.


258-258: Good refactoring of the handleSendEmail function.

Accepting form values as a parameter rather than using getValues() internally makes the function more explicit and testable.

Also applies to: 301-301


354-358: Improved async submission flow with loading state.

Good implementation of loading state management that prevents concurrent submissions.


720-733: Great refactoring using the EmailInput component.

Using the reusable EmailInput component for all recipient fields creates a more consistent UI and centralizes the recipient handling logic. This is a significant improvement to maintainability.

Also applies to: 736-749, 753-766


962-962: Good accessibility improvement.

Adding tabIndex={-1} to buttons that should be skipped in keyboard navigation improves accessibility.

Also applies to: 983-983

Comment on lines +487 to +503
// // Add a function to handle starting the resize
// const handleResizeStart = (e: React.MouseEvent) => {
// setIsResizing(true);
// resizeStartY.current = e.clientY;
// startHeight.current = editorHeight;
// };

// // Handle keyboard shortcuts for sending email
// const handleKeyDown = (e: React.KeyboardEvent) => {
// // Check for Cmd/Ctrl + Enter
// if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
// e.preventDefault();
// if (isFormValid) {
// handleSubmit(onSubmit)();
// }
// }
// };
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Reconsider commenting out these utility functions.

The editor resizing and keyboard shortcut handling functions have been commented out rather than removed. If these features are useful, consider reimplementing them.


🏁 Script executed:

#!/bin/bash
# Check if these features are implemented elsewhere
echo "Checking for editor resize and keyboard handling implementation elsewhere"
rg -A 5 "handleResizeStart|handleKeyDown.*Enter" --type tsx

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Retry search for handleResizeStart and handleKeyDown without specifying file types
echo "Searching for commented out utility functions across the codebase"
rg -A5 "handleResizeStart|handleKeyDown" .

Length of output: 10768


Address commented-out utilities in reply-composer.tsx

The two local helpers—handleResizeStart (drag‐to‐resize) and handleKeyDown (Cmd/Ctrl + Enter send)—are currently only defined here and have no live implementation elsewhere. Please either fully remove these commented blocks or restore them properly so the reply editor supports:

  • Drag‐to‐resize functionality
  • Cmd/Ctrl + Enter keyboard shortcut for sending

Location to update:

  • apps/mail/components/mail/reply-composer.tsx lines ~487–503
- //   const handleResizeStart = (e: React.MouseEvent) => {
- //     setIsResizing(true);
- //     resizeStartY.current = e.clientY;
- //     startHeight.current = editorHeight;
- //   };
+  // TODO: re-enable or remove drag‐to‐resize logic here

- //   const handleKeyDown = (e: React.KeyboardEvent) => {
- //     if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
- //       e.preventDefault();
- //       if (isFormValid) {
- //         handleSubmit(onSubmit)();
- //       }
- //     }
- //   };
+  // TODO: re-enable or remove Cmd/Ctrl+Enter send shortcut here
📝 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
// // Add a function to handle starting the resize
// const handleResizeStart = (e: React.MouseEvent) => {
// setIsResizing(true);
// resizeStartY.current = e.clientY;
// startHeight.current = editorHeight;
// };
// // Handle keyboard shortcuts for sending email
// const handleKeyDown = (e: React.KeyboardEvent) => {
// // Check for Cmd/Ctrl + Enter
// if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
// e.preventDefault();
// if (isFormValid) {
// handleSubmit(onSubmit)();
// }
// }
// };
// TODO: re-enable or remove drag‐to‐resize logic here
// TODO: re-enable or remove Cmd/Ctrl+Enter send shortcut here

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

♻️ Duplicate comments (1)
apps/mail/components/mail/search-bar.tsx (1)

85-88: Avoid any – keep the form schema type‑safe

has and fileName are still typed as any.
This silently disables type‑checking and was already pointed out in a previous review.
Declare them as string (or a dedicated union if the set of allowed values is finite).

🧹 Nitpick comments (1)
apps/mail/components/mail/search-bar.tsx (1)

292-305: Quoted‑phrase detection mis‑identifies “inside quotes”

The current check counts how many fragments the substring before the last quote splits into:

const isOpeningQuote =
  textBeforeCursor.slice(0, lastQuoteIndex).split('"').length % 2 === 0;

This returns false for He said "hello (only one quote so far), meaning the algorithm thinks we are outside a quoted phrase, while we are actually inside.

A simpler & reliable approach is to count quotes directly:

const quotesBeforeCursor = (textBeforeCursor.match(/"/g) ?? []).length;
const insideQuotedPhrase = quotesBeforeCursor % 2 === 1;

Then suppress suggestions when insideQuotedPhrase is true.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2ce0977 and 132f897.

📒 Files selected for processing (3)
  • apps/mail/app/api/driver/google.ts (2 hunks)
  • apps/mail/components/mail/search-bar.tsx (12 hunks)
  • apps/mail/components/ui/nav-main.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/mail/app/api/driver/google.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/mail/components/mail/search-bar.tsx (2)
apps/mail/hooks/use-search-value.ts (1)
  • useSearchValue (19-21)
apps/mail/lib/utils.ts (2)
  • parseNaturalLanguageDate (496-544)
  • parseNaturalLanguageSearch (455-494)
🔇 Additional comments (4)
apps/mail/components/ui/nav-main.tsx (4)

22-22: Good addition of the nuqs library for query state management.

Adding useQueryState from nuqs provides a more reactive approach to managing URL query parameters compared to directly accessing them from useSearchParams.


59-59: Great refactoring to reactive query state.

Replacing the direct access to search parameters with useQueryState creates a reactive binding to the URL state, which improves state synchronization across the component.


121-123: Clean simplification of URL construction logic.

The conditional logic for appending the category parameter has been simplified to always append it when present, which standardizes URL construction for navigation links across the application.


127-127: Correctly updated dependency array.

Adding category to the dependency array of useCallback ensures the function is properly re-memoized when the category changes, maintaining consistency with the reactive pattern.

Comment on lines 228 to +234
let searchQuery = searchTerms.join(' ');
searchQuery = extractMetaText(searchQuery) || '';

searchQuery = extractMetaText(searchQuery) || ''

const folder = data.folder ? data.folder.toUpperCase() : '';
console.log('Final search query:', {
value: searchQuery,
highlight: data.q,
folder: data.folder ? data.folder.toUpperCase() : '',
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

extractMetaText wipes out the composed Gmail query

extractMetaText is a helper for parsing AI chat responses, but here it’s invoked on the final query that you just built from the user form.
For inputs like

after:2024/03/01 before:2024/03/31 from:alice@example.com

the fallback branch inside extractMetaText reduces the string to only after: (see the ^.*?(from:|to:|subject:|is:|has:|after:|before:) replace).
That corrupts the query before it is stored in global state, leading to empty or broken search results.

Proposed fix: skip the call entirely for locally‑constructed queries.

-        let searchQuery = searchTerms.join(' ');
-        searchQuery = extractMetaText(searchQuery) || '';
+        const searchQuery = searchTerms.join(' ');

(You can still keep the helper for the AI‑flow, but behind an explicit flag.)

📝 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
let searchQuery = searchTerms.join(' ');
searchQuery = extractMetaText(searchQuery) || '';
searchQuery = extractMetaText(searchQuery) || ''
const folder = data.folder ? data.folder.toUpperCase() : '';
console.log('Final search query:', {
value: searchQuery,
highlight: data.q,
folder: data.folder ? data.folder.toUpperCase() : '',
// build the query directly without AI‐flow parsing
const searchQuery = searchTerms.join(' ');
console.log('Final search query:', {
value: searchQuery,
highlight: data.q,
folder: data.folder ? data.folder.toUpperCase() : '',
});

@nizzyabi nizzyabi merged commit c22b63b into main Apr 19, 2025
4 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.

2 participants