Skip to content

Comments

feat: finally done with Auto-Save , Update and Delete Drafts #1732

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

feat: finally done with Auto-Save , Update and Delete Drafts #1732
AnjanyKumarJaiswal wants to merge 16 commits intoMail-0:stagingfrom
AnjanyKumarJaiswal:feat/autoSave_Email_drafts

Conversation

@AnjanyKumarJaiswal
Copy link

@AnjanyKumarJaiswal AnjanyKumarJaiswal commented Jul 14, 2025

Created 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

Add screenshots or recordings here if applicable.


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 so users can save progress automatically and remove drafts when needed.

  • New Features
    • Drafts are auto-saved as users write.
    • Users can update and delete drafts directly.
    • Backend and API endpoints support these draft actions for both Google and Microsoft accounts.

Summary by CodeRabbit

  • New Features

    • Added the ability to update and delete email drafts directly from the email composer.
    • Introduced a discard (Trash) button with confirmation dialog for deleting drafts.
    • Drafts are now auto-saved after changes, and users receive notifications on save or delete actions.
  • Improvements

    • Enhanced draft management for both Google and Microsoft accounts, including explicit update and delete support.
    • Improved feedback and UI flow when closing or discarding a draft.
    • Streamlined draft fetching and UI rendering by removing loading placeholders.
  • Bug Fixes

    • Removed unnecessary loading states, ensuring the email composer is always accessible.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 14, 2025

Walkthrough

This change introduces explicit support for updating and deleting email drafts across both the frontend and backend. It adds new methods and API endpoints for draft updates and deletions, refactors draft management logic for Google and Microsoft drivers, and updates the email composer UI to allow discarding drafts. The thread list now refreshes after relevant actions.

Changes

Files/Paths Change Summary
apps/mail/components/create/create-email.tsx Uses useThreads for thread refetching, always renders EmailComposer, adds draft refetch on update, updates close logic.
apps/mail/components/create/email-composer.tsx Adds draft delete support, improves save/update logic, integrates discard UI, triggers thread refetch on changes.
apps/server/src/lib/driver/google.ts, apps/server/src/lib/driver/microsoft.ts Refactors draft creation logic, adds explicit updateDraft and deleteDraft methods for both Google and Microsoft drivers.
apps/server/src/lib/driver/types.ts Extends MailManager interface with updateDraft and deleteDraft methods.
apps/server/src/routes/chat.ts Adds updateDraft and deleteDraft methods to AgentRpcDO and ZeroAgent classes.
apps/server/src/trpc/routes/drafts.ts Adds update and delete mutations to drafts router, updates parameter names and result handling for consistency.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EmailComposer
    participant TRPC Router
    participant ZeroAgent
    participant Driver (Google/Microsoft)
    participant ThreadsList

    User->>EmailComposer: Click "Save" or auto-save
    EmailComposer->>TRPC Router: updateDraft(data)
    TRPC Router->>ZeroAgent: updateDraft(data)
    ZeroAgent->>Driver (Google/Microsoft): updateDraft(data)
    Driver (Google/Microsoft)-->>ZeroAgent: result
    ZeroAgent-->>TRPC Router: result
    TRPC Router-->>EmailComposer: result
    EmailComposer->>ThreadsList: refetchThreads()

    User->>EmailComposer: Click "Discard"
    EmailComposer->>TRPC Router: deleteDraft(id)
    TRPC Router->>ZeroAgent: deleteDraft(id)
    ZeroAgent->>Driver (Google/Microsoft): deleteDraft(id)
    Driver (Google/Microsoft)-->>ZeroAgent: void
    ZeroAgent-->>TRPC Router: void
    TRPC Router-->>EmailComposer: void
    EmailComposer->>ThreadsList: refetchThreads()
Loading

Possibly related PRs

Suggested reviewers

  • MrgSub

Poem

🐇✨ In burrows deep where emails bloom,
Drafts now dance, no longer gloom.
Save or toss with just a click,
Threads refreshed, the process slick.
Rabbits code with nimble paws,
Tidying inboxes without a pause! 📧🌿

