mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
13e2855484
commit
54ec8c6584
@@ -32,6 +32,7 @@ const FEATURE_GATE_LABELS: Partial<Record<keyof FeatureGates, { label: string; d
|
||||
debugModeEnabled: { label: 'Debug Mode', description: 'Allow users to enable debug/diagnostic mode' },
|
||||
folderIconsEnabled: { label: 'Folder Icons', description: 'Allow custom folder icon picker' },
|
||||
hoverActionsConfigEnabled: { label: 'Hover Actions Config', description: 'Allow users to customize email hover actions' },
|
||||
tabTitleSubjectEnabled: { label: 'Subject in Tab Title', description: 'Show the open message\'s subject in the browser tab title. Browsers also keep the title in history and screen shares show it; turn this off when mailboxes hold confidential correspondence, and the tab shows the folder instead.' },
|
||||
filesEnabled: { label: 'Files (WebDAV)', description: 'Enable file storage via WebDAV. WARNING: Large uploads can cause Stalwart/RocksDB instability. Not recommended for production.' },
|
||||
crossUnreadViewEnabled: { label: 'Unified Mailbox: Unread', description: 'Allow an "Unread" entry in the Unified Mailbox section that lists unread mail across the account and its shared folders (or every account when the cross-account sub-option is on). Honors the user\'s folder selection. Requires the matching per-user toggle in Settings → Appearance.' },
|
||||
crossStarredViewEnabled: { label: 'Unified Mailbox: Starred', description: 'Allow a "Starred" entry in the Unified Mailbox section that lists flagged/starred mail across the account and its shared folders (or every account when the cross-account sub-option is on). Honors the user\'s folder selection. Requires the matching per-user toggle in Settings → Appearance.' },
|
||||
|
||||
@@ -944,6 +944,9 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
}, [clearPendingUndoSend, pendingUndoSend]);
|
||||
|
||||
// Page title: "<context> - <account> - <app>" (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,
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
+12
-3
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user