Skip to content

Comments

feat: Auto-Save , Update and Delete Drafts#1807

Closed
AnjanyKumarJaiswal wants to merge 25 commits intoMail-0:stagingfrom
AnjanyKumarJaiswal:feat/autoSave_Email_drafts
Closed

feat: Auto-Save , Update and Delete Drafts#1807
AnjanyKumarJaiswal wants to merge 25 commits intoMail-0:stagingfrom
AnjanyKumarJaiswal:feat/autoSave_Email_drafts

Conversation

@AnjanyKumarJaiswal
Copy link

@AnjanyKumarJaiswal AnjanyKumarJaiswal commented Jul 23, 2025

Auto Save , Auto Update and delete drafts

  • this allows the user to save drafts without needing of tension about loosing it and delete the draft at any moment it has an auto update feature where it side by side updates the draft as the user keeps on writing the mail

Type of Change

Please delete options that are not relevant.

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature with breaking changes)
  • 📝 Documentation update
  • 🎨 UI/UX improvement
  • 🔒 Security enhancement
  • ⚡ Performance improvement

Areas Affected

Please check all that apply:

  • Email Integration (Gmail, IMAP, etc.)
  • User Interface/Experience
  • Authentication/Authorization
  • Data Storage/Management
  • API Endpoints
  • Documentation
  • Testing Infrastructure
  • Development Workflow
  • Deployment/Infrastructure

Testing Done

Describe the tests you've done:

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • Cross-browser testing (if UI changes)
  • Mobile responsiveness verified (if UI changes)

Security Considerations

For changes involving data or authentication:

  • No sensitive data is exposed
  • Authentication checks are in place
  • Input validation is implemented
  • Rate limiting is considered (if applicable)

Checklist

  • I have read the CONTRIBUTING document
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in complex areas
  • I have updated the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix/feature works
  • All tests pass locally
  • Any dependent changes are merged and published

Additional Notes

Add any other context about the pull request here.

Screenshots/Recordings

mail0.drafts.section-1753286890131.mp4

By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.


Summary by cubic

Added auto-save, update, and delete features for email drafts. Drafts now save automatically as you write, and you can delete drafts at any time.

  • New Features
    • Auto-save drafts every few seconds while composing.
    • Update existing drafts with new changes.
    • Delete drafts directly from the composer.

Summary by CodeRabbit

  • New Features

    • Added the ability to update and delete email drafts directly from the email composer.
    • Introduced auto-saving of drafts after changes and before closing the composer.
    • Added confirmation dialogs and toast notifications for draft actions.
  • Improvements

    • Threads and drafts now refresh automatically after relevant actions, ensuring up-to-date information.
    • Enhanced user interface for draft management, including clearer discard and delete options.
  • Bug Fixes

    • Removed loading spinner for drafts, allowing immediate access to the email composer.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 23, 2025

Bug Report

Name: Inconsistent attachment handling between Google and Microsoft drivers

Severity: High

Example test case:

  1. Create a draft email with attachments using a Microsoft connection.
  2. Save the draft.
  3. The attachments will not be saved or sent correctly because the Microsoft driver expects a File object but receives an object with a base64 string.

Description:
The serializeFiles function returns an array of objects with base64 encoded file data. The Google driver's createDraft and updateDraft functions correctly handle this format. However, the Microsoft driver expects File objects in the attachments array, and then calls arrayBuffer() on the File object. This inconsistency will cause attachments to fail when using a Microsoft connection. The serializeFiles function should return an array of File objects.

Comments? Email us. Your free trial ends in 6 days.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 23, 2025

Walkthrough

This change implements full draft management for mail, including the ability to update and delete drafts across the frontend and backend. It introduces new methods and mutations for updating and deleting drafts, updates the UI to support auto-saving and discarding drafts, and refactors control flow for thread and draft data refreshes.

Changes

File(s) Change Summary
Mail UI Components
apps/mail/components/create/create-email.tsx, apps/mail/components/create/email-composer.tsx
Added draft update/delete logic, auto-save, discard flow, thread/draft refetch, removed draft loading spinner, UI adjustments.
Mail Drivers
apps/server/src/lib/driver/google.ts, apps/server/src/lib/driver/microsoft.ts
Refactored draft creation, added updateDraft and deleteDraft methods for Google/Outlook, improved error handling and code clarity.
Driver Interface Types
apps/server/src/lib/driver/types.ts
Extended MailManager interface with updateDraft and deleteDraft methods.
Agent Layer
apps/server/src/routes/agent/index.ts, apps/server/src/routes/agent/rpc.ts
Added updateDraft and deleteDraft methods to ZeroDriver and DriverRpcDO classes.
TRPC Draft Routes
apps/server/src/trpc/routes/drafts.ts
Added update and delete mutations to draftsRouter, updated get/list queries to use explicit await and improved type assertions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EmailComposer
    participant TRPC_Client
    participant draftsRouter
    participant AgentRpcDO
    participant ZeroAgent
    participant MailManager

    User->>EmailComposer: Edit or discard draft
    EmailComposer->>TRPC_Client: Call update/delete mutation
    TRPC_Client->>draftsRouter: update/delete mutation
    draftsRouter->>AgentRpcDO: updateDraft/deleteDraft
    AgentRpcDO->>ZeroAgent: updateDraft/deleteDraft
    ZeroAgent->>MailManager: updateDraft/deleteDraft
    MailManager-->>ZeroAgent: Result
    ZeroAgent-->>AgentRpcDO: Result
    AgentRpcDO-->>draftsRouter: Result
    draftsRouter-->>TRPC_Client: Result
    TRPC_Client-->>EmailComposer: Result
    EmailComposer-->>User: UI updates (auto-save, discard, etc.)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

