diff --git a/package.json b/package.json index f14246498..315ed6e5e 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, "dependencies": { "electron-log": "5.4.4", - "electron-menubar": "10.1.3", + "electron-menubar": "10.1.4", "electron-updater": "6.8.9", "react": "19.2.8", "react-dom": "19.2.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9c64bc15..9b91ae5a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: 5.4.4 version: 5.4.4 electron-menubar: - specifier: 10.1.3 - version: 10.1.3(electron@43.2.0(supports-color@10.2.2)) + specifier: 10.1.4 + version: 10.1.4(electron@43.2.0(supports-color@10.2.2)) electron-updater: specifier: 6.8.9 version: 6.8.9(supports-color@10.2.2) @@ -2559,8 +2559,8 @@ packages: resolution: {integrity: sha512-istWgaXjBfURBSS8LWVW9C3jsc6+ac+tY1lXrQEOTp0lVj+a4OlO1Tmqb36GgnEUDv92DGC9VI1HNXwJinWpgA==} engines: {node: '>= 14'} - electron-menubar@10.1.3: - resolution: {integrity: sha512-5XRQCmYCiObJDvjROSGP3+MX/7AEA3qp5mVUtpo/CfOGCkhNA6q1dvhKnPC8B2+aKCJDjBQBMhgVW8pMMbedCQ==} + electron-menubar@10.1.4: + resolution: {integrity: sha512-Ly3jNCr1IKEric9iP9w2G5oCZ7lxJCCrH6EXVKrY2JkzQUcAEUduMgJYciE1sewwhcmlzZHGq/1mOlfdRlHr6w==} peerDependencies: electron: 43.2.0 @@ -6849,7 +6849,7 @@ snapshots: electron-log@5.4.4: {} - electron-menubar@10.1.3(electron@43.2.0(supports-color@10.2.2)): + electron-menubar@10.1.4(electron@43.2.0(supports-color@10.2.2)): dependencies: electron: 43.2.0(supports-color@10.2.2) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3710df06d..6f0d979be 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,9 +10,9 @@ allowBuilds: catalog: electron: '43.2.0' # Adopt this freshly published, first-party release ahead of pnpm's default -# minimumReleaseAge gate; it carries the Windows tray click fix (#3064). +# minimumReleaseAge gate; it carries the macOS dock icon fix (#3069). minimumReleaseAgeExclude: - - electron-menubar@10.1.3 + - electron-menubar@10.1.4 overrides: react-is: '19.2.8' # Force patched versions of transitive dev-tooling deps pulled in by diff --git a/src/main/lifecycle/first-run.test.ts b/src/main/lifecycle/first-run.test.ts index c95d09c79..cef342395 100644 --- a/src/main/lifecycle/first-run.test.ts +++ b/src/main/lifecycle/first-run.test.ts @@ -19,6 +19,7 @@ vi.mock('node:fs', () => ({ const moveToApplicationsFolderMock = vi.fn(); const isInApplicationsFolderMock = vi.fn(() => false); const getPathMock = vi.fn(() => '/User/Data'); +const dockHideMock = vi.fn(); const showMessageBoxMock = vi.fn(async () => ({ response: 0, @@ -30,7 +31,15 @@ vi.mock('electron', () => ({ getPath: () => getPathMock(), isInApplicationsFolder: () => isInApplicationsFolderMock(), moveToApplicationsFolder: () => moveToApplicationsFolderMock(), - } satisfies Pick, + dock: { + hide: () => dockHideMock(), + }, + } satisfies Pick< + Electron.App, + 'getPath' | 'isInApplicationsFolder' | 'moveToApplicationsFolder' + > & { + dock: { hide: () => void }; + }, dialog: { showMessageBox: () => showMessageBoxMock(), } satisfies Pick, @@ -125,6 +134,19 @@ describe('main/lifecycle/first-run', () => { expect(moveToApplicationsFolderMock).not.toHaveBeenCalled(); }); + it('re-hides the dock icon after the prompt closes (#3069)', async () => { + existsSyncMock.mockReturnValueOnce(false); + existsSyncMock.mockReturnValueOnce(false); + showMessageBoxMock.mockResolvedValueOnce({ + response: 1, + checkboxChecked: false, + }); + + await onFirstRunMaybe(); + + expect(dockHideMock).toHaveBeenCalled(); + }); + it('does not prompt on non-macOS', async () => { mac = false; existsSyncMock.mockReturnValueOnce(false); diff --git a/src/main/lifecycle/first-run.ts b/src/main/lifecycle/first-run.ts index 585a86f0f..570fd32a0 100644 --- a/src/main/lifecycle/first-run.ts +++ b/src/main/lifecycle/first-run.ts @@ -38,6 +38,10 @@ async function promptMoveToApplicationsFolder() { message: 'Move to Applications Folder?', }); + // Parentless dialogs pull a dock-hidden (accessory) app into the Dock and + // macOS does not restore the accessory policy on close; re-hide (#3069). + app.dock?.hide(); + if (response === 0) { app.moveToApplicationsFolder(); } diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts index 18e496dac..a583d8c47 100644 --- a/src/main/updater.test.ts +++ b/src/main/updater.test.ts @@ -1,4 +1,4 @@ -import { dialog } from 'electron'; +import { app, dialog } from 'electron'; import type { Menubar } from 'electron-menubar'; import { APPLICATION } from '../shared/constants'; @@ -45,6 +45,11 @@ vi.mock('electron', () => { } } return { + app: { + dock: { + hide: vi.fn(), + }, + }, dialog: { showMessageBox: vi.fn(), } satisfies Pick, @@ -139,6 +144,23 @@ describe('main/updater.ts', () => { expect(autoUpdater.quitAndInstall).toHaveBeenCalled(); }); + + it('re-hides the dock icon when user dismisses the dialog (#3069)', async () => { + vi.mocked(dialog.showMessageBox).mockResolvedValue({ + response: 1, // "Later" button index + checkboxChecked: false, + }); + + await updater.start(); + + emit('update-downloaded', { releaseName: 'v9.9.9' }); + + // Allow then() of showMessageBox promise to resolve + await Promise.resolve(); + + expect(autoUpdater.quitAndInstall).not.toHaveBeenCalled(); + expect(app.dock?.hide).toHaveBeenCalled(); + }); }); describe('update event handlers & scheduling', () => { diff --git a/src/main/updater.ts b/src/main/updater.ts index 214014d84..a0c27a180 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -1,4 +1,4 @@ -import { dialog, type MessageBoxOptions } from 'electron'; +import { app, dialog, type MessageBoxOptions } from 'electron'; import type { Menubar } from 'electron-menubar'; import { autoUpdater } from 'electron-updater'; @@ -200,7 +200,14 @@ export default class AppUpdater { dialog.showMessageBox(dialogOpts).then((returnValue) => { if (returnValue.response === 0) { autoUpdater.quitAndInstall(); + return; } + + // Presenting a parentless dialog forces a dock-hidden (accessory) macOS + // app to activate into the Dock, and the accessory policy is not + // restored when the dialog closes. Re-assert it so the dock icon + // disappears again (#3069). + app.dock?.hide(); }); } }