From 6941490ceaf8feff367e7c2c522a120434605283 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:24:38 +1000 Subject: [PATCH 1/3] Fix update toast visibility off macOS --- electron/updateToastWindowOptions.test.ts | 18 ++++++++++++++++++ electron/updateToastWindowOptions.ts | 8 ++++++++ electron/windows.ts | 6 ++++-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 electron/updateToastWindowOptions.test.ts create mode 100644 electron/updateToastWindowOptions.ts diff --git a/electron/updateToastWindowOptions.test.ts b/electron/updateToastWindowOptions.test.ts new file mode 100644 index 00000000..64934a84 --- /dev/null +++ b/electron/updateToastWindowOptions.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { getUpdateToastWindowAppearance } from "./updateToastWindowOptions"; + +describe("update toast window appearance", () => { + it("uses native transparency on macOS", () => { + expect(getUpdateToastWindowAppearance("darwin")).toEqual({ + transparent: true, + backgroundColor: "#00000000", + }); + }); + + it.each(["win32", "linux"] as const)("uses a visible opaque fallback on %s", (platform) => { + expect(getUpdateToastWindowAppearance(platform)).toEqual({ + transparent: false, + backgroundColor: "#101418", + }); + }); +}); diff --git a/electron/updateToastWindowOptions.ts b/electron/updateToastWindowOptions.ts new file mode 100644 index 00000000..b2253f20 --- /dev/null +++ b/electron/updateToastWindowOptions.ts @@ -0,0 +1,8 @@ +export function getUpdateToastWindowAppearance(platform: NodeJS.Platform) { + const transparent = platform === "darwin"; + + return { + transparent, + backgroundColor: transparent ? "#00000000" : "#101418", + }; +} diff --git a/electron/windows.ts b/electron/windows.ts index 47e90b8f..7579beaf 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -12,6 +12,7 @@ import { } from "./hudOverlayBounds"; import { getHudOverlayTaskbarOptions } from "./hudOverlayWindowOptions"; import { getPackagedRendererBaseUrl } from "./rendererServer"; +import { getUpdateToastWindowAppearance } from "./updateToastWindowOptions"; const electronWindowsDir = path.dirname(fileURLToPath(import.meta.url)); const nodeRequire = createRequire(import.meta.url); @@ -689,6 +690,7 @@ export function setHudOverlayRecordingActive(recording: boolean): void { export function createUpdateToastWindow(): BrowserWindow { const initialBounds = getUpdateToastBounds(); + const appearance = getUpdateToastWindowAppearance(process.platform); const win = new BrowserWindow({ width: initialBounds.width, @@ -696,14 +698,14 @@ export function createUpdateToastWindow(): BrowserWindow { x: initialBounds.x, y: initialBounds.y, frame: false, - transparent: true, + transparent: appearance.transparent, resizable: false, alwaysOnTop: true, skipTaskbar: true, hasShadow: false, show: false, focusable: true, - backgroundColor: "#00000000", + backgroundColor: appearance.backgroundColor, webPreferences: { preload: path.join(electronWindowsDir, "preload.mjs"), nodeIntegration: false, From 3ca4a7540c7344dec32930e4dbb50a4f83edc027 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:09:04 +1000 Subject: [PATCH 2/3] Use native update prompts off macOS --- electron/main.ts | 19 ++++++++++-- electron/updateToastWindowOptions.test.ts | 18 ----------- electron/updateToastWindowOptions.ts | 8 ----- electron/updater.ts | 37 ++++++++++++++++++++--- electron/windows.ts | 6 ++-- 5 files changed, 51 insertions(+), 37 deletions(-) delete mode 100644 electron/updateToastWindowOptions.test.ts delete mode 100644 electron/updateToastWindowOptions.ts diff --git a/electron/main.ts b/electron/main.ts index eb640251..3514c1eb 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -39,6 +39,7 @@ import { getUpdaterLogPath, getUpdateStatusSummary, installDownloadedUpdateNow, + previewNativeUpdateDialog, previewUpdateToast, setExperimentalUpdatesEnabled, setupAutoUpdates, @@ -592,6 +593,10 @@ function syncDockIcon() { } function sendUpdateToastToWindows(channel: "update-toast-state", payload: unknown) { + if (process.platform !== "darwin") { + return false; + } + if (!payload) { const existingWindow = getUpdateToastWindow(); if (existingWindow) { @@ -683,7 +688,12 @@ ipcMain.handle("set-experimental-updates-enabled", async (_event, enabled: unkno } }); -ipcMain.handle("preview-update-toast", () => { +ipcMain.handle("preview-update-toast", async () => { + if (process.platform !== "darwin") { + await previewNativeUpdateDialog(getUpdateDialogWindow); + return { success: true }; + } + return { success: previewUpdateToast(sendUpdateToastToWindows) }; }); @@ -1035,7 +1045,12 @@ app.whenReady().then(async () => { setupAutoUpdates(getUpdateDialogWindow, sendUpdateToastToWindows); if (IS_DEV && process.env.RECORDLY_DEV_PREVIEW_UPDATE === "1") { setTimeout(() => { - previewUpdateToast(sendUpdateToastToWindows); + if (process.platform === "darwin") { + previewUpdateToast(sendUpdateToastToWindows); + return; + } + + void previewNativeUpdateDialog(getUpdateDialogWindow); }, 750); } diff --git a/electron/updateToastWindowOptions.test.ts b/electron/updateToastWindowOptions.test.ts deleted file mode 100644 index 64934a84..00000000 --- a/electron/updateToastWindowOptions.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { getUpdateToastWindowAppearance } from "./updateToastWindowOptions"; - -describe("update toast window appearance", () => { - it("uses native transparency on macOS", () => { - expect(getUpdateToastWindowAppearance("darwin")).toEqual({ - transparent: true, - backgroundColor: "#00000000", - }); - }); - - it.each(["win32", "linux"] as const)("uses a visible opaque fallback on %s", (platform) => { - expect(getUpdateToastWindowAppearance(platform)).toEqual({ - transparent: false, - backgroundColor: "#101418", - }); - }); -}); diff --git a/electron/updateToastWindowOptions.ts b/electron/updateToastWindowOptions.ts deleted file mode 100644 index b2253f20..00000000 --- a/electron/updateToastWindowOptions.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function getUpdateToastWindowAppearance(platform: NodeJS.Platform) { - const transparent = platform === "darwin"; - - return { - transparent, - backgroundColor: transparent ? "#00000000" : "#101418", - }; -} diff --git a/electron/updater.ts b/electron/updater.ts index 8fb24177..206900f5 100644 --- a/electron/updater.ts +++ b/electron/updater.ts @@ -173,6 +173,10 @@ function showMessageBox( getMainWindow: () => BrowserWindow | null, options: MessageBoxOptions, ): Promise { + if (process.platform !== "darwin") { + return dialog.showMessageBox(options); + } + const window = getDialogWindow(getMainWindow); return window ? dialog.showMessageBox(window, options) : dialog.showMessageBox(options); } @@ -644,6 +648,28 @@ async function showDownloadedUpdateDialog( } } +export function previewNativeUpdateDialog(getMainWindow: () => BrowserWindow | null) { + return showDownloadedUpdateDialog(getMainWindow, DEV_UPDATE_PREVIEW_VERSION, { + isPreview: true, + }); +} + +async function showUpdateErrorDialog( + getMainWindow: () => BrowserWindow | null, + version: string, + error: unknown, +) { + await showMessageBox(getMainWindow, { + type: "error", + title: "Update Failed", + message: `Recordly ${version} could not be downloaded.`, + detail: String(error), + buttons: ["OK"], + defaultId: 0, + noLink: true, + }); +} + export async function checkForAppUpdates( getMainWindow: () => BrowserWindow | null, options?: { manual?: boolean }, @@ -745,10 +771,8 @@ export function setupAutoUpdates( return; } - if (manualCheckRequested) { - void showAvailableUpdateDialog(getMainWindow, info.version, sendToRenderer); - manualCheckRequested = false; - } + void showAvailableUpdateDialog(getMainWindow, info.version, sendToRenderer); + manualCheckRequested = false; }); autoUpdater.on("update-not-available", () => { @@ -811,10 +835,13 @@ export function setupAutoUpdates( downloadInProgress = false; downloadToastDismissed = false; installAfterDownloadRequested = false; - emitUpdateToastState( + const shownInRenderer = emitUpdateToastState( sendToRenderer, createUpdateErrorToastPayload(availableVersion, error), ); + if (!shownInRenderer) { + void showUpdateErrorDialog(getMainWindow, availableVersion, error); + } } }); diff --git a/electron/windows.ts b/electron/windows.ts index 7579beaf..47e90b8f 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -12,7 +12,6 @@ import { } from "./hudOverlayBounds"; import { getHudOverlayTaskbarOptions } from "./hudOverlayWindowOptions"; import { getPackagedRendererBaseUrl } from "./rendererServer"; -import { getUpdateToastWindowAppearance } from "./updateToastWindowOptions"; const electronWindowsDir = path.dirname(fileURLToPath(import.meta.url)); const nodeRequire = createRequire(import.meta.url); @@ -690,7 +689,6 @@ export function setHudOverlayRecordingActive(recording: boolean): void { export function createUpdateToastWindow(): BrowserWindow { const initialBounds = getUpdateToastBounds(); - const appearance = getUpdateToastWindowAppearance(process.platform); const win = new BrowserWindow({ width: initialBounds.width, @@ -698,14 +696,14 @@ export function createUpdateToastWindow(): BrowserWindow { x: initialBounds.x, y: initialBounds.y, frame: false, - transparent: appearance.transparent, + transparent: true, resizable: false, alwaysOnTop: true, skipTaskbar: true, hasShadow: false, show: false, focusable: true, - backgroundColor: appearance.backgroundColor, + backgroundColor: "#00000000", webPreferences: { preload: path.join(electronWindowsDir, "preload.mjs"), nodeIntegration: false, From 031067b722fc5363ad7f5729699e996764b48761 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:17:50 +1000 Subject: [PATCH 3/3] Simulate native update channels accurately --- electron/updater.ts | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/electron/updater.ts b/electron/updater.ts index 206900f5..86c2d76d 100644 --- a/electron/updater.ts +++ b/electron/updater.ts @@ -580,12 +580,19 @@ async function showAvailableUpdateDialog( getMainWindow: () => BrowserWindow | null, version: string, sendToRenderer?: UpdateToastSender, + options?: { isPreview?: boolean; isExperimental?: boolean }, ) { + const isPreview = Boolean(options?.isPreview); + const isExperimental = options?.isExperimental ?? getExperimentalUpdatesEnabled(); const result = await showMessageBox(getMainWindow, { type: "info", - title: "Update Available", - message: `Recordly ${version} is available.`, - detail: "Install and restart now, or remind me later.", + title: isExperimental ? "Experimental Update Available" : "Update Available", + message: `Recordly ${version} is available${isExperimental ? " on the experimental channel" : ""}.`, + detail: isPreview + ? `${isExperimental ? EXPERIMENTAL_UPDATE_DESCRIPTION : "This is a development preview of the standard update flow."} No real update will be installed.` + : isExperimental + ? EXPERIMENTAL_UPDATE_DESCRIPTION + : "Install and restart now, or remind me later.", buttons: ["Install & Restart", "Later"], defaultId: 0, cancelId: 1, @@ -593,10 +600,24 @@ async function showAvailableUpdateDialog( }); if (result.response === 0) { + if (isPreview) { + await showMessageBox(getMainWindow, { + type: "info", + title: "Preview Only", + message: "No real update was installed.", + detail: "This was only a manual development preview of the update prompt.", + }); + return; + } + await downloadAvailableUpdate(sendToRenderer, { installAfterDownload: true }); return; } + if (isPreview) { + return; + } + deferUpdateReminder(getMainWindow, sendToRenderer, UPDATE_REMINDER_DELAY_MS); } @@ -649,8 +670,9 @@ async function showDownloadedUpdateDialog( } export function previewNativeUpdateDialog(getMainWindow: () => BrowserWindow | null) { - return showDownloadedUpdateDialog(getMainWindow, DEV_UPDATE_PREVIEW_VERSION, { + return showAvailableUpdateDialog(getMainWindow, DEV_UPDATE_PREVIEW_VERSION, undefined, { isPreview: true, + isExperimental: DEV_UPDATE_PREVIEW_IS_EXPERIMENTAL, }); }