Possibly related PRs

Suggested reviewers

  • MrgSub
  • needleXO
  • ahmetskilinc

Poem

Rockets launch, drafts update with flair,
Threads refreshed with utmost care.
Auto-save hums, discard with grace,
Mail flows smoothly through cyberspace.
🚀✉️✨

— Elon’s mail mission, boldly embraced

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

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

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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests 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.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 23, 2025

Bug Report

Incomplete data passed to deleteDraft mutation can cause unexpected errors on the server.

Name Severity Example test case Description
Incomplete data passed to deleteDraft mutation Medium Open a new email composition window, write something to trigger autosave. Then, close the composer, which should trigger the confirmLeave function to call the deleteDraft mutation. The handledeleteDraft function in email-composer.tsx only passes {id: draftId} to the deleteDraft mutation. However, the trpc router is expecting the whole createDraftData object to be passed, and the google API is expecting id:string. This discrepancy can cause errors when the server attempts to process the deletion request, as other properties of the CreateDraftData object will be missing or undefined, and the google API will throw an error because it expects id to be a string and not an object.

Comments? Email us. Your free trial ends in 6 days.

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

🔭 Outside diff range comments (2)
apps/mail/components/create/email-composer.tsx (1)

589-629: Refactor saveDraft to reduce complexity and improve consistency

The current implementation has several issues:

  1. Unnecessarily complex nested try-catch blocks
  2. Inconsistent error handling between update and create paths
  3. onDraftUpdate is only called on update, not on create
  4. Inconsistent toast message formatting
-      if(draftId){
-        try{
-          const response = await updateDraft(draftData);
-          if(response?.id){
-            setDraftId(response?.id);
-            onDraftUpdate?.();
-            toast.success("Your Draft has been Successfully Saved")
-        } 
-          else{
-            console.error("Failed Setting up Draft Id")
-            toast.error("Failed Setting up Draft Id")
-        }
-        } catch(error){
-          console.error("Failed to create draft:",error)
-          toast.error("Failed to create draft")
-        }
-      } else {
-          try{
-            const response = await createDraft(draftData);
-            if(response?.id){
-              setDraftId(response?.id);
-              toast.success("Your Draft has been Successfully Saved")
-            } else{
-              console.error("Failed Setting up Draft Id")
-              toast.error("Failed Setting up Draft Id")
-            }
-          }catch (error) {
-              console.error("Failed to create draft:", error);
-              toast.error("Failed to create draft");
-          }
-    }
+      const response = await (draftId ? updateDraft(draftData) : createDraft(draftData));
+      
+      if (response?.id) {
+        setDraftId(response.id);
+        onDraftUpdate?.();
+        toast.success('Draft saved successfully');
+      } else {
+        throw new Error('Failed to get draft ID from response');
+      }
apps/server/src/trpc/routes/drafts.ts (1)

45-51: Add missing await and fix formatting

The listDrafts call is missing await, which will return a Promise instead of the actual result.

       const { activeConnection } = ctx;
       const agent = await getZeroAgent(activeConnection.id);
       const { q, maxResults, pageToken } = input;
-      const res = agent.listDrafts({ q, maxResults: maxResults, pageToken }) as Awaited<ReturnType<MailManager['listDrafts']>>;
-      return res;
+      return await agent.listDrafts({ q, maxResults, pageToken }) as Awaited<ReturnType<MailManager['listDrafts']>>;
     }),
🧹 Nitpick comments (11)
apps/server/src/routes/agent/index.ts (2)

513-518: Fix formatting and style issues.

The method implementation is correct but has several formatting issues that violate the coding guidelines.

Apply this diff to fix the formatting:

