From 932c12ad1dbd06cf6d3e8a66d965e26ad0f28614 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:33:49 +1000 Subject: [PATCH] chore(desktop): upgrade Electron to 44.4.3 (#50327) --- bun.lock | 8 ++--- .../e2e/performance/devex/desktop-startup.ts | 18 ++++-------- .../performance/unit/desktop-startup.test.ts | 24 +++++++-------- packages/desktop/package.json | 4 +-- packages/desktop/src/main/files/index.ts | 29 ++++++++++++++----- .../desktop/src/main/ipc-handlers/files.ts | 8 ++--- packages/desktop/src/main/windows/recovery.ts | 17 +++++++++-- 7 files changed, 59 insertions(+), 49 deletions(-) diff --git a/bun.lock b/bun.lock index a4b8d858a49..de8059a4115 100644 --- a/bun.lock +++ b/bun.lock @@ -409,7 +409,7 @@ "version": "2.0.12", "dependencies": { "@zip.js/zip.js": "2.7.62", - "electron-context-menu": "4.1.2", + "electron-context-menu": "5.0.0", "electron-log": "^5", "electron-updater": "6.8.9", "lighthouse": "13.4.1", @@ -436,7 +436,7 @@ "drizzle-kit": "catalog:", "drizzle-orm": "catalog:", "effect": "catalog:", - "electron": "42.10.1", + "electron": "44.4.3", "electron-builder": "26.15.7", "electron-vite": "6.0.0-beta.1", "puppeteer-core": "25.9.0", @@ -3880,13 +3880,13 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron": ["electron@42.10.1", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-ITc1HPeoDzsxCCaH6MFsN67Nq2nUiJf5N9pBWxzhUpFfKJF0IwiAkKT3emg1lPbvC4OfFE+Cdwe4vhgdOZ1YKg=="], + "electron": ["electron@44.4.3", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-LTpSFTB40qVCXIX5xMo+cgHI/Jjkbjw7VpB26PccEbroqOn72LBukeaDwPVo1fBYzSzs0c9iPuAucFCO7Tw81Q=="], "electron-builder": ["electron-builder@26.15.7", "", { "dependencies": { "app-builder-lib": "26.15.7", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.15.7", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-DBpaNzxsPs1BvEblzFoNriSbzsBqDCy/gseIngeEhYzQG1IxfB7Hvc2tBBVmpWE2BTQGP9J1RrAvDT+Vc/uAxg=="], "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.15.7", "", { "dependencies": { "app-builder-lib": "26.15.7", "builder-util": "26.15.3", "electron-winstaller": "5.4.0" } }, "sha512-B4uvn2NzFSuf084udWqugludFull6CRJiWe2dLzMnZLl6G5hdAGk0fsBMGlBSpKjvQCJn8IPc+S7OnJ+GXqwLA=="], - "electron-context-menu": ["electron-context-menu@4.1.2", "", { "dependencies": { "cli-truncate": "^4.0.0", "electron-dl": "^4.0.0", "electron-is-dev": "^3.0.1" } }, "sha512-9xYTUV0oRqKL50N9W71IrXNdVRB0LuBp3R1zkUdUc2wfIa2/QZwYYj5RLuO7Tn7ZSLVIaO3X6u+EIBK+cBvzrQ=="], + "electron-context-menu": ["electron-context-menu@5.0.0", "", { "dependencies": { "cli-truncate": "^4.0.0", "electron-dl": "^4.0.0", "electron-is-dev": "^3.0.1" } }, "sha512-rgFpRtwY0/rhsRCoz9rE6VM4WueEsLbIpca7ucOhERVrbGB2dQrxa9xwBNLplU54jRgPuv6nTEbxoplE5bzy2A=="], "electron-dl": ["electron-dl@4.0.0", "", { "dependencies": { "ext-name": "^5.0.0", "pupa": "^3.1.0", "unused-filename": "^4.0.1" } }, "sha512-USiB9816d2JzKv0LiSbreRfTg5lDk3lWh0vlx/gugCO92ZIJkHVH0UM18EHvKeadErP6Xn4yiTphWzYfbA2Ong=="], diff --git a/packages/app/e2e/performance/devex/desktop-startup.ts b/packages/app/e2e/performance/devex/desktop-startup.ts index 69d2d81425b..a8f832e6de9 100644 --- a/packages/app/e2e/performance/devex/desktop-startup.ts +++ b/packages/app/e2e/performance/devex/desktop-startup.ts @@ -17,14 +17,12 @@ const milestones = [ "electronSpawnStarted", "debugEndpointReady", "electronStarted", + "layersReady", + "windowVisible", "serviceEnsureStarted", "serviceSpawnRequested", "serviceReady", - "backgroundLoadingReady", "rendererViteConnected", - "rendererInitializationStarted", - "rendererInitializationReady", - "windowVisible", "homeReady", ] as const const phases = [ @@ -128,14 +126,12 @@ const milestonePatterns: ReadonlyArray<{ name: Milestone; text: string }> = [ { name: "electronSpawnStarted", text: "starting electron app..." }, { name: "debugEndpointReady", text: "DevTools listening on ws://" }, { name: "electronStarted", text: "app starting" }, + { name: "layersReady", text: "layers ready" }, + { name: "windowVisible", text: "main window visible" }, { name: "serviceEnsureStarted", text: "starting v2 background service" }, { name: "serviceSpawnRequested", text: "v2 CLI background service starting" }, { name: "serviceReady", text: "v2 CLI background service ready" }, - { name: "backgroundLoadingReady", text: "loading task finished" }, { name: "rendererViteConnected", text: "[vite] connected." }, - { name: "rendererInitializationStarted", text: "awaiting server ready" }, - { name: "rendererInitializationReady", text: "server ready" }, - { name: "windowVisible", text: "main window visible" }, ] async function createColdProfile() { @@ -404,14 +400,12 @@ function requireMilestones(observed: Partial>) { electronSpawnStarted: get("electronSpawnStarted"), debugEndpointReady: get("debugEndpointReady"), electronStarted: get("electronStarted"), + layersReady: get("layersReady"), + windowVisible: get("windowVisible"), serviceEnsureStarted: get("serviceEnsureStarted"), serviceSpawnRequested: get("serviceSpawnRequested"), serviceReady: get("serviceReady"), - backgroundLoadingReady: get("backgroundLoadingReady"), rendererViteConnected: get("rendererViteConnected"), - rendererInitializationStarted: get("rendererInitializationStarted"), - rendererInitializationReady: get("rendererInitializationReady"), - windowVisible: get("windowVisible"), homeReady: get("homeReady"), } } diff --git a/packages/app/e2e/performance/unit/desktop-startup.test.ts b/packages/app/e2e/performance/unit/desktop-startup.test.ts index 8bf4576c2e8..bf7c59355ff 100644 --- a/packages/app/e2e/performance/unit/desktop-startup.test.ts +++ b/packages/app/e2e/performance/unit/desktop-startup.test.ts @@ -20,14 +20,12 @@ describe("desktop startup benchmark", () => { ["electronSpawnStarted", "starting electron app..."], ["debugEndpointReady", "DevTools listening on ws://"], ["electronStarted", "app starting"], + ["layersReady", "layers ready"], + ["windowVisible", "main window visible"], ["serviceEnsureStarted", "starting v2 background service"], ["serviceSpawnRequested", "v2 CLI background service starting"], ["serviceReady", "v2 CLI background service ready"], - ["backgroundLoadingReady", "loading task finished"], ["rendererViteConnected", "[vite] connected."], - ["rendererInitializationStarted", "awaiting server ready"], - ["rendererInitializationReady", "server ready"], - ["windowVisible", "main window visible"], ] as const cases.forEach(([milestone, line]) => { expect(milestoneForLine(`\u001b[32m${line}\u001b[39m`)).toBe(milestone) @@ -58,14 +56,12 @@ function sample(run: number, commandToHomeReadyMs: number): DesktopStartupSample electronSpawnStarted: 7, debugEndpointReady: 8, electronStarted: 9, - serviceEnsureStarted: 10, - serviceSpawnRequested: 11, - serviceReady: 12, - backgroundLoadingReady: 13, - rendererViteConnected: 14, - rendererInitializationStarted: 15, - rendererInitializationReady: 16, - windowVisible: 17, + layersReady: 10, + windowVisible: 11, + serviceEnsureStarted: 12, + serviceSpawnRequested: 13, + serviceReady: 14, + rendererViteConnected: 15, homeReady: commandToHomeReadyMs, } return { @@ -80,8 +76,8 @@ function sample(run: number, commandToHomeReadyMs: number): DesktopStartupSample electronStartup: 2, serviceSpawnWait: 1, serviceProcessStartup: 1, - rendererStartup: commandToHomeReadyMs - 14, - visibleWindowToHome: commandToHomeReadyMs - 17, + rendererStartup: commandToHomeReadyMs - 15, + visibleWindowToHome: commandToHomeReadyMs - 11, }, service: { version: "2.0.0-local-test", url: "http://127.0.0.1:3000", pid: run }, } diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 3d24b6e9782..4aa95c21602 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -26,7 +26,7 @@ "main": "./out/main/index.js", "dependencies": { "@zip.js/zip.js": "2.7.62", - "electron-context-menu": "4.1.2", + "electron-context-menu": "5.0.0", "electron-log": "^5", "electron-updater": "6.8.9", "lighthouse": "13.4.1" @@ -53,7 +53,7 @@ "drizzle-kit": "catalog:", "drizzle-orm": "catalog:", "effect": "catalog:", - "electron": "42.10.1", + "electron": "44.4.3", "electron-builder": "26.15.7", "electron-vite": "6.0.0-beta.1", "puppeteer-core": "25.9.0", diff --git a/packages/desktop/src/main/files/index.ts b/packages/desktop/src/main/files/index.ts index 5a03a1481e5..93480ccffaa 100644 --- a/packages/desktop/src/main/files/index.ts +++ b/packages/desktop/src/main/files/index.ts @@ -1,7 +1,7 @@ export * as DesktopFiles from "./index" import { execFile } from "node:child_process" -import { clipboard, dialog, shell } from "electron" +import { clipboard, dialog, nativeImage, shell } from "electron" import { Context, Effect, FileSystem, Layer, Path } from "effect" import type { DirectoryPickerOptions, FilePickerOptions, SaveFilePickerOptions } from "../../shared/ipc-contract" import { scoped } from "../native/logging" @@ -92,18 +92,31 @@ function make(fs: FileSystem.FileSystem, path: Path.Path) { shell.showItemInFolder(target) return true }), - readClipboardImage() { - const image = clipboard.readImage() + readClipboardImage: Effect.fn("DesktopFiles.readClipboardImage")(function* () { + const items = yield* Effect.promise(() => clipboard.read()) + const found = clipboardImageTypes + .flatMap((mime) => items.filter((item) => item.types.includes(mime)).map((item) => ({ item, mime }))) + .at(0) + if (!found) return null + // getType() is typed as Blob | ClipboardBookmark; only the bookmark format yields the latter. + const payload = yield* Effect.promise(async () => found.item.getType(found.mime)) + if (!(payload instanceof Blob)) return null + const bytes = yield* Effect.promise(() => payload.arrayBuffer()) + // Re-encode through nativeImage so the renderer always receives PNG and the image dimensions. + const image = nativeImage.createFromBuffer(Buffer.from(bytes)) if (image.isEmpty()) return null const size = image.getSize() - return { buffer: new Uint8Array(image.toPNG()).buffer, width: size.width, height: size.height } - }, - writeClipboardText(text: string) { - clipboard.writeText(text) - }, + return { buffer: new Uint8Array(image.toPNG()), width: size.width, height: size.height } + }), + writeClipboardText: Effect.fn("DesktopFiles.writeClipboardText")(function* (text: string) { + yield* Effect.promise(() => clipboard.writeText(text)) + }), } } +// Chromium exposes copied bitmaps as image/png; JPEG only appears when an app placed one explicitly. +const clipboardImageTypes = ["image/png", "image/jpeg"] + export const openExternalURL = Effect.fn("DesktopFiles.openExternalURL")(function* (value: string) { const url = resolveExternalURL(value) if (!url) { diff --git a/packages/desktop/src/main/ipc-handlers/files.ts b/packages/desktop/src/main/ipc-handlers/files.ts index d03b7f6010a..933f842b58d 100644 --- a/packages/desktop/src/main/ipc-handlers/files.ts +++ b/packages/desktop/src/main/ipc-handlers/files.ts @@ -32,12 +32,8 @@ export const fileHandlers = FileRpcs.toLayer( Effect.orDie, ), FilesRevealPath: ({ path }) => files.revealPath(path), - FilesReadClipboardImage: () => - Effect.sync(() => { - const image = files.readClipboardImage() - return image ? { ...image, buffer: new Uint8Array(image.buffer) } : null - }), - FilesWriteClipboardText: ({ text }) => Effect.sync(() => files.writeClipboardText(text)), + FilesReadClipboardImage: () => files.readClipboardImage(), + FilesWriteClipboardText: ({ text }) => files.writeClipboardText(text), }) }), ) diff --git a/packages/desktop/src/main/windows/recovery.ts b/packages/desktop/src/main/windows/recovery.ts index 2e2a00a1c69..1d19791214a 100644 --- a/packages/desktop/src/main/windows/recovery.ts +++ b/packages/desktop/src/main/windows/recovery.ts @@ -142,9 +142,20 @@ export const makeWindowRecovery = Effect.gen(function* () { ) sampler.stopAndFlush() }) - win.webContents.on("console-message", (_event, level, message, line, sourceId) => { - if (message.toLowerCase().includes("terminal") || sourceId.toLowerCase().includes("terminal")) { - runFork(scoped("pty", Effect.logInfo("console", { window: name, level, message, line, sourceId }))) + win.webContents.on("console-message", (event) => { + if (event.message.toLowerCase().includes("terminal") || event.sourceId.toLowerCase().includes("terminal")) { + runFork( + scoped( + "pty", + Effect.logInfo("console", { + window: name, + level: event.level, + message: event.message, + line: event.lineNumber, + sourceId: event.sourceId, + }), + ), + ) } }) win.webContents.on("preload-error", (_event, path, error) => {