From 54ec8c6584ef8b10da699b6c7f64c29caad726a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Y=C4=B1lmaz?= <33634879+oguzyilmaznet@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:13:06 +0300 Subject: [PATCH] feat: let operators keep the message subject out of the tab title Opening a message puts its subject in the browser-tab title. The title does not stay in the tab strip: browsers keep it in history and in the session-restore list, and screen shares show it. For mailboxes that hold other people's correspondence that is a leak nobody audits. A new feature gate, tabTitleSubjectEnabled ("Subject in Tab Title" under Admin -> Policy), is on by default, so nothing changes for existing installs. With it off, the title names the folder instead, so the tab still says where you are. It is an admin gate rather than a user setting: whose mail the boxes hold is a deployment decision. The gate counts as off until the policy has loaded, so a subject never slips into history while the setting is still unknown. Refs #1159 Co-Authored-By: Claude Opus 5.5 --- app/(main)/admin/_tabs/policy.tsx | 1 + components/mail/mail-app.tsx | 7 +++++-- lib/__tests__/tab-title.test.ts | 8 ++++++++ lib/admin/types.ts | 2 ++ lib/tab-title.ts | 15 ++++++++++++--- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/(main)/admin/_tabs/policy.tsx b/app/(main)/admin/_tabs/policy.tsx index 29c1a6a56..bab81c8ad 100644 --- a/app/(main)/admin/_tabs/policy.tsx +++ b/app/(main)/admin/_tabs/policy.tsx @@ -32,6 +32,7 @@ const FEATURE_GATE_LABELS: Partial - - " (see useDocumentTitle) + // The subject is kept out until the policy has loaded: a gate that keeps + // subjects out of browser history must not let one through while unknown. + const subjectInTabTitle = usePolicyStore((s) => s.loaded && s.isFeatureEnabled('tabTitleSubjectEnabled')); const titleContext = useMemo(() => { // Composing email const composer = showComposer @@ -967,8 +970,8 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { mailboxLine = unreadCount > 0 ? `${mailboxName} (${unreadCount})` : mailboxName; } - return mailTitleContext({ composer, subject, mailbox: mailboxLine }); - }, [showComposer, composerMode, selectedEmail, selectedMailbox, mailboxes, t]); + return mailTitleContext({ composer, subject, mailbox: mailboxLine }, { showSubject: subjectInTabTitle }); + }, [showComposer, composerMode, selectedEmail, selectedMailbox, mailboxes, t, subjectInTabTitle]); useDocumentTitle(titleContext); // When this page is rendered inside the Pro shell as the Mail tab body, diff --git a/lib/__tests__/tab-title.test.ts b/lib/__tests__/tab-title.test.ts index d8c67c4c0..1b0073fe8 100644 --- a/lib/__tests__/tab-title.test.ts +++ b/lib/__tests__/tab-title.test.ts @@ -41,4 +41,12 @@ describe('mailTitleContext', () => { it('has nothing to add before a mailbox is selected', () => { expect(mailTitleContext({})).toBeNull(); }); + + it('names the mailbox instead of the open message when the subject is kept out', () => { + expect(mailTitleContext({ ...view, composer: null }, { showSubject: false })).toBe('Inbox (3)'); + }); + + it('still names the composer when the subject is kept out', () => { + expect(mailTitleContext(view, { showSubject: false })).toBe('Reply'); + }); }); diff --git a/lib/admin/types.ts b/lib/admin/types.ts index 25f7a1e7a..437712c37 100644 --- a/lib/admin/types.ts +++ b/lib/admin/types.ts @@ -58,6 +58,7 @@ export interface FeatureGates { debugModeEnabled: boolean; folderIconsEnabled: boolean; hoverActionsConfigEnabled: boolean; + tabTitleSubjectEnabled: boolean; filesEnabled: boolean; contactsEnabled: boolean; /** @deprecated Folded into `crossAllViewEnabled`; normalized forward on policy load. */ @@ -85,6 +86,7 @@ export const DEFAULT_FEATURE_GATES: FeatureGates = { debugModeEnabled: true, folderIconsEnabled: true, hoverActionsConfigEnabled: true, + tabTitleSubjectEnabled: true, filesEnabled: true, contactsEnabled: true, allMailViewEnabled: false, diff --git a/lib/tab-title.ts b/lib/tab-title.ts index 37bd7e216..87fa1ce2f 100644 --- a/lib/tab-title.ts +++ b/lib/tab-title.ts @@ -21,7 +21,16 @@ export interface MailTitleView { mailbox?: string | null; } -/** The context part of the mail view's tab title: composer, then message, then mailbox. */ -export function mailTitleContext(view: MailTitleView): string | null { - return view.composer || view.subject || view.mailbox || null; +/** + * The context part of the mail view's tab title: composer, then message, then + * mailbox. The subject does not stay in the tab strip - browsers keep it in + * history and in the session-restore list, and screen shares show it - so an + * operator can keep it out (`tabTitleSubjectEnabled`). The mailbox line is + * shown instead, so the tab still says where you are. (#1159) + */ +export function mailTitleContext( + view: MailTitleView, + { showSubject = true }: { showSubject?: boolean } = {}, +): string | null { + return view.composer || (showSubject ? view.subject : null) || view.mailbox || null; }