fix(desktop): restore notification sounds (#2582)

Restore desktop notification sounds by default, add a persisted Play sound setting, and group notification permission and testing in a dedicated Notifications section.
This commit is contained in:
Glen Stampoultzis
2026-08-08 22:28:37 +02:00
committed by GitHub
parent b19db9537c
commit b9b3a24d58
24 changed files with 451 additions and 123 deletions
@@ -1,6 +1,7 @@
import path from "node:path";
import { existsSync } from "node:fs";
import { app, BrowserWindow, Notification, ipcMain, nativeImage } from "electron";
import { getDesktopSettingsStore } from "../settings/desktop-settings-electron.js";
interface NotificationInput {
title?: unknown;
@@ -95,11 +96,12 @@ export function registerNotificationHandlers(): void {
const body = toTrimmedString(rawInput?.body) ?? undefined;
const data = toRecord(rawInput?.data);
const icon = getNotificationIcon();
const settings = await getDesktopSettingsStore().get();
const notification = new Notification({
title,
...(body ? { body } : {}),
...(icon ? { icon } : {}),
silent: true,
silent: !settings.notifications.playSound,
});
activeNotifications.add(notification);
@@ -81,6 +81,7 @@ describe("desktop-settings", () => {
expect(settings).toEqual({
releaseChannel: "stable",
notifications: { playSound: true },
daemon: {
manageBuiltInDaemon: true,
keepRunningAfterQuit: false,
@@ -102,6 +103,7 @@ describe("desktop-settings", () => {
expect(next).toEqual({
releaseChannel: "beta",
notifications: { playSound: true },
daemon: {
manageBuiltInDaemon: true,
keepRunningAfterQuit: false,
@@ -110,6 +112,45 @@ describe("desktop-settings", () => {
expect(files).toEqual(["desktop-settings.json"]);
});
it("defaults notification sounds on for existing settings documents", async () => {
const userDataPath = await createTempUserDataDir();
directories.add(userDataPath);
await writeFile(
settingsFilePath(userDataPath),
JSON.stringify({
version: 1,
settings: {
releaseChannel: "stable",
daemon: {
manageBuiltInDaemon: true,
keepRunningAfterQuit: false,
},
},
migrations: {
legacyRendererSettingsImported: true,
daemonStopOnQuitDefaultApplied: true,
},
}),
);
const settings = await createDesktopSettingsStore({ userDataPath }).get();
expect(settings.notifications.playSound).toBe(true);
});
it("keeps an explicit notification sound choice across restarts", async () => {
const userDataPath = await createTempUserDataDir();
directories.add(userDataPath);
await createDesktopSettingsStore({ userDataPath }).patch({
notifications: { playSound: false },
});
const settings = await createDesktopSettingsStore({ userDataPath }).get();
expect(settings.notifications.playSound).toBe(false);
});
it("does not let stale legacy renderer settings override an explicit desktop patch", async () => {
const userDataPath = await createTempUserDataDir();
directories.add(userDataPath);
@@ -229,6 +270,7 @@ describe("desktop-settings", () => {
expect(migrated).toEqual({
releaseChannel: "beta",
notifications: { playSound: true },
daemon: {
manageBuiltInDaemon: false,
keepRunningAfterQuit: false,
@@ -6,6 +6,9 @@ import type { AppReleaseChannel } from "../features/auto-updater.js";
export interface DesktopSettings {
releaseChannel: AppReleaseChannel;
notifications: {
playSound: boolean;
};
daemon: {
manageBuiltInDaemon: boolean;
keepRunningAfterQuit: boolean;
@@ -14,6 +17,7 @@ export interface DesktopSettings {
interface DesktopSettingsPatch {
releaseChannel?: AppReleaseChannel;
notifications?: Partial<DesktopSettings["notifications"]>;
daemon?: Partial<DesktopSettings["daemon"]>;
}
@@ -38,6 +42,9 @@ export interface DesktopSettingsStore {
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
releaseChannel: "stable",
notifications: {
playSound: true,
},
daemon: {
manageBuiltInDaemon: true,
keepRunningAfterQuit: false,
@@ -73,6 +80,7 @@ function buildDefaultDocument(): PersistedDesktopSettingsDocument {
version: 1,
settings: {
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
notifications: { ...DEFAULT_DESKTOP_SETTINGS.notifications },
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
},
migrations: {
@@ -85,6 +93,7 @@ function buildDefaultDocument(): PersistedDesktopSettingsDocument {
function coerceDesktopSettings(input: unknown): DesktopSettings {
const result: DesktopSettings = {
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
notifications: { ...DEFAULT_DESKTOP_SETTINGS.notifications },
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
};
@@ -97,6 +106,13 @@ function coerceDesktopSettings(input: unknown): DesktopSettings {
result.releaseChannel = releaseChannel;
}
if (isRecord(input.notifications)) {
const playSound = coerceBoolean(input.notifications.playSound);
if (playSound !== null) {
result.notifications.playSound = playSound;
}
}
if (isRecord(input.daemon)) {
const manageBuiltInDaemon = coerceBoolean(input.daemon.manageBuiltInDaemon);
if (manageBuiltInDaemon !== null) {
@@ -124,6 +140,13 @@ function coerceDesktopSettingsPatch(input: unknown): DesktopSettingsPatch {
patch.releaseChannel = releaseChannel;
}
if (isRecord(input.notifications)) {
const playSound = coerceBoolean(input.notifications.playSound);
if (playSound !== null) {
patch.notifications = { playSound };
}
}
if (isRecord(input.daemon)) {
const daemonPatch: Partial<DesktopSettings["daemon"]> = {};
const manageBuiltInDaemon = coerceBoolean(input.daemon.manageBuiltInDaemon);
@@ -169,6 +192,7 @@ function mergeDesktopSettings(
): DesktopSettings {
return {
releaseChannel: patch.releaseChannel ?? current.releaseChannel,
notifications: { ...current.notifications, ...patch.notifications },
daemon: { ...current.daemon, ...patch.daemon },
};
}