Merge pull request #861 from dealerweb/fix/fresh-compose-tab-title

fix: keep the fresh-compose tab title clear of the selected email subject
This commit is contained in:
Linus Rath
2026-08-25 19:26:54 +02:00
committed by GitHub
3 changed files with 63 additions and 12 deletions
+9 -12
View File
@@ -94,7 +94,7 @@ import { plainTextToComposerBody, getQuoteBodies } from "@/lib/email-composer-ut
import { appLifecycleHooks, uiHooks, routerHooks, toastHooks, emailHooks } from "@/lib/plugin-hooks";
import { emailToReadView } from "@/lib/plugin-projection";
import { buildQuoteHeader } from "@/lib/quote-header";
import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix";
import { buildComposeTabTitle, buildReplySubject } from "@/lib/subject-prefix";
import { buildForwardAsAttachmentPayload } from "@/lib/forward-as-attachment";
import { getEffectiveLocale } from '@/i18n/detect-locale';
import {
@@ -913,17 +913,14 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
} : undefined);
const effectiveMode = pendingDraft?.mode ?? composerMode;
const baseSubject = (pendingDraft?.subject?.trim() || selectedEmail?.subject?.trim()) ?? '';
let title = t('email_composer.new_message');
if (baseSubject) {
if (effectiveMode === 'reply' || effectiveMode === 'replyAll') {
title = buildReplySubject(baseSubject, t('email_composer.prefix.reply'));
} else if (effectiveMode === 'forward') {
title = buildForwardSubject(baseSubject, t('email_composer.prefix.forward'));
} else {
title = baseSubject;
}
}
const title = buildComposeTabTitle({
mode: effectiveMode,
draftSubject: pendingDraft?.subject,
sourceSubject: selectedEmail?.subject,
replyPrefix: t('email_composer.prefix.reply'),
forwardPrefix: t('email_composer.prefix.forward'),
fallback: t('email_composer.new_message'),
});
useProTabStore.getState().openComposeTab({
sessionId: composerSessionId + 1,
+31
View File
@@ -3,6 +3,7 @@ import {
stripSubjectPrefixes,
buildReplySubject,
buildForwardSubject,
buildComposeTabTitle,
} from '@/lib/subject-prefix';
describe('stripSubjectPrefixes', () => {
@@ -54,3 +55,33 @@ describe('buildReplySubject / buildForwardSubject', () => {
expect(buildReplySubject('', 'AW:')).toBe('AW:');
});
});
describe('buildComposeTabTitle', () => {
const prefixes = { replyPrefix: 'Re:', forwardPrefix: 'Fwd:', fallback: 'New message' };
it('never inherits the selected email subject for a fresh compose (#856)', () => {
expect(buildComposeTabTitle({ mode: 'compose', sourceSubject: 'Invoice August 2026', ...prefixes }))
.toBe('New message');
});
it('uses the draft subject for a fresh compose when one exists (mailto, draft edit)', () => {
expect(buildComposeTabTitle({ mode: 'compose', draftSubject: 'Lunch?', sourceSubject: 'Invoice August 2026', ...prefixes }))
.toBe('Lunch?');
});
it('derives reply and forward titles from the source subject', () => {
expect(buildComposeTabTitle({ mode: 'reply', sourceSubject: 'Invoice August 2026', ...prefixes }))
.toBe('Re: Invoice August 2026');
expect(buildComposeTabTitle({ mode: 'replyAll', sourceSubject: 'AW: Invoice', ...prefixes }))
.toBe('Re: Invoice');
expect(buildComposeTabTitle({ mode: 'forward', sourceSubject: 'Invoice', ...prefixes }))
.toBe('Fwd: Invoice');
});
it('prefers a resumed draft subject over the source and falls back when both are blank', () => {
expect(buildComposeTabTitle({ mode: 'reply', draftSubject: 'Re: edited', sourceSubject: 'Original', ...prefixes }))
.toBe('Re: edited');
expect(buildComposeTabTitle({ mode: 'reply', sourceSubject: ' ', ...prefixes }))
.toBe('New message');
});
});
+23
View File
@@ -119,3 +119,26 @@ export function buildForwardSubject(subject: string | undefined | null, prefix:
const stripped = stripSubjectPrefixes(subject);
return stripped ? `${prefix} ${stripped}` : prefix;
}
/**
* Working title for a compose tab. Reply and forward derive it from the
* message they act on; a fresh compose must NOT inherit the subject of
* whatever email happens to be selected in the list (#856) - it starts on
* the fallback ("New message") until a draft subject exists.
*/
export function buildComposeTabTitle(opts: {
mode: 'compose' | 'reply' | 'replyAll' | 'forward';
draftSubject?: string | null;
sourceSubject?: string | null;
replyPrefix: string;
forwardPrefix: string;
fallback: string;
}): string {
const draft = opts.draftSubject?.trim() ?? '';
const source = opts.mode === 'compose' ? '' : (opts.sourceSubject?.trim() ?? '');
const base = draft || source;
if (!base) return opts.fallback;
if (opts.mode === 'reply' || opts.mode === 'replyAll') return buildReplySubject(base, opts.replyPrefix);
if (opts.mode === 'forward') return buildForwardSubject(base, opts.forwardPrefix);
return base;
}