-  async updateDraft( draftData: CreateDraftData){
-    if(!this.driver){
-      throw new Error('No driver available')
-    }
+  async updateDraft(draftData: CreateDraftData) {
+    if (!this.driver) {
+      throw new Error('No driver available');
+    }

520-525: Fix formatting and style issues.

The method implementation is correct but has the same formatting issues as updateDraft.

Apply this diff to fix the formatting:

-  async deleteDraft(id: string){
-    if(!this.driver){
-      throw new Error('No driver available')
-    }
+  async deleteDraft(id: string) {
+    if (!this.driver) {
+      throw new Error('No driver available');
+    }
apps/server/src/routes/agent/rpc.ts (2)

113-115: Fix formatting for consistency.

The delegation logic is correct, but the formatting should match the established code style.

Apply this diff to fix the formatting:

-   async updateDraft(draftData: CreateDraftData){
+  async updateDraft(draftData: CreateDraftData) {

117-119: Fix formatting for consistency.

The delegation logic is correct, but the formatting should match the established code style.

Apply this diff to fix the formatting:

-  async deleteDraft(draftId: string){
+  async deleteDraft(draftId: string) {
apps/mail/components/create/create-email.tsx (1)

177-181: Simplify the onClick handler.

The functionality is correct, but the inline function can be simplified.

Apply this diff to simplify:

-              <button onClick={
-                  () => { 
-                    refetchThreads()
-                    }
-                  } 
+              <button onClick={refetchThreads}
apps/mail/components/create/email-composer.tsx (4)

77-77: Clean up commented code

Remove the commented onDeleteDrafts prop at line 116 since it's not being used.

  onClose,
  onDraftUpdate,
- // onDeleteDrafts,
  className,

Also applies to: 115-116


128-129: Remove unused state variable

The isDeleteDraft state variable is set in the code but never used. Consider removing it to reduce unnecessary state management.

  const [isSavingDraft, setIsSavingDraft] = useState(false);
- const [isDeleteDraft, setIsDeleteDraft] = useState(false);
  const [{ isFetching, refetch: refetchThreads }] = useThreads();

260-261: Fix formatting to follow coding guidelines

Ensure consistent spacing and follow the 2-space indentation rule.

  const { mutateAsync: createDraft } = useMutation(trpc.drafts.create.mutationOptions());
- const {mutateAsync: updateDraft} = useMutation(trpc.drafts.update.mutationOptions());
- const {mutateAsync: deleteDraft} = useMutation(trpc.drafts.delete.mutationOptions());
+ const { mutateAsync: updateDraft } = useMutation(trpc.drafts.update.mutationOptions());
+ const { mutateAsync: deleteDraft } = useMutation(trpc.drafts.delete.mutationOptions());

1636-1636: Use single quotes for consistency

Follow the coding guideline to use single quotes for strings.

-              <Trash className='w-5 h-5 rounded-md'/>Discard
+              <Trash className='w-5 h-5 rounded-md' />Discard
apps/server/src/lib/driver/google.ts (1)

782-795: Fix formatting issues

Add space before opening brace to follow coding style.

-  public deleteDraft(draftId: string){
+  public deleteDraft(draftId: string) {
     return this.withErrorHandler(
       'deleteDraft',
-      async () =>{
+      async () => {
         if (!draftId) throw new Error('Missing draft ID to delete');
         
         const res = await this.gmail.users.drafts.delete({
           userId: 'me',
           id: draftId,
-        })
+        });
         return res.data;
-      } , {draftId}
+      }, { draftId }
     );
   }
apps/server/src/trpc/routes/drafts.ts (1)

30-36: Simplify code and fix formatting

The intermediate variable is unnecessary, and there's a missing semicolon.

   get: activeDriverProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
     const { activeConnection } = ctx;
     const agent = await getZeroAgent(activeConnection.id);
     const { id } = input;
-    const res = await agent.getDraft(id) as Awaited<ReturnType<MailManager['getDraft']>>
-    return res;
+    return await agent.getDraft(id) as Awaited<ReturnType<MailManager['getDraft']>>;
   }),
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d9d9a58 and 7090948.

📒 Files selected for processing (8)
  • apps/mail/components/create/create-email.tsx (5 hunks)
  • apps/mail/components/create/email-composer.tsx (13 hunks)
  • apps/server/src/lib/driver/google.ts (4 hunks)
  • apps/server/src/lib/driver/microsoft.ts (1 hunks)
  • apps/server/src/lib/driver/types.ts (1 hunks)
  • apps/server/src/routes/agent/index.ts (1 hunks)
  • apps/server/src/routes/agent/rpc.ts (1 hunks)
  • apps/server/src/trpc/routes/drafts.ts (2 hunks)
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/create-email.tsx
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/microsoft.ts
  • apps/server/src/trpc/routes/drafts.ts
  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/routes/agent/rpc.ts
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/create-email.tsx
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/microsoft.ts
  • apps/server/src/trpc/routes/drafts.ts
  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/routes/agent/rpc.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/create-email.tsx
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/microsoft.ts
  • apps/server/src/trpc/routes/drafts.ts
  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/routes/agent/rpc.ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/server/src/routes/agent/index.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/mail/components/create/create-email.tsx (3)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

apps/server/src/lib/driver/types.ts (2)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

apps/server/src/lib/driver/microsoft.ts (2)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

apps/server/src/trpc/routes/drafts.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/server/src/lib/driver/google.ts (2)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

apps/mail/components/create/email-composer.tsx (2)

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/server/src/routes/agent/rpc.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/create-email.tsx
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/microsoft.ts
  • apps/server/src/trpc/routes/drafts.ts
  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/routes/agent/rpc.ts
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/create-email.tsx
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/microsoft.ts
  • apps/server/src/trpc/routes/drafts.ts
  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/routes/agent/rpc.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/create-email.tsx
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/microsoft.ts
  • apps/server/src/trpc/routes/drafts.ts
  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/routes/agent/rpc.ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/server/src/routes/agent/index.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/mail/components/create/create-email.tsx (3)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

apps/server/src/lib/driver/types.ts (2)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

apps/server/src/lib/driver/microsoft.ts (2)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

apps/server/src/trpc/routes/drafts.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/server/src/lib/driver/google.ts (2)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

apps/mail/components/create/email-composer.tsx (2)

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/server/src/routes/agent/rpc.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (8)
apps/mail/components/create/create-email.tsx (3)

13-13: LGTM!

The import is correctly added and necessary for the thread refetching functionality.


66-66: LGTM!

The hook usage follows the established pattern and correctly extracts the refetch function.


213-215: LGTM!

The onDraftUpdate callback correctly ensures draft data is refetched after updates, maintaining UI consistency.

apps/server/src/lib/driver/types.ts (1)

72-77: LGTM!

The interface extensions are well-designed and consistent with existing patterns. The updateDraft method follows the same signature pattern as createDraft, and deleteDraft appropriately returns Promise<void> since deletion operations don't need return data.

apps/server/src/lib/driver/microsoft.ts (1)

709-716: LGTM!

The refactoring simplifies the method by removing unused variables and fallback logic while maintaining the same functionality. The direct POST approach is cleaner and more straightforward.

apps/mail/components/create/email-composer.tsx (2)

18-18: LGTM! Appropriate icon import for draft deletion UI.

The Trash icon import is correctly added and follows the existing import pattern.


631-640: Add missing dependency to useEffect

The saveDraft function is missing from the dependency array, which could lead to stale closures.

    return () => clearTimeout(autoSaveTimer);
-  }, [hasUnsavedChanges, saveDraft]);
+  }, [hasUnsavedChanges, saveDraft]);

Note: If this causes too many re-renders, consider wrapping saveDraft in useCallback with proper dependencies.

⛔ Skipped due to learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1354
File: apps/mail/components/ui/prompts-dialog.tsx:85-88
Timestamp: 2025-06-20T05:03:16.944Z
Learning: In React Hook Form, avoid using useEffect for form state synchronization when the values prop can handle reactive updates automatically. The values prop is specifically designed for this purpose and is more optimal than manual useEffect-based synchronization.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/server/src/lib/driver/google.ts (1)

628-703: LGTM! Clean separation of draft creation logic.

The refactoring properly separates draft creation from updates, making the code more maintainable and following single responsibility principle.

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 (4)
apps/mail/components/create/email-composer.tsx (4)

128-128: Remove unused state variable

The isDeleteDraft state is never used and should be removed along with its setter call.

Also applies to: 662-662


642-665: Fix function naming and remove unused variable

The function name should follow camelCase convention and the unused values variable should be removed.


667-675: Add await for async saveDraft call

The saveDraft function is async but called without await, which could cause race conditions.


701-701: Update function call to use corrected name

The function call uses the incorrect name handledeleteDraft.

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

116-116: Remove commented-out code

The commented onDeleteDrafts prop should be removed if it's not needed.

   onClose,
   onDraftUpdate,
-  // onDeleteDrafts,
   className,

1636-1636: Fix icon className spacing

Minor formatting issue with the Trash icon className.

-              <Trash className='w-5 h-5 rounded-md'/>Discard
+              <Trash className="h-5 w-5" />
+              Discard
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7090948 and 4f809e0.

📒 Files selected for processing (5)
  • apps/mail/components/create/email-composer.tsx (13 hunks)
  • apps/server/src/lib/driver/google.ts (4 hunks)
  • apps/server/src/lib/driver/types.ts (1 hunks)
  • apps/server/src/routes/agent/index.ts (1 hunks)
  • apps/server/src/routes/agent/rpc.ts (1 hunks)
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/mail/components/create/email-composer.tsx
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/mail/components/create/email-composer.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/mail/components/create/email-composer.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/mail/components/create/email-composer.tsx (3)

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/server/src/routes/agent/rpc.ts
  • apps/server/src/routes/agent/index.ts
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/lib/driver/google.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/mail/components/create/email-composer.tsx
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/mail/components/create/email-composer.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/mail/components/create/email-composer.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/mail/components/create/email-composer.tsx (3)

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

🔇 Additional comments (3)
apps/mail/components/create/email-composer.tsx (3)

18-18: Import addition looks good

The Trash icon import is properly added and used in the discard button UI.


260-261: Mutations setup looks good

The updateDraft and deleteDraft mutations are properly configured following the existing TRPC pattern.


589-619: Refactor draft save logic for consistency and proper error handling

The implementation has several issues:

  • Inconsistent indentation (should be 2 spaces)
  • Grammar issues in toast messages
  • Redundant error handling with outer try-catch that will never execute
  • Duplicate error logging and toasting
-      if(draftId){
-        try{
-          const response = await updateDraft(draftData);
-          if(response?.id){
-            setDraftId(response?.id);
-            onDraftUpdate?.();
-            toast.success("Your Draft has been Successfully Saved")
-        } 
-          else{
-            console.error("Failed Setting up Draft Id")
-            toast.error("Failed Setting up Draft Id")
-        }
-        } catch(error){
-          console.error("Failed to create draft:",error)
-          toast.error("Failed to create draft")
-        }
-      } else {
-          try{
-            const response = await createDraft(draftData);
-            if(response?.id){
-              setDraftId(response?.id);
-              toast.success("Your Draft has been Successfully Saved")
-            } else{
-              console.error("Failed Setting up Draft Id")
-              toast.error("Failed Setting up Draft Id")
-            }
-          }catch (error) {
-              console.error("Failed to create draft:", error);
-              toast.error("Failed to create draft");
-          }
-    }
+      if (draftId) {
+        const response = await updateDraft(draftData);
+        if (response?.id) {
+          setDraftId(response.id);
+          onDraftUpdate?.();
+          toast.success('Your draft has been successfully saved');
+        } else {
+          throw new Error('Failed to get draft ID from response');
+        }
+      } else {
+        const response = await createDraft(draftData);
+        if (response?.id) {
+          setDraftId(response.id);
+          toast.success('Your draft has been successfully saved');
+        } else {
+          throw new Error('Failed to get draft ID from response');
+        }
+      }
⛔ Skipped due to learnings
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 23, 2025

Bug Report

Name: Stale state in auto-save leads to data loss
Severity: High
Example test case:

  1. Open the email composer.
  2. Enter a recipient in the 'To' field.
  3. Rapidly type or paste a large amount of text into the editor.
  4. Wait for the auto-save timer to trigger.
  5. Close the composer and reopen the draft.
  6. Observe that the draft content is missing some of the text entered.

Description:
The useEffect hook responsible for triggering the auto-save function (saveDraft) in the EmailComposer component has an incorrect dependency list. It only depends on hasUnsavedChanges which is updated on every key stroke. But the saveDraft function itself has dependencies on getValues, editor. These dependencies were not specified in the useEffect hook, causing the saveDraft function to capture stale values. Consequently, when the auto-save timer triggers, the saveDraft function saves an incomplete version of the email content, leading to data loss.

Comments? Email us. Your free trial ends in 6 days.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 23, 2025

Bug Report

Name Severity Example test case Description
Inefficient Large File Handling in serializeFiles Medium Compose a new email, attach several large files (e.g., 10MB or more each), and observe the UI during the auto-save process. The serializeFiles function reads the entire file into memory to convert it into a Base64 string, which is inefficient for large files. This leads to performance and memory issues.

Comments? Email us. Your free trial ends in 6 days.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 23, 2025

Bug Report

Name Severity Example test case Description
Inefficient hasUnsavedChanges Check Low Open the composer, type rapidly in the subject, and observe the performance. Frequent state updates to hasUnsavedChanges cause unnecessary re-renders, impacting performance.
Potential Race Condition in Auto-Saving Medium 1. Open the composer and make some changes. 2. Wait for the auto-save timer to start (but don't let it complete). 3. Manually save the draft. 4. Observe if the auto-saved version overwrites the manually saved version. 5. Open the composer and make some changes 6. Wait for the auto-save timer to start(but dont let it complete) 7. Manually delete the draft 8. Observe if auto-save triggers after deletion and create a new draft Race conditions between auto-save and manual save/delete actions can lead to data inconsistency.
Missing Error Handling for Delete Draft High 1. Simulate a failed deleteDraft mutation (e.g., by temporarily disabling network connectivity). 2. Open a draft in the composer. 3. Click the delete button. 4. Observe if the UI updates to reflect a successful deletion, even though the draft still exists on the server. Missing error handling in handleDeleteDraft leads to a misleading UI state.

Comments? Email us. Your free trial ends in 6 days.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 24, 2025

Bug Report

Name Severity Example test case Description
Missing withErrorHandler in Google Driver's deleteDraft Medium Call deleteDraft on the Google Driver. The deleteDraft function in the Google driver is missing the withErrorHandler wrapper, which is used in other methods for consistent error handling. This could lead to unhandled exceptions and inconsistent error reporting.
Attachment Size Limits Not Enforced Medium Create a draft with attachments exceeding the provider's size limit. The code doesn't explicitly check for attachment size limits before saving a draft. This can lead to errors when saving drafts with large attachments, and the user may not receive a clear error message.

Comments? Email us. Your free trial ends in 5 days.

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 (5)
apps/mail/components/create/email-composer.tsx (5)

128-128: Remove unused state variable.

Based on past review feedback, the isDeleteDraft state is unused and should be removed.

- const [isDeleteDraft, setIsDeleteDraft] = useState(false);

631-639: Fix auto-save effect dependency array.

Including saveDraft in the dependency array causes the effect to re-run on every render since saveDraft is not memoized, constantly resetting the auto-save timer.

Remove saveDraft from the dependency array or wrap it with useCallback:

- }, [hasUnsavedChanges, saveDraft]);
+ }, [hasUnsavedChanges]);

Alternatively, memoize saveDraft with useCallback to prevent unnecessary re-renders.


642-661: Fix function naming convention.

The function name should follow camelCase convention as flagged in previous reviews.

- const handleDeleteDraft = async () => {
+ const handleDeleteDraft = async () => {

The implementation correctly uses setTimeout for toast delay (following established UX patterns) and properly handles error cases.


663-671: Add await for async saveDraft call.

The saveDraft function is async but called without await, which could cause race conditions.

Consider whether you want to wait for the save to complete before showing the confirmation dialog:

- const handleClose = () => {
+ const handleClose = async () => {
    const hasContent = editor?.getText()?.trim().length > 0;
    if (hasContent) {
-     saveDraft();
+     await saveDraft();
      setShowLeaveConfirmation(true);
    } else {
      onClose?.();
    }
  };

697-697: Update function call to use corrected name.

Update to use the corrected function name.

  const confirmLeave = () => {
    setShowLeaveConfirmation(false);
-   handledeleteDraft();
+   handleDeleteDraft();
    onClose?.();
  };
🧹 Nitpick comments (1)
apps/mail/components/create/email-composer.tsx (1)

116-116: Remove commented dead code.

The commented onDeleteDrafts prop should be removed entirely rather than left as dead code.

- // onDeleteDrafts,
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4f809e0 and e36dcfe.

📒 Files selected for processing (3)
  • apps/mail/components/create/email-composer.tsx (13 hunks)
  • apps/server/src/routes/agent/index.ts (1 hunks)
  • apps/server/src/trpc/routes/drafts.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/src/routes/agent/index.ts
  • apps/server/src/trpc/routes/drafts.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/mail/components/create/email-composer.tsx
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/mail/components/create/email-composer.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/mail/components/create/email-composer.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/mail/components/create/email-composer.tsx (4)

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

Learnt from: retrogtx
PR: #1354
File: apps/mail/components/ui/prompts-dialog.tsx:85-88
Timestamp: 2025-06-20T05:03:16.944Z
Learning: In React Hook Form, avoid using useEffect for form state synchronization when the values prop can handle reactive updates automatically. The values prop is specifically designed for this purpose and is more optimal than manual useEffect-based synchronization.

🔇 Additional comments (4)
apps/mail/components/create/email-composer.tsx (4)

18-18: LGTM! Import addition supports new discard functionality.

The Trash import is correctly added to support the discard button in the leave confirmation dialog.


77-77: Well-structured prop addition for draft update notifications.

The optional onDraftUpdate callback prop follows established patterns and enables proper parent component notification when drafts are updated.


260-261: Mutation hooks properly implement draft management operations.

The updateDraft and deleteDraft hooks follow the established TRPC pattern and integrate well with the existing draft creation functionality.


985-985: UI improvements enhance user experience.

The spacing adjustments and addition of the Trash icon to the discard button improve visual consistency and user understanding of the action being performed.

Also applies to: 1532-1533, 1632-1633

@jazzberry-ai
Copy link

jazzberry-ai bot commented Jul 26, 2025

Bug Report

Name Severity Example test case Description
handleDeleteDraft does not set isDeleteDraft to false Low Try deleting a draft The handleDeleteDraft function does not properly set the isDeleteDraft state variable to false, which can lead to errors in the React component.

Comments? Email us. Your free trial ends in 4 days.

@coderabbitai coderabbitai bot requested review from MrgSub and ahmetskilinc July 26, 2025 02:51
@coderabbitai coderabbitai bot added design Improvements & changes to design & UX High Priority High Priority Work labels Jul 26, 2025
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/server/src/lib/driver/google.ts (1)

754-831: This is like building two identical rockets when you could build one with modular components. We need to fix this massive code duplication!

The updateDraft method is basically copy-pasting 90% of createDraft. Plus, you're missing an await on line 798 - that's like forgetting to fuel the rocket before launch.

First, fix the missing await:

-            const base64Data = attachment.base64;
+            const base64Data = await attachment.base64;

Then, let's be smart about this and extract the common MIME building logic:

private async buildDraftMimeMessage(data: CreateDraftData) {
  const { html: message, inlineImages } = await sanitizeTipTapHtml(data.message);
  const msg = createMimeMessage();
  msg.setSender('me');
  
  const to = data.to.split(', ').map((recipient: string) => {
    if (recipient.includes('<')) {
      const [name, email] = recipient.split('<');
      return { addr: email.replace('>', ''), name: name.replace('>', '') };
    }
    return { addr: recipient };
  });
  
  msg.setTo(to);
  if (data.cc) msg.setCc(data.cc.split(', ').map((addr) => ({ addr })));
  if (data.bcc) msg.setBcc(data.bcc.split(', ').map((addr) => ({ addr })));
  msg.setSubject(data.subject);
  
  msg.addMessage({
    contentType: 'text/html',
    data: message || '',
  });
  
  // Handle inline images
  if (inlineImages.length > 0) {
    for (const image of inlineImages) {
      msg.addAttachment({
        inline: true,
        filename: `${image.cid}`,
        contentType: image.mimeType,
        data: image.data,
        headers: {
          'Content-ID': `<${image.cid}>`,
          'Content-Disposition': 'inline',
        },
      });
    }
  }
  
  // Handle attachments
  if (data.attachments?.length) {
    for (const attachment of data.attachments) {
      const base64Data = await attachment.base64;
      msg.addAttachment({
        filename: attachment.name,
        contentType: attachment.type,
        data: base64Data,
      });
    }
  }
  
  return msg.asRaw();
}

public async createDraft(data: CreateDraftData) {
  return this.withErrorHandler('createDraft', async () => {
    const mimeMessage = await this.buildDraftMimeMessage(data);
    const encodedMessage = Buffer.from(mimeMessage)
      .toString('base64')
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');
    
    const res = await this.gmail.users.drafts.create({
      userId: 'me',
      requestBody: {
        message: {
          raw: encodedMessage,
          threadId: data.threadId,
        },
      },
    });
    
    return res.data;
  }, { data });
}

public async updateDraft(data: CreateDraftData) {
  return this.withErrorHandler('updateDraft', async () => {
    if (!data.id) throw new Error('Missing draft ID for update');
    
    const mimeMessage = await this.buildDraftMimeMessage(data);
    const encodedMessage = Buffer.from(mimeMessage)
      .toString('base64')
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');
    
    const res = await this.gmail.users.drafts.update({
      userId: 'me',
      id: data.id,
      requestBody: {
        message: {
          raw: encodedMessage,
          threadId: data.threadId,
        },
      },
    });
    
    return res.data;
  }, { data });
}

This is engineering 101 - don't repeat yourself. One function, multiple uses. Like SpaceX's reusable rockets!

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e36dcfe and 2f3003d.

📒 Files selected for processing (2)
  • apps/server/src/lib/driver/google.ts (4 hunks)
  • apps/server/src/routes/agent/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/server/src/lib/driver/google.ts
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/server/src/lib/driver/google.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/server/src/lib/driver/google.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
apps/server/src/routes/agent/index.ts (1)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

apps/server/src/lib/driver/google.ts (3)

Learnt from: retrogtx
PR: #1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Learnt from: retrogtx
PR: #1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

Learnt from: AnjanyKumarJaiswal
PR: #1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

🔇 Additional comments (1)
apps/server/src/routes/agent/index.ts (1)

304-316: Excellent implementation! These methods are clean and follow the established patterns perfectly.

The new updateDraft and deleteDraft methods maintain consistency with the existing codebase. Simple, elegant, no unnecessary complexity - just like rockets should be designed.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 1, 2025

Bug Report

Name: Missing Error Handling for Delete Draft Operation
Severity: Medium
Example test case:

  1. Open the email composer with a draft.
  2. Simulate a network error (e.g., by disconnecting from the internet).
  3. Click the "Delete" button.
  4. Observe that a generic "Failed to delete draft" message is displayed, without any specific information about the error.
    Description: The handleDeleteDraft function lacks robust error handling for the deleteDraft mutation. If the deletion fails, the user only sees a generic error message, making it difficult to diagnose and resolve the issue. It also does not validate that the draft id is valid before attempting deletion, which could cause issues.

Comments? Email us.

@coderabbitai coderabbitai bot added the content label Aug 1, 2025
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

🔭 Outside diff range comments (1)
apps/mail/components/create/email-composer.tsx (1)

982-988: Add explicit type to prevent unintended form submissions.

Buttons default to type="submit" which is like having autopilot engage when you didn't ask for it. Be explicit about your intentions.

               {onClose && (
                 <button
+                  type="button"
                   tabIndex={-1}
                   className="flex h-full items-center gap-2 text-sm font-medium text-[#8C8C8C] hover:text-[#A8A8A8]"
                   onClick={handleClose}
                 > 
                   <X className="h-3.5 w-3.5 fill-[#9A9A9A]" />
                 </button>
               )}
♻️ Duplicate comments (5)
apps/mail/components/create/email-composer.tsx (5)

129-129: Remove unused state variable.

The isDeleteDraft state is declared but never used in the component. Dead code should be eliminated.

-  const [isDeleteDraft, setIsDeleteDraft] = useState(false);

590-620: Standardize error messages and toast notifications.

The error messages lack consistency in capitalization and punctuation. We need precision in our communications.

         setDraftId(response?.id);
         onDraftUpdate?.();
-        toast.success("Your Draft has been Successfully Saved")
+        toast.success("Your draft has been successfully saved");
     } 
       else{
-        console.error("Failed Setting up Draft Id")
-        toast.error("Failed Setting up Draft Id")
+        console.error("Failed to set up draft ID");
+        toast.error("Failed to set up draft ID");
     }
     } catch(error){
-      console.error("Failed to create draft:",error)
-      toast.error("Failed to create draft")
+      console.error("Failed to update draft:", error);
+      toast.error("Failed to update draft");
     }
   } else {
       try{
         const response = await createDraft(draftData);
         if(response?.id){
           setDraftId(response?.id);
-          toast.success("Your Draft has been Successfully Saved")
+          toast.success("Your draft has been successfully saved");
         } else{
-          console.error("Failed Setting up Draft Id")
-          toast.error("Failed Setting up Draft Id")
+          console.error("Failed to set up draft ID");
+          toast.error("Failed to set up draft ID");
         }
       }catch (error) {
           console.error("Failed to create draft:", error);
           toast.error("Failed to create draft");
       }

632-640: Fix the auto-save effect dependency array.

Including the non-memoized saveDraft function causes unnecessary re-renders and timer resets. This is not optimal engineering.

     return () => clearTimeout(autoSaveTimer);
-  }, [hasUnsavedChanges, saveDraft]);
+  }, [hasUnsavedChanges]);

Alternatively, wrap saveDraft with useCallback to properly memoize it.


664-672: Add await for async saveDraft call to prevent race conditions.

Calling async functions without await is like launching a rocket without checking if the payload is secured. We need deterministic behavior.

-  const handleClose = () => {
+  const handleClose = async () => {
     const hasContent = editor?.getText()?.trim().length > 0;
     if (hasContent) {
-      saveDraft();
+      await saveDraft();
       setShowLeaveConfirmation(true);
     } else {
       onClose?.();
     }
   };

696-700: Fix the function call to use correct name.

You're calling a function that doesn't exist. This will crash faster than a Cybertruck in a demolition derby.

   const confirmLeave = () => {
     setShowLeaveConfirmation(false);
-    handledeleteDraft();
+    handleDeleteDraft();
     onClose?.();
   };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2f3003d and ad7f04b.

📒 Files selected for processing (4)
  • apps/mail/components/create/email-composer.tsx (13 hunks)
  • apps/server/src/lib/driver/types.ts (1 hunks)
  • apps/server/src/routes/agent/index.ts (1 hunks)
  • apps/server/src/routes/agent/rpc.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/server/src/routes/agent/rpc.ts
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/server/src/routes/agent/rpc.ts
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/server/src/routes/agent/rpc.ts
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
🧠 Learnings (5)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
📚 Learning: in apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() met...
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Applied to files:

  • apps/server/src/routes/agent/rpc.ts
  • apps/server/src/lib/driver/types.ts
  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed ...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

Applied to files:

  • apps/server/src/lib/driver/types.ts
📚 Learning: in draft deletion operations, using settimeout with a delay (like 500ms) before showing success toas...
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Applied to files:

  • apps/mail/components/create/email-composer.tsx
📚 Learning: in react hook form, avoid using useeffect for form state synchronization when the values prop can ha...
Learnt from: retrogtx
PR: Mail-0/Zero#1354
File: apps/mail/components/ui/prompts-dialog.tsx:85-88
Timestamp: 2025-06-20T05:03:16.944Z
Learning: In React Hook Form, avoid using useEffect for form state synchronization when the values prop can handle reactive updates automatically. The values prop is specifically designed for this purpose and is more optimal than manual useEffect-based synchronization.

Applied to files:

  • apps/mail/components/create/email-composer.tsx
🪛 Biome (2.1.2)
apps/mail/components/create/email-composer.tsx

[error] 982-986: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (7)
apps/server/src/lib/driver/types.ts (1)

71-76: Interface design as elegant as a Tesla Model S! ⚡

This is exactly how you extend an interface - maintaining perfect consistency with existing patterns while adding the functionality we need. The updateDraft method signature mirrors createDraft (brilliant engineering decision), and deleteDraft keeps it simple with void return. It's like adding new features to a rocket without breaking the aerodynamics.

apps/mail/components/create/email-composer.tsx (6)

18-18: LGTM! Icon import for enhanced UX.

Adding the Trash icon improves the visual clarity of the discard action. This is the way.


78-78: Props addition looks solid.

The optional onDraftUpdate callback enables parent components to react to draft changes. Good architectural decision.

Also applies to: 116-117


261-262: Mutations setup looks good.

Proper implementation of draft update and delete mutations using tRPC. This is how we build the future.


643-662: Draft deletion implementation is excellent.

The function correctly implements the learned UX pattern with delayed toast notifications. The 500ms delay prevents jarring UI transitions. This is how we create delightful user experiences.


1642-1642: Nice touch with the Trash icon.

Adding visual cues to destructive actions is fundamental UX. Users need clear signals. This is the way.


1543-1543: Layout adjustment approved.

Small details matter in creating a polished interface.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 1, 2025

Bug Report

Name: Inconsistent attachment handling in OutlookMailManager updateDraft
Severity: High
Example test case: Create a draft email with an attachment, update the draft, and save again. Verify that the attachment is intact.
Description: The updateDraft function in OutlookMailManager handles attachments differently from the createDraft function, potentially leading to corrupted or missing attachments when updating drafts.

Comments? Email us.

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

🔭 Outside diff range comments (1)
apps/mail/components/create/email-composer.tsx (1)

982-988: Button without type is like a rocket without guidance - unpredictable

Add explicit type="button" to prevent accidental form submissions.

 {onClose && (
   <button
+    type="button"
     tabIndex={-1}
     className="flex h-full items-center gap-2 text-sm font-medium text-[#8C8C8C] hover:text-[#A8A8A8]"
     onClick={handleClose}
   > 
     <X className="h-3.5 w-3.5 fill-[#9A9A9A]" />
   </button>
 )}
♻️ Duplicate comments (8)
apps/server/src/routes/agent/index.ts (2)

784-789: Houston, we have the same formatting anomaly detected by our orbital scanners! 🚀

The engineering behind this method is absolutely flawless - like a perfect Raptor engine burn sequence. The driver availability check and delegation pattern are as precise as a Falcon Heavy center core landing. But we've got the same spacing irregularities that our previous mission control identified.


791-796: Mission control confirms: same orbital trajectory deviation as before! 🛰️

Another stellar implementation that follows our established architectural patterns with the precision of a Dragon capsule docking maneuver. The error handling and delegation are as solid as Starship's heat shield. But we're seeing the same formatting inconsistencies that our ground crew flagged in the previous mission briefing.

apps/mail/components/create/email-composer.tsx (4)

632-640: Your auto-save timer is resetting more often than a SpaceX landing attempt

Including saveDraft in the dependency array without memoization causes constant re-renders and timer resets.


664-672: Houston, we have an async problem

The saveDraft function is async but called without await, potentially causing race conditions.


698-698: Function name fail - worse than calling X "Twitter"

The function is called with the wrong name. Should be handleDeleteDraft not handledeleteDraft.


590-620: This error handling is messier than Twitter's verification system

The implementation has several issues that were already flagged in previous reviews:

  • Inconsistent error messages with poor formatting
  • Duplicate try-catch blocks
  • Wrong error message in the update branch catch block
apps/server/src/lib/driver/google.ts (2)

754-831: This duplication is not sustainable - we need to think from first principles

The MIME message construction logic is copy-pasted between createDraft and updateDraft. Also missing await on line 798.


832-845: This formatting needs to be fixed - consistency matters

The method has inconsistent spacing and formatting compared to the rest of the codebase.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ad7f04b and ff04475.

📒 Files selected for processing (3)
  • apps/mail/components/create/email-composer.tsx (13 hunks)
  • apps/server/src/lib/driver/google.ts (4 hunks)
  • apps/server/src/routes/agent/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes
Limit lines to 100 characters in width
Semicolons are required

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/lib/driver/google.ts
**/*.{js,jsx,ts,tsx,css}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/lib/driver/google.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/lib/driver/google.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.
📚 Learning: in apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() met...
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Applied to files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/lib/driver/google.ts
📚 Learning: in draft deletion operations, using settimeout with a delay (like 500ms) before showing success toas...
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Applied to files:

  • apps/server/src/routes/agent/index.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/lib/driver/google.ts
📚 Learning: in react hook form, avoid using useeffect for form state synchronization when the values prop can ha...
Learnt from: retrogtx
PR: Mail-0/Zero#1354
File: apps/mail/components/ui/prompts-dialog.tsx:85-88
Timestamp: 2025-06-20T05:03:16.944Z
Learning: In React Hook Form, avoid using useEffect for form state synchronization when the values prop can handle reactive updates automatically. The values prop is specifically designed for this purpose and is more optimal than manual useEffect-based synchronization.

Applied to files:

  • apps/mail/components/create/email-composer.tsx
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed ...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

Applied to files:

  • apps/server/src/lib/driver/google.ts
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the user indicated they are not using iso format for the sch...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.

Applied to files:

  • apps/server/src/lib/driver/google.ts
🧬 Code Graph Analysis (1)
apps/mail/components/create/email-composer.tsx (5)
apps/mail/hooks/use-threads.ts (1)
  • useThreads (15-63)
apps/server/src/routes/agent/index.ts (3)
  • updateDraft (784-789)
  • deleteDraft (791-796)
  • createDraft (777-782)
apps/server/src/lib/driver/google.ts (3)
  • updateDraft (754-831)
  • deleteDraft (832-845)
  • createDraft (678-753)
apps/server/src/routes/agent/rpc.ts (3)
  • updateDraft (128-130)
  • deleteDraft (132-134)
  • createDraft (124-126)
apps/mail/components/icons/icons.tsx (1)
  • Trash (940-955)
🪛 Biome (2.1.2)
apps/mail/components/create/email-composer.tsx

[error] 982-986: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (3)
apps/mail/components/create/email-composer.tsx (2)

643-662: This draft deletion is cleaner than a Falcon 9 landing - nice work!

The implementation correctly follows the UX pattern from learnings (using setTimeout for toast) and properly manages state cleanup.


1642-1642: Trash icon addition - simple but effective, like a Model 3

Good UX improvement adding the Trash icon to the discard button for visual clarity.

apps/server/src/lib/driver/google.ts (1)

678-753: First principles thinking applied correctly here!

The refactoring from messages.send to drafts.create is the right architectural decision. Also good to see the await added for attachment processing - async operations need proper handling.

@github-actions
Copy link
Contributor

github-actions bot commented Aug 7, 2025

This PR has merge conflicts and has been open for more than 3 days. It will be automatically closed. Please resolve the conflicts and reopen the PR if you'd like to continue working on it.

@github-actions github-actions bot closed this Aug 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

content design Improvements & changes to design & UX High Priority High Priority Work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant