feat: finally done with Auto-Save , Update and Delete Drafts #1732
feat: finally done with Auto-Save , Update and Delete Drafts #1732AnjanyKumarJaiswal wants to merge 16 commits intoMail-0:stagingfrom
Conversation
…yKumarJaiswal/mail0 into feat/autoSave_Email_drafts
…yKumarJaiswal/mail0 into feat/autoSave_Email_drafts
…yKumarJaiswal/mail0 into feat/autoSave_Email_drafts
WalkthroughThis 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
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()
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
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
- The PR implements critical user-facing functionality with complex state management
- Score reflects solid implementation but lack of automated tests and incomplete security review
- 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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 updateDraftThe
updateDraftmethod contains nearly identical code tocreateDraft. 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 methodThe 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 mutationThe 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 mutationThe 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 importThe
useQueryimport 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 variableThe
isFetchingvariable is destructured but never used.- const [{ isFetching, refetch: refetchThreads }] = useThreads(); + const [{ refetch: refetchThreads }] = useThreads();
622-632: Consider implementing debounced auto-saveThe 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
📒 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
updateDraftanddeleteDraftmethods are well-designed and consistent with the existingcreateDraftmethod 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
useThreadshook import is appropriate for managing thread state updates when drafts are modified.
213-215: Good callback implementation for draft updates.The
onDraftUpdatecallback 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: Verifyattachment.base64return type and unify accessI’ve spotted an inconsistency in how
attachment.base64is consumed inapps/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.base64is aPromise<string>or a synchronousstringon yourCreateDraftData.attachmentstype (e.g. inapps/server/src/types.tsor wherever it’s defined). Once the return type is known, update both methods to use the same pattern:
- If it returns
Promise<string>, addawaitinupdateDraft.- If it’s a plain
string, removeawaitfromcreateDraft.
|
@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>
There was a problem hiding this comment.
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
isDeleteDraftstate is declared but never set totrue, 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
onDeleteDraftsprop 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
setTimeoutfor the toast notification is correctly implemented for better UX. However, there are a few minor issues:
- Function name typo:
handledeleteDraftshould behandleDeleteDraft- The
valuesvariable 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
📒 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
Trashicon import is correctly added to support the new discard functionality.
38-38: Good addition for thread management.The
useThreadsimport is correctly added to support thread refetching after draft operations.
77-77: Well-defined callback prop.The
onDraftUpdateprop is properly typed and will enable parent components to react to draft updates.
129-129: Good implementation of thread refetching.The
refetchThreadsfunction is correctly destructured fromuseThreadshook to refresh the thread list after draft operations.
259-260: Mutations properly implemented.The
updateDraftanddeleteDraftmutations 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
handleClosefunction 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
createDraftlogic 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(); |
There was a problem hiding this comment.
Why do we need to refetch threads?
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
but why shouldnt we show a message for confirmation that the user draft is saved?
Created Auto Save , Auto Update and delete drafts
Type of Change
Please delete options that are not relevant.
Areas Affected
Please check all that apply:
Testing Done
Describe the tests you've done:
Security Considerations
For changes involving data or authentication:
Checklist
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.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes