From a0429fc037b75f059429c0aecff8a966cea6383a Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Sat, 20 Jun 2026 02:32:08 +0530 Subject: [PATCH] email reply all and first name signoff --- .../renderer/src/components/email-view.tsx | 42 +++++++++++-------- .../assistant/skills/draft-emails/skill.ts | 1 + .../core/src/knowledge/classify_thread.ts | 4 +- .../core/src/knowledge/inline_task_agent.ts | 2 +- .../core/src/pre_built/email-draft.md | 1 + 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/apps/x/apps/renderer/src/components/email-view.tsx b/apps/x/apps/renderer/src/components/email-view.tsx index b8b2e537..5d62898a 100644 --- a/apps/x/apps/renderer/src/components/email-view.tsx +++ b/apps/x/apps/renderer/src/components/email-view.tsx @@ -906,7 +906,7 @@ const AI_GENERATE_SYSTEM = '\n' + 'Do not use markdown. Do not add any commentary, labels, or surrounding quotes. ' + 'When recipient names are provided, address them naturally (e.g. "Hi ,"). ' + - 'When the sender\'s name is provided, sign off with it; otherwise omit the sign-off name ' + + 'When the sender\'s first name is provided, sign off with that first name only; otherwise omit the sign-off name ' + '(never write a placeholder like "[Your Name]").' const AI_REWRITE_SYSTEM = @@ -931,8 +931,13 @@ function parseGeneratedEmail(text: string): { subject: string | null; body: stri return { subject: null, body: text } } -// Guarantee the sender's name signs off the email. If the model already ended -// with the name (e.g. "Best,\nHarsh"), leave it; otherwise append it. +function firstNameFromDisplayName(name: string): string { + const trimmed = name.trim().replace(/^["']|["']$/g, '') + return trimmed.split(/\s+/)[0] || '' +} + +// Guarantee the sender's first name signs off the email. If the model already +// ended with the name (e.g. "Best,\nHarsh"), leave it; otherwise append it. function ensureSignature(body: string, name: string): string { const signer = name.trim() if (!signer) return body @@ -977,7 +982,7 @@ const ComposeBox = memo(function ComposeBox({ const [showCc, setShowCc] = useState(initialRecipients.cc.length > 0) const [showBcc, setShowBcc] = useState(false) const [subject, setSubject] = useState(() => (thread ? composeSubject(mode, thread.subject) : '')) - const modeLabel = isNew ? 'New message' : mode === 'forward' ? 'Forward' : mode === 'replyAll' ? 'Reply all' : 'Reply' + const modeLabel = isNew ? 'New message' : mode === 'forward' ? 'Forward' : mode === 'replyAll' ? 'Reply All' : 'Reply' const initialContent = useMemo(() => { if (!thread) return '' @@ -1050,6 +1055,7 @@ const ComposeBox = memo(function ComposeBox({ // The signed-in account's display name, used to sign off AI-generated emails. const [selfName, setSelfName] = useState('') + const selfFirstName = useMemo(() => firstNameFromDisplayName(selfName), [selfName]) useEffect(() => { if (!isNew) return let cancelled = false @@ -1104,7 +1110,7 @@ const ComposeBox = memo(function ComposeBox({ }) .filter(Boolean) if (recipientNames.length) ctx.push(`Recipient(s): ${recipientNames.join(', ')}`) - if (selfName) ctx.push(`Sender's name (sign off as this): ${selfName}`) + if (selfFirstName) ctx.push(`Sender's first name (sign off as this): ${selfFirstName}`) if (subject.trim()) ctx.push(`Desired subject hint: ${subject.trim()}`) if (current) ctx.push(`Existing draft (revise or build on it):\n${current}`) prompt = `${ctx.length ? ctx.join('\n') + '\n\n' : ''}Instruction: ${instruction.trim()}` @@ -1130,8 +1136,8 @@ const ComposeBox = memo(function ComposeBox({ if (aiMode === 'generate') { const { subject: generatedSubject, body } = parseGeneratedEmail(res.text) if (generatedSubject) setSubject(generatedSubject) - // Always sign off with the account name, even if the model omitted it. - const signed = ensureSignature(body, selfName) + // Always sign off with the account first name, even if the model omitted it. + const signed = ensureSignature(body, selfFirstName) editor.chain().focus().selectAll().insertContent(plainTextToHtml(signed)).run() setHasGenerated(true) } else { @@ -1495,10 +1501,17 @@ function ThreadDetail({ return () => { cancelled = true } }, []) - const canReplyAll = useMemo(() => { - const { to, cc } = buildRecipients('replyAll', thread, selfEmail) - return cc.length > 0 || to.length > 1 - }, [thread, selfEmail]) + const replyAllRecipients = useMemo( + () => buildRecipients('replyAll', thread, selfEmail), + [thread, selfEmail], + ) + const canReplyAll = replyAllRecipients.cc.length > 0 || replyAllRecipients.to.length > 1 + const replyAllButton = canReplyAll ? ( + + ) : null const toggleExpand = useCallback((index: number) => { setExpandedIndices((prev) => { @@ -1568,16 +1581,11 @@ function ThreadDetail({
+ {replyAllButton} - {canReplyAll && ( - - )}