Conversation
WalkthroughThe undo-send hook now branches for scheduled vs immediate sends, clamps negative time remaining to zero, standardizes 15s undo/toast behavior, and refines toast actions and post-undo restoration. Server-side, the default scheduling delay is reduced from 30s to 15s when no scheduleAt is provided. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Mail UI (Composer)
participant Hook as use-undo-send
participant Server
User->>UI: Send
UI->>Hook: onSend(emailData)
alt emailData.scheduleAt provided
Hook->>Hook: wasUserScheduled = true
Hook->>UI: Show "Email scheduled" toast (15s, Undo)
opt User clicks Undo within window
Hook->>Server: unsendEmail(messageId)
Server-->>Hook: success/failure
Hook->>UI: Show "Schedule cancelled"/"Failed to cancel"
end
else immediate send (no scheduleAt)
Hook->>Hook: wasUserScheduled = false
Hook->>UI: Show "Email sent" toast (15s, Undo)
opt User clicks Undo within window
Hook->>Server: unsendEmail(messageId)
Server-->>Hook: success/failure
Hook->>Hook: serialize attachments -> undoEmailData
Hook->>UI: Update URL params to reopen composer
Hook->>UI: Show "Send cancelled"/"Failed to cancel"
end
end
sequenceDiagram
participant UI as Mail UI
participant Server
UI->>Server: sendMail({ scheduleAt? })
alt scheduleAt provided
Server->>Server: targetTime = scheduleAt
else no scheduleAt, scheduling enabled
Server->>Server: targetTime = now + 15s (was 30s)
end
Server->>Server: rawDelaySeconds = floor((targetTime - now)/1000)
Server-->>UI: queued/scheduled response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
cubic analysis
1 issue found across 2 files • Review in cubic
React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.
| } | ||
| }, | ||
| }, | ||
| duration: 15_000, |
There was a problem hiding this comment.
Toast duration is hard-coded to 15 000 ms instead of matching the computed timeRemaining, so the undo button may remain visible after the backend window has closed
Prompt for AI agents
Address the following comment on apps/mail/hooks/use-undo-send.ts at line 88:
<comment>Toast duration is hard-coded to 15 000 ms instead of matching the computed timeRemaining, so the undo button may remain visible after the backend window has closed</comment>
<file context>
@@ -68,13 +68,31 @@ export const useUndoSend = () => {
if (isSendResult(result) && settings?.settings?.undoSendEnabled) {
const { messageId, sendAt } = result;
- const timeRemaining = sendAt ? sendAt - Date.now() : 15_000;
+ const timeRemaining = sendAt ? Math.max(0, sendAt - Date.now()) : 15_000;
+ const wasUserScheduled = Boolean(emailData?.scheduleAt);
if (timeRemaining > 5_000) {
- toast.success('Email scheduled', {
</file context>
| duration: 15_000, | |
| duration: timeRemaining, |
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
apps/mail/hooks/use-undo-send.ts (1)
93-124: Decouple undo cancellation from attachment restore; fix misleading error toast.Right now, any failure during serialization/storage will show “Failed to cancel,” even if unsend succeeded. Let’s separate concerns: show failure only if unsend fails; treat restore failures as non-blocking warnings. Also add a small guard to avoid blowing up localStorage when attachments are large.
Apply this diff:
- onClick: async () => { - try { - await unsendEmail({ messageId }); - - if (emailData) { - const serializedAttachments = await serializeFiles(emailData.attachments); - const serializableData: SerializableEmailData = { - ...emailData, - attachments: serializedAttachments, - }; - localStorage.setItem('undoEmailData', JSON.stringify(serializableData)); - } - - const url = new URL(window.location.href); - url.searchParams.delete('activeReplyId'); - url.searchParams.delete('mode'); - url.searchParams.delete('draftId'); - url.searchParams.set('isComposeOpen', 'true'); - window.history.replaceState({}, '', url.toString()); - - toast.info('Send cancelled'); - } catch { - toast.error('Failed to cancel'); - } - }, + onClick: async () => { + // 1) Cancel send. If this fails, bail early with an error toast. + try { + await unsendEmail({ messageId }); + } catch { + toast.error('Failed to cancel'); + return; + } + + // 2) Best-effort restore of compose state (non-blocking for UX). + try { + if (emailData) { + // Avoid localStorage quota explosions; ~1.5MB cap. + const totalBytes = emailData.attachments?.reduce((s, f) => s + (f?.size || 0), 0) || 0; + if (totalBytes <= 1.5 * 1024 * 1024) { + const serializedAttachments = await serializeFiles(emailData.attachments); + const serializableData: SerializableEmailData = { + ...emailData, + attachments: serializedAttachments, + }; + localStorage.setItem('undoEmailData', JSON.stringify(serializableData)); + } else { + console.warn('[undo-send] Attachments too large to persist in localStorage; skipping restore.'); + } + } + } catch (e) { + console.warn('[undo-send] Failed to serialize/store attachments for undo restore', e); + // Continue; unsend already succeeded. + } + + // 3) Reopen composer via URL flags. + const url = new URL(window.location.href); + url.searchParams.delete('activeReplyId'); + url.searchParams.delete('mode'); + url.searchParams.delete('draftId'); + url.searchParams.set('isComposeOpen', 'true'); + window.history.replaceState({}, '', url.toString()); + + toast.info('Send cancelled'); + },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
apps/mail/hooks/use-undo-send.ts(2 hunks)apps/server/src/trpc/routes/mail.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{css,js,ts,jsx,tsx,mdx}
📄 CodeRabbit Inference Engine (.cursor/rules/tailwind-css-v4.mdc)
**/*.{css,js,ts,jsx,tsx,mdx}: Chain variants together for composable variants (e.g.,group-has-data-potato:opacity-100).
Use new variants such asstarting,not-*,inert,nth-*,in-*,open(for:popover-open), and**for all descendants.
Do not use deprecated utilities likebg-opacity-*,text-opacity-*,border-opacity-*, anddivide-opacity-*; use the new syntax (e.g.,bg-black/50).
Use renamed utilities:shadow-smis nowshadow-xs,shadowis nowshadow-sm,drop-shadow-smis nowdrop-shadow-xs,drop-shadowis nowdrop-shadow-sm,blur-smis nowblur-xs,bluris nowblur-sm,rounded-smis nowrounded-xs,roundedis nowrounded-sm,outline-noneis nowoutline-hidden.
Usebg-(--brand-color)syntax for CSS variables in arbitrary values instead ofbg-[--brand-color].
Stacked variants now apply left-to-right instead of right-to-left.
Files:
apps/server/src/trpc/routes/mail.tsapps/mail/hooks/use-undo-send.ts
**/*.{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
Semicolons are required
Files:
apps/server/src/trpc/routes/mail.tsapps/mail/hooks/use-undo-send.ts
**/*.{js,jsx,ts,tsx,css}
📄 CodeRabbit Inference Engine (AGENT.md)
Use Prettier with sort-imports and Tailwind plugins
Files:
apps/server/src/trpc/routes/mail.tsapps/mail/hooks/use-undo-send.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (AGENT.md)
Enable TypeScript strict mode
Files:
apps/server/src/trpc/routes/mail.tsapps/mail/hooks/use-undo-send.ts
🧠 Learnings (4)
📓 Common 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#1573
File: apps/mail/components/create/template-button.tsx:197-216
Timestamp: 2025-07-28T05:37:50.566Z
Learning: retrogtx prefers less nitpicky code review suggestions and may dismiss detailed accessibility/best practice recommendations with casual responses like "bro please".
📚 Learning: 2025-06-28T03:56:09.376Z
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/trpc/routes/mail.ts
📚 Learning: 2025-06-27T04:59:29.731Z
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/trpc/routes/mail.ts
📚 Learning: 2025-07-15T03:31:14.991Z
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/hooks/use-undo-send.ts
⏰ 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). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/mail/hooks/use-undo-send.ts (3)
71-73: Clamp + 15s default: robust and consistent.Clamping negative time and defaulting to 15_000 makes this resilient and consistent with server. The wasUserScheduled flag is the right axis for branching UX.
93-124: undoEmailData is properly cleared after consumptionI’ve confirmed that
localStorage.removeItem('undoEmailData')is invoked inapps/mail/components/create/create-email.tsx(around line 151), so the data is purged once it’s used. No further changes needed here—onward and upward! 🚀
75-91: closeButton prop validated: Ran a repo-wide audit—closeButtonis our standard toast option anddismissibledoesn’t exist. Sonner supportscloseButtonin our version. Let’s launch!
| targetTime = parsedTime; | ||
| } else { | ||
| targetTime = Date.now() + 30_000; | ||
| targetTime = Date.now() + 15_000; | ||
| } | ||
|
|
||
| const rawDelaySeconds = Math.floor((targetTime - Date.now()) / 1000); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
15s default undo window set server-side — aligned and faster.
Good change. This halves the latency tax on users and syncs with the new UI toast duration.
Run a quick sweep to ensure no 30s remnants linger elsewhere (server or client):
🏁 Script executed:
#!/bin/bash
# Find hard-coded 30s durations/delays
rg -n --hidden -S '30_000|30000|duration:\s*30000|delaySeconds\W*30'Length of output: 209
🏁 Script executed:
#!/bin/bash
# Inspect the usage of the 30s RESYNC_COOLDOWN_MS constant in mail.ts
rg -n -C 3 'RESYNC_COOLDOWN_MS' apps/server/src/trpc/routes/mail.tsLength of output: 553
Remaining 30 s constant in mail.ts must be addressed
Quick sweep turned up one true 30 000 ms delay in your TRPC route:
• apps/server/src/trpc/routes/mail.ts (around line 174):
const RESYNC_COOLDOWN_MS = 30000;
Please confirm whether this should be reduced to 15 000 ms to match the new UI toast duration—or left intentionally at 30 s if it serves a different purpose.
(The “cpu_ms”: 300000 in wrangler.jsonc is just your worker’s CPU limit and can be ignored here.)
🤖 Prompt for AI Agents
In apps/server/src/trpc/routes/mail.ts around lines 516 to 521, there is a
hardcoded 30_000 ms cooldown constant (RESYNC_COOLDOWN_MS) that conflicts with
the new UI toast duration; change RESYNC_COOLDOWN_MS from 30000 to 15000 to
match the UI (or, if 30s is intentional for a different reason, add a clear
comment explaining the intentional difference and leave it as-is), and update
any dependent calculations or tests that assume 30_000 ms to use the new value.
Summary by cubic
Added a close (cross) button to the undo email toast and reduced the undo window from 30 seconds to 15 seconds.
Summary by CodeRabbit
New Features
Bug Fixes