fix: keep notifications alive so clicks still work on macOS

GC was collecting the JS Notification object after ~1 min, silently
killing click handlers. Hold a ref in a Set, release on click/close/
failed. Also order app.focus({steal}) before restore/show so the app
comes forward when minimized to Dock or Cmd+H'd.
This commit is contained in:
Bohdan Triapitsyn
2026-04-20 23:46:24 +03:00
parent 08258908c2
commit 630d9e3a82
+22 -6
View File
@@ -581,12 +581,23 @@ const focusForegroundWindow = () => {
const target = state.mainWindow && !state.mainWindow.isDestroyed()
? state.mainWindow
: windows.find((window) => window.isVisible()) || windows[0];
if (target.isMinimized()) target.restore();
if (!target.isVisible()) target.show();
target.focus();
// macOS: bring the app to foreground FIRST. When the window is minimized
// to the Dock or hidden via Cmd+H, the app is in the background, and
// subsequent window.show/restore/focus calls won't pull it forward
// unless app.focus runs first.
if (process.platform === 'darwin') app.focus({ steal: true });
if (target.isMinimized()) target.restore();
target.show();
target.focus();
if (typeof target.moveTop === 'function') target.moveTop();
};
// Keep references to live notifications so they aren't garbage-collected
// before the OS fires click/close. On macOS, losing the JS reference causes
// click events to silently stop firing after ~1 min.
// See https://blog.bloomca.me/2025/02/22/electron-mac-notifications
const activeNotifications = new Set();
const maybeShowNativeNotification = (rawInput) => {
const payload = normalizeNotificationInput(rawInput);
const requireHidden = Boolean(payload.requireHidden ?? payload.require_hidden);
@@ -614,12 +625,18 @@ const maybeShowNativeNotification = (rawInput) => {
...(process.platform === 'darwin' ? { sound: 'Glass' } : {}),
});
activeNotifications.add(notification);
const release = () => { activeNotifications.delete(notification); };
notification.on('click', () => {
focusForegroundWindow();
if (sessionId) {
emitToAllWindows('openchamber:open-session', { sessionId });
}
release();
});
notification.on('close', release);
notification.on('failed', release);
notification.show();
};
@@ -2179,9 +2196,8 @@ app.on('activate', async () => {
if (windows.length > 0) {
const visibleWindow = windows.find((window) => window.isVisible());
const targetWindow = visibleWindow || state.mainWindow || windows[0];
if (!targetWindow.isVisible()) {
targetWindow.show();
}
if (targetWindow.isMinimized()) targetWindow.restore();
targetWindow.show();
targetWindow.focus();
return;
}