diff --git a/apps/desktop/src/main/__tests__/permission-overlay-controller.test.ts b/apps/desktop/src/main/__tests__/permission-overlay-controller.test.ts index 5450db4f3a..aa43c203d1 100644 --- a/apps/desktop/src/main/__tests__/permission-overlay-controller.test.ts +++ b/apps/desktop/src/main/__tests__/permission-overlay-controller.test.ts @@ -21,6 +21,7 @@ import { type PermissionOverlayWindowLike, } from '../permission-overlay/permission-overlay-controller.js'; import { + BUNDLE_ICON_OPTIONS, loadNativeBundleIcon, resolveAppBundle, } from '../permission-overlay/app-bundle.js'; @@ -317,6 +318,12 @@ describe('app bundle resolution for the drag', () => { assert.equal(await loadNativeBundleIcon(true, async () => 'icon'), 'icon'); }); + it('never requests the large icon size that kills packaged macOS builds', () => { + // 'large' hits a fatal NOTREACHED inside Chromium's IconLoader on + // macOS (SIGTRAP, not a catchable error) — see issue #3352. + assert.notEqual(BUNDLE_ICON_OPTIONS.size as string, 'large'); + }); + it('walks three levels up from the executable to the .app', () => { assert.deepEqual( resolveAppBundle({ diff --git a/apps/desktop/src/main/permission-overlay/app-bundle.ts b/apps/desktop/src/main/permission-overlay/app-bundle.ts index 4c148f1511..01767a101d 100644 --- a/apps/desktop/src/main/permission-overlay/app-bundle.ts +++ b/apps/desktop/src/main/permission-overlay/app-bundle.ts @@ -34,6 +34,15 @@ export interface ResolveAppBundleDeps { exists(path: string): boolean; } +/** + * The only size every platform can actually deliver. `'large'` is + * unsupported on macOS: Chromium's IconLoader hits a fatal NOTREACHED and + * the process dies with SIGTRAP before the promise settles — no JavaScript + * error is ever thrown, so the try/catch in `loadNativeBundleIcon` cannot + * save the app. Callers upscale the 32x32 result as needed. + */ +export const BUNDLE_ICON_OPTIONS = { size: 'normal' } as const; + /** * Reading a bundle icon is presentation-only. The original unpackaged npm * Electron runtime could terminate natively while macOS resolved its bundle diff --git a/apps/desktop/src/main/permission-overlay/permission-overlay-main.ts b/apps/desktop/src/main/permission-overlay/permission-overlay-main.ts index c9a992d776..9aa7701974 100644 --- a/apps/desktop/src/main/permission-overlay/permission-overlay-main.ts +++ b/apps/desktop/src/main/permission-overlay/permission-overlay-main.ts @@ -24,7 +24,7 @@ import { join } from 'node:path'; import type { UiLocale } from '@maka/core/ui-locale'; import { resolveOverlayAssetDir } from '../overlay-assets.js'; import { openSystemPermissionPane, requestPermissionAccess } from '../permissions-actions.js'; -import { loadNativeBundleIcon, resolveAppBundle } from './app-bundle.js'; +import { BUNDLE_ICON_OPTIONS, loadNativeBundleIcon, resolveAppBundle } from './app-bundle.js'; import { getPermissionOverlayCopy } from './permission-overlay-copy.js'; import { createPermissionOverlayController, @@ -77,7 +77,7 @@ export function createPermissionOverlayMain( async function resolveAppIconDataUrl(bundlePath: string | null): Promise { if (!bundlePath) return null; const icon = await loadNativeBundleIcon(app.isPackaged, () => - app.getFileIcon(bundlePath, { size: 'large' }), + app.getFileIcon(bundlePath, BUNDLE_ICON_OPTIONS), ); if (!icon || icon.isEmpty()) return null; // nativeImage.createFromPath does not decode .icns reliably. Asking @@ -274,7 +274,7 @@ function attachCardGestures(win: import('electron').BrowserWindow): void { } if (icon.isEmpty()) { const fallback = await loadNativeBundleIcon(app.isPackaged, () => - app.getFileIcon(resolved.bundlePath, { size: 'large' }), + app.getFileIcon(resolved.bundlePath, BUNDLE_ICON_OPTIONS), ); if (fallback && !fallback.isEmpty()) icon = fallback.resize({ width: 64, height: 64 }); // The file drag still works without a decorative drag image.