✨ Finishing Touches
  • 📝 Generate Docstrings

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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor

@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.

Greptile Summary

This PR implements a robust auto-save functionality for email drafts across both Google and Microsoft email providers. The implementation includes:

  • Auto-saving drafts every 3 seconds when changes are detected
  • Separate and clear implementations for create/update/delete draft operations
  • Improved error handling and user feedback through toast notifications
  • Seamless UI updates through proper refetching of drafts and threads

The changes span across the entire stack, from the driver interfaces (Google/Microsoft implementations) to the UI components, providing a cohesive and reliable draft management system.

Confidence Score: 3.5/5

  1. The PR implements critical user-facing functionality with complex state management
  2. Score reflects solid implementation but lack of automated tests and incomplete security review
  3. Key areas needing attention:
    • apps/mail/components/create/email-composer.tsx: Auto-save timing and error handling
    • apps/server/src/lib/driver/google.ts and microsoft.ts: Draft ID validation
    • Missing unit and integration tests across all changes

7 files reviewed, 14 comments
Edit PR Review Bot Settings | Greptile

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic found 4 issues across 7 files. Review them in cubic.dev

React with 👍 or 👎 to teach cubic. Tag @cubic-dev-ai to give specific feedback.

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

🔭 Outside diff range comments (1)
apps/server/src/lib/driver/google.ts (1)

571-726: Refactor to eliminate code duplication between createDraft and updateDraft

The updateDraft method contains nearly identical code to createDraft. This violates the DRY principle and makes maintenance more difficult. Extract the common MIME message construction logic into a private helper method.

+  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 || '',
+    });
+
+    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',
+          },
+        });
+      }
+    }
+
+    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,
+        });
+      }
+    }
+
+    const mimeMessage = msg.asRaw();
+    const encodedMessage = Buffer.from(mimeMessage)
+      .toString('base64')
+      .replace(/\+/g, '-')
+      .replace(/\//g, '_')
+      .replace(/=+$/, '');
+
+    return encodedMessage;
+  }

   public createDraft(data: CreateDraftData) {
     return this.withErrorHandler(
       'createDraft',
       async () => {
-        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 || '',
-        });
-
-        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',
-              },
-            });
-          }
-        }
-
-        if (data.attachments?.length) {
-          for (const attachment of data.attachments) {
-            // const arrayBuffer = await attachment.arrayBuffer();
-            // const base64Data = Buffer.from(arrayBuffer).toString('base64');
-            const base64Data = await attachment.base64;
-            msg.addAttachment({
-              filename: attachment.name,
-              contentType: attachment.type,
-              data: base64Data,
-            });
-          }
-        }
-
-        const mimeMessage = msg.asRaw();
-        const encodedMessage = Buffer.from(mimeMessage)
-          .toString('base64')
-          .replace(/\+/g, '-')
-          .replace(/\//g, '_')
-          .replace(/=+$/, '');
+        const encodedMessage = await this.buildDraftMimeMessage(data);

         const requestBody = {
           message: {
             raw: encodedMessage,
             threadId: data.threadId,
           },
         };

         const res = await this.gmail.users.drafts.create({
           userId: 'me',
           requestBody,
         });

         return res.data;
       },
       { data }
     );
   }
   public updateDraft(data: CreateDraftData) {
     return this.withErrorHandler(
       'updateDraft',
       async () => {
         if (!data.id) throw new Error('Missing draft ID for update');

-        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 || '',
-        });
-        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',
-              },
-            });
-          }
-        }
-
-        if (data.attachments?.length) {
-          for (const attachment of data.attachments) {
-            const base64Data = attachment.base64;
-            msg.addAttachment({
-              filename: attachment.name,
-              contentType: attachment.type,
-              data: base64Data,
-            });
-          }
-        }
-
-        const mimeMessage = msg.asRaw();
-        const encodedMessage = Buffer.from(mimeMessage)
-          .toString('base64')
-          .replace(/\+/g, '-')
-          .replace(/\//g, '_')
-          .replace(/=+$/, '');
+        const encodedMessage = await this.buildDraftMimeMessage(data);

         const requestBody = {
           message: {
             raw: encodedMessage,
             threadId: data.threadId,
           },
         };

         const res = await this.gmail.users.drafts.update({
           userId: 'me',
           id: data.id,
           requestBody,
         });

         return res.data;
       },
       { data }
     );
   }
