feat: play the involved sound for new mail by default

This commit is contained in:
Linus Rath
2026-10-31 01:21:02 +01:00
parent dafdda5347
commit 5eabdd8904
32 changed files with 100 additions and 32 deletions
+3 -1
View File
@@ -3,6 +3,8 @@ import { withBasePath } from '@/lib/browser-navigation';
export type NotificationSoundChoice = 'default' | 'cheerful' | 'involved' | 'swift' | 'relax';
// 'default' is the beep. It was the default sound until Involved replaced
// it, and keeps its id so stored and synced choices still match.
export const NOTIFICATION_SOUNDS: { id: NotificationSoundChoice; file?: string }[] = [
{ id: 'default' },
{ id: 'cheerful', file: '/notification/cheerful-527.mp3' },
@@ -53,7 +55,7 @@ function playFile(file: string) {
export function playNotificationSound(sound?: NotificationSoundChoice) {
try {
const choice = sound ?? 'default';
const choice = sound ?? 'involved';
const entry = NOTIFICATION_SOUNDS.find((s) => s.id === choice);
if (entry?.file) {
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "اختبار صوت الإشعار",
"sounds": {
"default": "افتراضي (تنبيه)",
"default": "تنبيه",
"cheerful": "مرح",
"involved": "متفاعل",
"swift": "إيماءة سريعة",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Prova el so de notificació",
"sounds": {
"default": "Per defecte (bip)",
"default": "Bip",
"cheerful": "Alegre",
"involved": "Elaborat",
"swift": "Gest ràpid",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Otestovat zvuk oznámení",
"sounds": {
"default": "Výchozí (Pípnutí)",
"default": "Pípnutí",
"cheerful": "Veselý",
"involved": "Dynamický",
"swift": "Rychlý",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Test notifikationslyd",
"sounds": {
"default": "Standard (Bip)",
"default": "Bip",
"cheerful": "Glad",
"involved": "Engageret",
"swift": "Hurtig gestus",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Benachrichtigungston testen",
"sounds": {
"default": "Standard (Piepton)",
"default": "Piepton",
"cheerful": "Fröhlich",
"involved": "Aufwendig",
"swift": "Schnelle Geste",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Test notification sound",
"sounds": {
"default": "Default (Beep)",
"default": "Beep",
"cheerful": "Cheerful",
"involved": "Involved",
"swift": "Swift Gesture",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Probar sonido de notificación",
"sounds": {
"default": "Predeterminado (Pitido)",
"default": "Pitido",
"cheerful": "Alegre",
"involved": "Elaborado",
"swift": "Gesto rápido",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Testi teavitusheli",
"sounds": {
"default": "Vaikimisi (piiks)",
"default": "Piiks",
"cheerful": "Rõõmus",
"involved": "Elav",
"swift": "Kiire liigutus",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "تست صدای اعلان",
"sounds": {
"default": "پیش‌فرض (بیپ)",
"default": "بیپ",
"cheerful": "شاد",
"involved": "درگیر",
"swift": "سریع",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Tester le son de notification",
"sounds": {
"default": "Par défaut (Bip)",
"default": "Bip",
"cheerful": "Joyeux",
"involved": "Élaboré",
"swift": "Geste rapide",
+1 -1
View File
@@ -1595,7 +1595,7 @@
"notifications": {
"test_sound": "צליל הודעת בדיקה",
"sounds": {
"default": "ברירת מחדל (ביפ)",
"default": "ביפ",
"cheerful": "עליז",
"involved": "מעורב",
"swift": "מחווה מהירה",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Értesítési hang tesztelése",
"sounds": {
"default": "Alapértelmezett (sípolás)",
"default": "Sípolás",
"cheerful": "Vidám",
"involved": "Lelkes",
"swift": "Gyors mozdulat",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Testa il suono di notifica",
"sounds": {
"default": "Predefinito (Bip)",
"default": "Bip",
"cheerful": "Allegro",
"involved": "Elaborato",
"swift": "Gesto veloce",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "通知音をテスト",
"sounds": {
"default": "デフォルト(ビープ)",
"default": "ビープ",
"cheerful": "チアフル",
"involved": "インボルブド",
"swift": "スウィフトジェスチャー",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "알림음 테스트",
"sounds": {
"default": "기본 (비프음)",
"default": "비프음",
"cheerful": "밝게",
"involved": "경쾌하게",
"swift": "빠르게",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Pārbaudīt paziņojuma skaņu",
"sounds": {
"default": "Noklusējuma (Signāls)",
"default": "Signāls",
"cheerful": "Priecīgs",
"involved": "Iesaistošs",
"swift": "Ātrs",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Туршилтын мэдэгдлийн дуу",
"sounds": {
"default": "Өгөгдмөл (Дохио)",
"default": "Дохио",
"cheerful": "Хөгжилтэй",
"involved": "Оролцсон",
"swift": "Шуурхай зангаа",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Test varslingslyden",
"sounds": {
"default": "Standard (pip)",
"default": "Pip",
"cheerful": "Munter",
"involved": "Engasjert",
"swift": "Rask",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Meldingsgeluid testen",
"sounds": {
"default": "Standaard (Pieptoon)",
"default": "Pieptoon",
"cheerful": "Vrolijk",
"involved": "Uitgebreid",
"swift": "Snel gebaar",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Przetestuj dźwięk powiadomienia",
"sounds": {
"default": "Domyślny (Beep)",
"default": "Beep",
"cheerful": "Pogodny",
"involved": "Dynamiczny",
"swift": "Szybki gest",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Testar som de notificação",
"sounds": {
"default": "Padrão (Bipe)",
"default": "Bipe",
"cheerful": "Alegre",
"involved": "Elaborado",
"swift": "Gesto rápido",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Testați sunetul de notificare",
"sounds": {
"default": "Implicit (Bip)",
"default": "Bip",
"cheerful": "Vesel",
"involved": "Implicat",
"swift": "Gest rapid",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Проверить звук уведомления",
"sounds": {
"default": "По умолчанию (Сигнал)",
"default": "Сигнал",
"cheerful": "Радостный",
"involved": "Вовлечённый",
"swift": "Быстрый жест",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "Otestovať zvuk oznámenia",
"sounds": {
"default": "Predvolený (Pípnutie)",
"default": "Pípnutie",
"cheerful": "Veselý",
"involved": "Dynamický",
"swift": "Rýchly",
+1 -1
View File
@@ -4070,7 +4070,7 @@
"notifications": {
"test_sound": "ทดสอบเสียงแจ้งเตือน",
"sounds": {
"default": "ค่าเริ่มต้น (เสียงบี๊บ)",
"default": "เสียงบี๊บ",
"cheerful": "ร่าเริง (Cheerful)",
"involved": "กระตือรือร้น (Involved)",
"swift": "รวดเร็ว (Swift Gesture)",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Bildirim sesini test et",
"sounds": {
"default": "Varsayılan (Bip)",
"default": "Bip",
"cheerful": "Neşeli",
"involved": "Odaklı",
"swift": "Hızlı Jest",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "Тестовий звук сповіщення",
"sounds": {
"default": "За замовчуванням (звуковий сигнал)",
"default": "Звуковий сигнал",
"cheerful": "Веселий",
"involved": "Залучений",
"swift": "Швидкий жест",
+1 -1
View File
@@ -1633,7 +1633,7 @@
"notifications": {
"test_sound": "測試通知聲音",
"sounds": {
"default": "預設(蜂鳴聲)",
"default": "蜂鳴聲",
"cheerful": "快樂",
"involved": "沉浸",
"swift": "輕快手勢",
+1 -1
View File
@@ -1630,7 +1630,7 @@
"notifications": {
"test_sound": "测试通知声音",
"sounds": {
"default": "默认(蜂鸣声)",
"default": "蜂鸣声",
"cheerful": "快乐",
"involved": "沉浸",
"swift": "轻快手势",
@@ -0,0 +1,50 @@
import { beforeEach, describe, it, expect } from 'vitest';
import { useSettingsStore, migrateSettings } from '../settings-store';
// The new mail sound defaults to Involved; 'default' is the beep, which was
// the default before.
const migrate = (state: Record<string, unknown>, version: number) =>
migrateSettings(state, version) as unknown as Record<string, unknown>;
describe('notification sound default', () => {
beforeEach(() => {
useSettingsStore.getState().resetToDefaults();
});
it('plays Involved on a fresh install', () => {
expect(useSettingsStore.getState().notificationSoundChoice).toBe('involved');
});
it('moves a beep the user never picked to Involved', () => {
expect(migrate({ notificationSoundChoice: 'default', explicitSettings: [] }, 9).notificationSoundChoice).toBe('involved');
});
it('keeps a beep the user picked', () => {
const out = migrate({ notificationSoundChoice: 'default', explicitSettings: ['notificationSoundChoice'] }, 9);
expect(out.notificationSoundChoice).toBe('default');
});
it('keeps any other sound the user picked', () => {
expect(migrate({ notificationSoundChoice: 'relax', explicitSettings: [] }, 9).notificationSoundChoice).toBe('relax');
});
it('does not take the beep from before explicitSettings existed as chosen', () => {
const out = migrate({ notificationSoundChoice: 'default' }, 7);
expect(out.notificationSoundChoice).toBe('involved');
expect(out.explicitSettings).not.toContain('notificationSoundChoice');
});
it('moves the beep along in a server copy or export from an older build', () => {
const store = useSettingsStore.getState();
expect(store.importSettings(JSON.stringify({ notificationSoundChoice: 'default', explicitSettings: [] }))).toBe(true);
expect(useSettingsStore.getState().notificationSoundChoice).toBe('involved');
expect(store.importSettings(JSON.stringify({ notificationSoundChoice: 'default' }))).toBe(true);
expect(useSettingsStore.getState().notificationSoundChoice).toBe('involved');
expect(useSettingsStore.getState().explicitSettings).not.toContain('notificationSoundChoice');
expect(store.importSettings(JSON.stringify({ notificationSoundChoice: 'default', explicitSettings: ['notificationSoundChoice'] }))).toBe(true);
expect(useSettingsStore.getState().notificationSoundChoice).toBe('default');
});
});
+18 -2
View File
@@ -942,7 +942,7 @@ const DEFAULT_SETTINGS = {
// Email Notifications
emailNotificationsEnabled: true,
emailNotificationSound: true,
notificationSoundChoice: 'default' as NotificationSoundChoice,
notificationSoundChoice: 'involved' as NotificationSoundChoice,
pushNotifyInboxOnly: false,
notificationMutes: EMPTY_NOTIFICATION_MUTES,
pushRelayUrl: '',
@@ -1495,6 +1495,7 @@ export const useSettingsStore = create<SettingsState>()(
settings.protocolOpenMode = settings.protocolMailtoOpenMode;
}
migrateAnimationsSetting(settings);
migrateNotificationSoundDefault(settings);
// Apply settings, noting which keys were taken over for the
// onSettingsImport hook.
@@ -1896,7 +1897,7 @@ export const useSettingsStore = create<SettingsState>()(
},
{
name: 'settings-storage',
version: 9,
version: 10,
migrate: migrateSettings,
onRehydrateStorage: () => {
return (state) => {
@@ -1994,6 +1995,9 @@ export function migrateSettings(persisted: unknown, version: number): SettingsSt
if (version < 6 || !isPlainRecord(state.preferredIdentityIds)) {
state.preferredIdentityIds = {};
}
// Before the v8 step: a beep from before explicitSettings existed was
// the default then, and must not be taken as chosen below.
migrateNotificationSoundDefault(state);
// v8: admin policy defaults (#1248) skip settings the user chose.
// Earlier builds did not record that, so a setting that differs from
// the build default is taken as chosen.
@@ -2020,6 +2024,18 @@ export function migrateAnimationsSetting(state: Record<string, unknown>): void {
}
}
/**
* v10: the new mail sound defaults to Involved instead of the beep. A beep
* the user never picked was only the old default and moves to Involved; one
* they picked stays. Imports and server copies from older builds still
* carry the beep and go through this too.
*/
export function migrateNotificationSoundDefault(state: Record<string, unknown>): void {
if (state.notificationSoundChoice !== 'default') return;
const chosen = Array.isArray(state.explicitSettings) && state.explicitSettings.includes('notificationSoundChoice');
if (!chosen) state.notificationSoundChoice = 'involved';
}
// Helper functions to apply settings to DOM
/** The settings applyAccessibilityTo() writes onto <html>. */