🧹 Nitpick comments (8)
apps/mail/components/create/create-email.tsx (1)

177-182: Consider the performance impact of automatic refetching.

The onClick handler automatically refetches threads when the dialog closes. While this ensures UI consistency, consider the performance implications for users with many threads.

You might want to debounce the refetch or only refetch when drafts were actually modified:

<button onClick={
-  () => { 
-    refetchThreads()
-    }
+  () => {
+    // Only refetch if drafts were modified
+    if (draftWasModified) {
+      refetchThreads()
+    }
+  }
  } 
  className="dark:bg-panelDark flex items-center gap-1 rounded-lg bg-[#F0F0F0] px-2 py-1.5">
apps/server/src/lib/driver/google.ts (1)

727-740: Fix formatting issues in deleteDraft method

The method has inconsistent formatting that should be corrected for better readability.

-  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 (2)

13-20: Fix formatting issues in update mutation

The mutation has inconsistent spacing that should be corrected.

   update: activeDriverProcedure
     .input(createDraftData)
-    .mutation(async ({input, ctx}) =>{
-      const {activeConnection} = ctx;
+    .mutation(async ({ input, ctx }) => {
+      const { activeConnection } = ctx;
       const agent = await getZeroAgent(activeConnection.id);
       const res = await agent.updateDraft(input);
       return res;
     }),

21-29: Fix formatting issues in delete mutation

The mutation has inconsistent spacing that should be corrected.

   delete: activeDriverProcedure
-    .input(z.object({id: z.string()}))
-    .mutation(async({input,ctx})=>{
-      const {activeConnection} = ctx;
+    .input(z.object({ id: z.string() }))
+    .mutation(async ({ input, ctx }) => {
+      const { activeConnection } = ctx;
       const agent = await getZeroAgent(activeConnection.id);
-      const {id} = input;
+      const { id } = input;
       const res = await agent.deleteDraft(id);
       return res;
     }),
apps/mail/components/create/email-composer.tsx (4)

34-34: Remove unused import

The useQuery import is not used anywhere in this component.

-import { useMutation, useQuery } from '@tanstack/react-query';
+import { useMutation } from '@tanstack/react-query';

981-981: Remove extra space

-                > 
+                >

129-129: Remove unused destructured variable

The isFetching variable is destructured but never used.

-  const [{ isFetching, refetch: refetchThreads }] = useThreads();
+  const [{ refetch: refetchThreads }] = useThreads();

622-632: Consider implementing debounced auto-save

The current auto-save implementation triggers a save 3 seconds after any change, but rapid successive changes could cause multiple saves. Consider implementing a proper debounce mechanism.

Consider using a debounced save approach:

import { useDebouncedCallback } from 'use-debounce';

const debouncedSave = useDebouncedCallback(
  () => {
    saveDraft();
  },
  3000
);

useEffect(() => {
  if (hasUnsavedChanges) {
    debouncedSave();
  }
}, [hasUnsavedChanges, debouncedSave]);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 816279b and 33c8eef.

📒 Files selected for processing (7)
  • 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/chat.ts (2 hunks)
  • apps/server/src/trpc/routes/drafts.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
apps/server/src/lib/driver/types.ts (1)
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.
apps/mail/components/create/create-email.tsx (1)
Learnt from: retrogtx
PR: Mail-0/Zero#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.
apps/server/src/lib/driver/google.ts (1)
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.
apps/server/src/trpc/routes/drafts.ts (1)
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.
apps/mail/components/create/email-composer.tsx (1)
Learnt from: retrogtx
PR: Mail-0/Zero#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.
🧬 Code Graph Analysis (2)
apps/server/src/lib/driver/types.ts (1)
apps/server/src/lib/schemas.ts (1)
  • CreateDraftData (37-37)
apps/server/src/routes/chat.ts (1)
apps/server/src/lib/schemas.ts (1)
  • CreateDraftData (37-37)
⏰ 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 (7)
apps/server/src/lib/driver/types.ts (1)

60-65: LGTM! Interface changes follow established patterns.

The new updateDraft and deleteDraft methods are well-designed and consistent with the existing createDraft method signature. The type definitions are clear and appropriate for the intended functionality.

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

13-13: Good addition for thread management.

The useThreads hook import is appropriate for managing thread state updates when drafts are modified.


213-215: Good callback implementation for draft updates.

The onDraftUpdate callback properly triggers draft refetching when the EmailComposer component updates drafts, ensuring data consistency.

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

709-717: Simplified createDraft implementation looks good.

The createDraft method is now focused solely on creating new drafts, which aligns with the separation of concerns introduced by the new updateDraft method.

apps/server/src/routes/chat.ts (2)

221-227: Good delegation implementation in AgentRpcDO.

The updateDraft and deleteDraft methods follow the established pattern of delegating to mainDo, maintaining consistency with other methods in the class.


787-799: Consistent implementation in ZeroAgent.

Both methods properly check for driver availability and delegate to the driver, following the same pattern as other methods in the class. The error handling is consistent and appropriate.

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

612-623: Verify attachment.base64 return type and unify access

I’ve spotted an inconsistency in how attachment.base64 is consumed in apps/server/src/lib/driver/google.ts:

  • In createDraft (around line 616):
    const base64Data = await attachment.base64;
  • In updateDraft (around line 693):
    const base64Data = attachment.base64;

Please confirm whether attachment.base64 is a Promise<string> or a synchronous string on your CreateDraftData.attachments type (e.g. in apps/server/src/types.ts or wherever it’s defined). Once the return type is known, update both methods to use the same pattern:

  • If it returns Promise<string>, add await in updateDraft.
  • If it’s a plain string, remove await from createDraft.

@AnjanyKumarJaiswal
Copy link
Author

@MrgSub hey adam i have resolved all the AI comments and applied the best practice you can check the PR and please let me if any changes needs to be done

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
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 (2)
apps/mail/components/create/email-composer.tsx (2)

627-636: Auto-save effect implemented correctly.

The auto-save logic with 3-second delay is well implemented. However, the debug console.log should be removed as discussed in previous comments.

  const autoSaveTimer = setTimeout(() => {
-   console.log('Draft Save TimeOut');
    saveDraft();
  }, 3000);

128-128: isDeleteDraft state is still unused.

The isDeleteDraft state is declared but never set to true, making it ineffective for showing loading state during draft deletion.

  try {
+   setIsDeleteDraft(true);
    const response = await deleteDraft({id: draftId});
🧹 Nitpick comments (3)
apps/mail/components/create/email-composer.tsx (3)

115-116: Remove commented code.

The commented onDeleteDrafts prop should be removed to keep the code clean.

  onClose,
  onDraftUpdate,
- // onDeleteDrafts,
  className,

639-661: Function needs minor improvements.

The draft deletion logic is sound, and the setTimeout for the toast notification is correctly implemented for better UX. However, there are a few minor issues:

  1. Function name typo: handledeleteDraft should be handleDeleteDraft
  2. The values variable is declared but unused
-const handledeleteDraft = async () => {
-  const values = getValues();
+const handleDeleteDraft = async () => {
   if (!draftId) {

Also update the reference on line 698:

-handledeleteDraft();
+handleDeleteDraft();

1532-1538: Remove commented code.

The commented discard button should be removed to keep the codebase clean.

-        {/* <Button 
-        className='flex p-2 hover:text-black hover:bg-white max-h-[35px] h-screen bg-black text-center text-zinc-300 text-sm'
-        onClick={()=>{setShowLeaveConfirmation(true)}}
-        disabled = {editor.getText().trim().length < 1}
-        >
-        <Trash className='w-5 h-5 rounded-md'/>Discard</Button> */}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c3790a3 and 6599c07.

📒 Files selected for processing (1)
  • apps/mail/components/create/email-composer.tsx (13 hunks)
🧰 Additional context used
🧠 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.321Z
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.950Z
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 (5)
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.950Z
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.321Z
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: Mail-0/Zero#1354
File: apps/mail/components/ui/prompts-dialog.tsx:148-159
Timestamp: 2025-06-20T05:34:41.297Z
Learning: In the prompt management system, users should be allowed to save empty prompts as this gives them the choice to disable certain AI functionality or start fresh. Validation should not prevent empty prompts from being saved.
Learnt from: retrogtx
PR: Mail-0/Zero#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: Fahad-Dezloper
PR: Mail-0/Zero#1440
File: apps/mail/components/create/ai-chat.tsx:101-113
Timestamp: 2025-06-22T19:23:10.599Z
Learning: On small and medium screens (mobile devices), buttons don't naturally lose focus after being clicked like they do on desktop browsers. This requires manual intervention using setTimeout with additional blur() calls and DOM manipulation to properly clear focus and active states in button click handlers for responsive design.
🧬 Code Graph Analysis (1)
apps/mail/components/create/email-composer.tsx (4)
apps/server/src/routes/chat.ts (6)
  • updateDraft (215-217)
  • updateDraft (791-796)
  • deleteDraft (219-221)
  • deleteDraft (798-803)
  • createDraft (211-213)
  • createDraft (784-789)
apps/server/src/lib/driver/google.ts (3)
  • updateDraft (682-759)
  • deleteDraft (760-773)
  • createDraft (604-681)
apps/server/src/lib/driver/microsoft.ts (2)
  • updateDraft (718-785)
  • createDraft (632-717)
apps/mail/components/icons/icons.tsx (1)
  • Trash (940-955)
🔇 Additional comments (10)
apps/mail/components/create/email-composer.tsx (10)

18-18: Import looks good.

The Trash icon import is correctly added to support the new discard functionality.


38-38: Good addition for thread management.

The useThreads import is correctly added to support thread refetching after draft operations.


77-77: Well-defined callback prop.

The onDraftUpdate prop is properly typed and will enable parent components to react to draft updates.


129-129: Good implementation of thread refetching.

The refetchThreads function is correctly destructured from useThreads hook to refresh the thread list after draft operations.


259-260: Mutations properly implemented.

The updateDraft and deleteDraft mutations are correctly set up using tRPC for the new draft management functionality.


588-609: Draft save logic has been improved.

The conditional logic for updating existing drafts vs creating new ones is working as intended. The try-catch error handling provides better user feedback.


664-673: Auto-save on close is well implemented.

The handleClose function correctly auto-saves the draft before showing the confirmation dialog when there's content, providing a good user experience.


985-988: Close button styling improved.

The close button styling and functionality are correctly implemented.


1637-1637: Discard button with trash icon is well designed.

The trash icon in the discard button provides clear visual feedback for the destructive action.


610-615: Remove duplicate code.

Lines 610-615 contain duplicate createDraft logic that should not be there. This appears to be a merge conflict or copy-paste error.

-      const response = await createDraft(draftData);
-      if(response?.id){
-        setDraftId(response?.id);
-        toast.success("Your Draft has been Successfully Saved")
-      }
-    }
⛔ 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.950Z
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.321Z
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.

const { mutateAsync: sendEmail } = useMutation(trpc.mail.send.mutationOptions());
const [isComposeOpen, setIsComposeOpen] = useQueryState('isComposeOpen');
const [, setThreadId] = useQueryState('threadId');
const [{refetch: refetchThreads }] = useThreads();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need to refetch threads?

Copy link
Author

Choose a reason for hiding this comment

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

actually why i did it because when the user clicks the esc button the emails are not refreshed automatically though
it needs to be done manually

if(response?.id){
setDraftId(response.id);
onDraftUpdate?.();
toast.success("Your Draft has been Successfully Saved")
Copy link
Collaborator

Choose a reason for hiding this comment

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

no toast please

Copy link
Author

Choose a reason for hiding this comment

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

but why shouldnt we show a message for confirmation that the user draft is saved?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants