From 8bbbab50514910040d2a4fa2be5021c83134afcf Mon Sep 17 00:00:00 2001 From: Rakshith Bhat <88523594+RakshithBhat03@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:00:33 +0530 Subject: [PATCH 1/4] fix(web): align sidebar statuses with project names (#7491) Co-authored-by: GPT-5.6 --- apps/web/src/components/Sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 31a73d133075..060cdd47b59a 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -1422,7 +1422,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { isWokeStatus ? "pointer-events-auto" : "pointer-events-none group-has-[:focus-visible]/sidebar-status-slot:absolute group-has-[:focus-visible]/sidebar-status-slot:right-0 group-has-[:focus-visible]/sidebar-status-slot:opacity-0 group-hover/sidebar-row:absolute group-hover/sidebar-row:right-0 group-hover/sidebar-row:opacity-0", - "self-center justify-self-end tabular-nums text-secondary-label transition-opacity", + "flex items-center self-center justify-self-end tabular-nums text-secondary-label transition-opacity", snoozeMenuOpen && "pointer-events-none absolute right-0 opacity-0", )} > From 24c4ba68f536d56e8482a1e4d7070a6771da551d Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 19 Aug 2026 04:42:30 -0400 Subject: [PATCH 2/4] fix(desktop): close the window before quit cleanup (#6562) --- apps/desktop/src/app/DesktopLifecycle.test.ts | 255 +++++++++++++----- apps/desktop/src/app/DesktopLifecycle.ts | 49 +++- .../src/electron/ElectronWindow.test.ts | 9 +- apps/desktop/src/electron/ElectronWindow.ts | 33 ++- 4 files changed, 253 insertions(+), 93 deletions(-) diff --git a/apps/desktop/src/app/DesktopLifecycle.test.ts b/apps/desktop/src/app/DesktopLifecycle.test.ts index eb045be282b6..f5ff3d5f6af6 100644 --- a/apps/desktop/src/app/DesktopLifecycle.test.ts +++ b/apps/desktop/src/app/DesktopLifecycle.test.ts @@ -1,4 +1,5 @@ import { assert, describe, it } from "@effect/vitest"; +import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Ref from "effect/Ref"; @@ -7,91 +8,110 @@ import type * as Electron from "electron"; import * as ElectronApp from "../electron/ElectronApp.ts"; import * as ElectronTheme from "../electron/ElectronTheme.ts"; +import * as ElectronWindow from "../electron/ElectronWindow.ts"; import * as DesktopEnvironment from "./DesktopEnvironment.ts"; import * as DesktopLifecycle from "./DesktopLifecycle.ts"; import * as DesktopShutdown from "./DesktopShutdown.ts"; import * as DesktopState from "./DesktopState.ts"; import * as DesktopWindow from "../window/DesktopWindow.ts"; +function makeElectronAppLayer( + appListeners: Map void>, + quit: Effect.Effect = Effect.void, +) { + const registerListener = (eventName: string, listener: (...args: readonly unknown[]) => void) => + Effect.acquireRelease( + Effect.sync(() => { + appListeners.set(eventName, listener); + }), + () => + Effect.sync(() => { + appListeners.delete(eventName); + }), + ).pipe(Effect.asVoid); + + return Layer.succeed(ElectronApp.ElectronApp, { + metadata: Effect.die("unexpected metadata read"), + name: Effect.succeed("T3 Code"), + systemLocale: Effect.succeed("en-US"), + whenReady: Effect.void, + quit, + exit: () => Effect.void, + relaunch: () => Effect.void, + setPath: () => Effect.void, + setName: () => Effect.void, + setAboutPanelOptions: () => Effect.void, + setAppUserModelId: () => Effect.void, + getAppMetrics: Effect.succeed([]), + isDefaultProtocolClient: () => Effect.succeed(false), + setAsDefaultProtocolClient: () => Effect.succeed(true), + setDesktopName: () => Effect.void, + setDockIcon: () => Effect.void, + appendCommandLineSwitch: () => Effect.void, + removeCommandLineSwitch: () => Effect.void, + onBeforeQuitForUpdate: (listener) => registerListener("before-quit-for-update", listener), + on: (eventName, listener) => + registerListener(eventName, listener as unknown as (...args: readonly unknown[]) => void), + } satisfies ElectronApp.ElectronApp["Service"]); +} + +const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { + shouldUseDarkColors: Effect.succeed(false), + setSource: () => Effect.void, + onUpdated: () => Effect.void, +}); + +function makeElectronWindowLayer(destroyAll: Effect.Effect = Effect.void) { + return Layer.succeed(ElectronWindow.ElectronWindow, { + create: () => Effect.die("unexpected window creation"), + main: Effect.die("unexpected main window read"), + currentMainOrFirst: Effect.die("unexpected current window read"), + focusedMainOrFirst: Effect.die("unexpected focused window read"), + setMain: () => Effect.void, + clearMain: () => Effect.void, + reveal: () => Effect.void, + sendAll: () => Effect.void, + destroyAll, + syncAllAppearance: () => Effect.void, + }); +} + +function makeDesktopWindowLayer( + input: { + readonly activate?: Effect.Effect; + readonly flushMainWindowBounds?: Effect.Effect; + } = {}, +) { + return Layer.succeed(DesktopWindow.DesktopWindow, { + createMain: Effect.die("unexpected window creation"), + ensureMain: Effect.die("unexpected window creation"), + revealOrCreateMain: Effect.die("unexpected window creation"), + activate: input.activate ?? Effect.void, + createMainIfBackendReady: Effect.void, + showConnectingSplash: Effect.void, + handleBackendReady: () => Effect.void, + handleBackendNotReady: Effect.void, + flushMainWindowBounds: input.flushMainWindowBounds ?? Effect.void, + dispatchMenuAction: () => Effect.void, + zoomMain: () => Effect.void, + syncAppearance: Effect.void, + }); +} + describe("DesktopLifecycle", () => { for (const platform of ["darwin", "win32", "linux"] satisfies ReadonlyArray) { it.effect(`lets the updater's quit event proceed on ${platform}`, () => { const appListeners = new Map void>(); - - const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { - metadata: Effect.die("unexpected metadata read"), - name: Effect.succeed("T3 Code"), - systemLocale: Effect.succeed("en-US"), - whenReady: Effect.void, - quit: Effect.void, - exit: () => Effect.void, - relaunch: () => Effect.void, - setPath: () => Effect.void, - setName: () => Effect.void, - setAboutPanelOptions: () => Effect.void, - setAppUserModelId: () => Effect.void, - getAppMetrics: Effect.succeed([]), - isDefaultProtocolClient: () => Effect.succeed(false), - setAsDefaultProtocolClient: () => Effect.succeed(true), - setDesktopName: () => Effect.void, - setDockIcon: () => Effect.void, - appendCommandLineSwitch: () => Effect.void, - removeCommandLineSwitch: () => Effect.void, - onBeforeQuitForUpdate: (listener) => - Effect.acquireRelease( - Effect.sync(() => { - appListeners.set("before-quit-for-update", listener); - }), - () => - Effect.sync(() => { - appListeners.delete("before-quit-for-update"); - }), - ).pipe(Effect.asVoid), - on: (eventName, listener) => - Effect.acquireRelease( - Effect.sync(() => { - appListeners.set( - eventName, - listener as unknown as (...args: readonly unknown[]) => void, - ); - }), - () => - Effect.sync(() => { - appListeners.delete(eventName); - }), - ).pipe(Effect.asVoid), - } satisfies ElectronApp.ElectronApp["Service"]); - - const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { - shouldUseDarkColors: Effect.succeed(false), - setSource: () => Effect.void, - onUpdated: () => Effect.void, - }); - - const desktopWindowLayer = Layer.succeed(DesktopWindow.DesktopWindow, { - createMain: Effect.die("unexpected window creation"), - ensureMain: Effect.die("unexpected window creation"), - revealOrCreateMain: Effect.die("unexpected window creation"), - activate: Effect.void, - createMainIfBackendReady: Effect.void, - showConnectingSplash: Effect.void, - handleBackendReady: () => Effect.void, - handleBackendNotReady: Effect.void, - flushMainWindowBounds: Effect.void, - dispatchMenuAction: () => Effect.void, - zoomMain: () => Effect.void, - syncAppearance: Effect.void, - }); - const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { platform, isDevelopment: false, } as DesktopEnvironment.DesktopEnvironment["Service"]); const layer = DesktopLifecycle.layer.pipe( - Layer.provideMerge(electronAppLayer), + Layer.provideMerge(makeElectronAppLayer(appListeners)), Layer.provideMerge(electronThemeLayer), - Layer.provideMerge(desktopWindowLayer), + Layer.provideMerge(makeElectronWindowLayer()), + Layer.provideMerge(makeDesktopWindowLayer()), Layer.provideMerge(environmentLayer), Layer.provideMerge(DesktopShutdown.layer), Layer.provideMerge(DesktopState.layer), @@ -123,4 +143,103 @@ describe("DesktopLifecycle", () => { ).pipe(Effect.provide(layer)); }); } + + it.effect("destroys windows before waiting for backend shutdown", () => + Effect.gen(function* () { + const appListeners = new Map void>(); + const shutdownRequested = yield* Deferred.make(); + const allowShutdown = yield* Deferred.make(); + const quitRequested = yield* Deferred.make(); + const events: string[] = []; + + const quit = Effect.sync(() => { + events.push("quit"); + }).pipe(Effect.andThen(Deferred.succeed(quitRequested, undefined)), Effect.asVoid); + const destroyAll = Effect.sync(() => { + events.push("destroy"); + }); + const flushMainWindowBounds = Effect.sync(() => { + events.push("flush"); + }); + + const desktopShutdownLayer = Layer.succeed(DesktopShutdown.DesktopShutdown, { + request: Effect.sync(() => { + events.push("request"); + }).pipe(Effect.andThen(Deferred.succeed(shutdownRequested, undefined)), Effect.asVoid), + awaitRequest: Deferred.await(shutdownRequested), + markComplete: Deferred.succeed(allowShutdown, undefined).pipe(Effect.asVoid), + awaitComplete: Deferred.await(allowShutdown), + isComplete: Deferred.isDone(allowShutdown), + }); + + const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { + platform: "darwin", + isDevelopment: false, + } as DesktopEnvironment.DesktopEnvironment["Service"]); + + const layer = DesktopLifecycle.layer.pipe( + Layer.provideMerge(makeElectronAppLayer(appListeners, quit)), + Layer.provideMerge(electronThemeLayer), + Layer.provideMerge(makeElectronWindowLayer(destroyAll)), + Layer.provideMerge(makeDesktopWindowLayer({ flushMainWindowBounds })), + Layer.provideMerge(environmentLayer), + Layer.provideMerge(desktopShutdownLayer), + Layer.provideMerge(DesktopState.layer), + ); + + yield* Effect.scoped( + Effect.gen(function* () { + const lifecycle = yield* DesktopLifecycle.DesktopLifecycle; + yield* lifecycle.register; + + const event = { preventDefault: () => undefined } as Electron.Event; + appListeners.get("before-quit")?.(event); + + yield* Deferred.await(shutdownRequested); + const eventsBeforeCleanup = [...events]; + yield* Deferred.succeed(allowShutdown, undefined); + yield* Deferred.await(quitRequested); + + assert.deepEqual(eventsBeforeCleanup, ["flush", "destroy", "request"]); + assert.deepEqual(events, ["flush", "destroy", "request", "quit"]); + }), + ).pipe(Effect.provide(layer)); + }), + ); + + it.effect("ignores app activation while quitting", () => + Effect.gen(function* () { + const appListeners = new Map void>(); + let activationCount = 0; + const activate = Effect.sync(() => { + activationCount += 1; + }); + const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, { + platform: "darwin", + isDevelopment: false, + } as DesktopEnvironment.DesktopEnvironment["Service"]); + const layer = DesktopLifecycle.layer.pipe( + Layer.provideMerge(makeElectronAppLayer(appListeners)), + Layer.provideMerge(electronThemeLayer), + Layer.provideMerge(makeElectronWindowLayer()), + Layer.provideMerge(makeDesktopWindowLayer({ activate })), + Layer.provideMerge(environmentLayer), + Layer.provideMerge(DesktopShutdown.layer), + Layer.provideMerge(DesktopState.layer), + ); + + yield* Effect.scoped( + Effect.gen(function* () { + const lifecycle = yield* DesktopLifecycle.DesktopLifecycle; + const state = yield* DesktopState.DesktopState; + yield* lifecycle.register; + yield* Ref.set(state.quitting, true); + + appListeners.get("activate")?.(); + + assert.equal(activationCount, 0); + }), + ).pipe(Effect.provide(layer)); + }), + ); }); diff --git a/apps/desktop/src/app/DesktopLifecycle.ts b/apps/desktop/src/app/DesktopLifecycle.ts index ab03d18f38d4..6a98e59eb870 100644 --- a/apps/desktop/src/app/DesktopLifecycle.ts +++ b/apps/desktop/src/app/DesktopLifecycle.ts @@ -12,6 +12,7 @@ import { makeComponentLogger } from "./DesktopObservability.ts"; import * as DesktopShutdown from "./DesktopShutdown.ts"; import * as ElectronApp from "../electron/ElectronApp.ts"; import * as ElectronTheme from "../electron/ElectronTheme.ts"; +import * as ElectronWindow from "../electron/ElectronWindow.ts"; import * as DesktopState from "./DesktopState.ts"; import * as DesktopWindow from "../window/DesktopWindow.ts"; @@ -35,8 +36,12 @@ export type DesktopLifecycleRuntimeServices = | ElectronApp.ElectronApp | ElectronTheme.ElectronTheme; +type DesktopLifecycleRegistrationServices = + | DesktopLifecycleRuntimeServices + | ElectronWindow.ElectronWindow; + /** - * @effect-expect-leaking DesktopEnvironment | DesktopShutdown | DesktopState | DesktopWindow | ElectronApp | ElectronTheme + * @effect-expect-leaking DesktopEnvironment | DesktopShutdown | DesktopState | DesktopWindow | ElectronApp | ElectronTheme | ElectronWindow */ export class DesktopLifecycle extends Context.Service< DesktopLifecycle, @@ -44,7 +49,11 @@ export class DesktopLifecycle extends Context.Service< readonly relaunch: ( reason: string, ) => Effect.Effect; - readonly register: Effect.Effect; + readonly register: Effect.Effect< + void, + never, + Scope.Scope | DesktopLifecycleRegistrationServices + >; } >()("@t3tools/desktop/app/DesktopLifecycle") {} @@ -73,14 +82,13 @@ function addScopedListener>( } const requestDesktopShutdownAndWait = Effect.fn("desktop.lifecycle.requestShutdownAndWait")( - function* (): Effect.fn.Return< - void, - never, - DesktopShutdown.DesktopShutdown | DesktopWindow.DesktopWindow - > { + function* ( + afterBoundsFlush: Effect.Effect = Effect.void, + ): Effect.fn.Return { const shutdown = yield* DesktopShutdown.DesktopShutdown; const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.flushMainWindowBounds; + yield* afterBoundsFlush; yield* shutdown.request; yield* shutdown.awaitComplete; }, @@ -88,7 +96,9 @@ const requestDesktopShutdownAndWait = Effect.fn("desktop.lifecycle.requestShutdo function handleBeforeQuit( event: Electron.Event, - runEffect: (effect: Effect.Effect) => Promise, + runEffect: ( + effect: Effect.Effect, + ) => Promise, allowQuit: () => boolean, markQuitAllowed: () => void, ): void { @@ -107,9 +117,16 @@ function handleBeforeQuit( void runEffect( Effect.gen(function* () { const state = yield* DesktopState.DesktopState; + const electronWindow = yield* ElectronWindow.ElectronWindow; yield* Ref.set(state.quitting, true); yield* logLifecycleInfo("before-quit received"); - yield* requestDesktopShutdownAndWait(); + yield* requestDesktopShutdownAndWait( + electronWindow.destroyAll.pipe( + Effect.catchCause((cause) => + logLifecycleError("failed to destroy windows before shutdown", { cause }), + ), + ), + ); }).pipe(Effect.withSpan("desktop.lifecycle.beforeQuit")), ).finally(() => { markQuitAllowed(); @@ -124,7 +141,9 @@ function handleBeforeQuit( function quitFromSignal( signal: "SIGINT" | "SIGTERM", - runEffect: (effect: Effect.Effect) => Promise, + runEffect: ( + effect: Effect.Effect, + ) => Promise, ): void { void runEffect( Effect.gen(function* () { @@ -173,7 +192,7 @@ export const make = DesktopLifecycle.of({ const electronApp = yield* ElectronApp.ElectronApp; const electronTheme = yield* ElectronTheme.ElectronTheme; const environment = yield* DesktopEnvironment.DesktopEnvironment; - const context = yield* Effect.context(); + const context = yield* Effect.context(); const runEffect = Effect.runPromiseWith(context); let quitAllowed = false; let updaterQuitAllowed = false; @@ -204,7 +223,13 @@ export const make = DesktopLifecycle.of({ ); }); yield* electronApp.on("activate", () => { - void runEffect(desktopWindow.activate.pipe(Effect.withSpan("desktop.lifecycle.activate"))); + void runEffect( + Effect.gen(function* () { + const state = yield* DesktopState.DesktopState; + if (yield* Ref.get(state.quitting)) return; + yield* desktopWindow.activate; + }).pipe(Effect.withSpan("desktop.lifecycle.activate")), + ); }); yield* electronApp.on("window-all-closed", () => { void runEffect( diff --git a/apps/desktop/src/electron/ElectronWindow.test.ts b/apps/desktop/src/electron/ElectronWindow.test.ts index 67819def623d..bebb0e5c4178 100644 --- a/apps/desktop/src/electron/ElectronWindow.test.ts +++ b/apps/desktop/src/electron/ElectronWindow.test.ts @@ -208,7 +208,7 @@ describe("ElectronWindow", () => { }).pipe(Effect.provide(TestLayer)), ); - it.effect("preserves destroy failures with the target window", () => + it.effect("preserves destroy failures and continues with later windows", () => Effect.gen(function* () { const cause = new Error("window destroy failed"); const window = { @@ -217,7 +217,11 @@ describe("ElectronWindow", () => { throw cause; }), } as unknown as Electron.BrowserWindow; - getAllWindowsMock.mockReturnValueOnce([window]); + const laterWindow = { + id: 44, + destroy: vi.fn(), + } as unknown as Electron.BrowserWindow; + getAllWindowsMock.mockReturnValueOnce([window, laterWindow]); const electronWindow = yield* ElectronWindow.ElectronWindow; const exit = yield* Effect.exit(electronWindow.destroyAll); @@ -231,6 +235,7 @@ describe("ElectronWindow", () => { assert.isNull(error.channel); assert.strictEqual(error.cause, cause); } + assert.equal(vi.mocked(laterWindow.destroy).mock.calls.length, 1); }).pipe(Effect.provide(TestLayer)), ); }); diff --git a/apps/desktop/src/electron/ElectronWindow.ts b/apps/desktop/src/electron/ElectronWindow.ts index 4671328587ae..5f6a9d34280b 100644 --- a/apps/desktop/src/electron/ElectronWindow.ts +++ b/apps/desktop/src/electron/ElectronWindow.ts @@ -1,6 +1,8 @@ import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; +import type * as Cause from "effect/Cause"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; @@ -257,18 +259,27 @@ export const make = Effect.gen(function* () { } }), destroyAll: Effect.gen(function* () { + let firstFailure: Cause.Cause | undefined; for (const window of yield* listWindows) { - yield* Effect.try({ - try: () => window.destroy(), - catch: (cause) => - new ElectronWindowOperationError({ - operation: "destroy-window", - platform, - windowId: window.id, - channel: null, - cause, - }), - }).pipe(Effect.orDie); + const exit = yield* Effect.exit( + Effect.try({ + try: () => window.destroy(), + catch: (cause) => + new ElectronWindowOperationError({ + operation: "destroy-window", + platform, + windowId: window.id, + channel: null, + cause, + }), + }).pipe(Effect.orDie), + ); + if (Exit.isFailure(exit)) { + firstFailure ??= exit.cause; + } + } + if (firstFailure !== undefined) { + return yield* Effect.failCause(firstFailure); } }), syncAllAppearance: Effect.fn("desktop.electron.window.syncAllAppearance")(function* ( From f2d5fc91e3030e5c3956fdadc13e1eaa25bcabe3 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 19 Aug 2026 06:59:26 -0400 Subject: [PATCH 3/4] fix(desktop): stop automatic passkey prompts (#7522) --- .../components/clerk/electronPasskeys.test.ts | 60 +++++++++++++++++ apps/web/src/main.tsx | 14 ++-- pnpm-lock.yaml | 64 +++++++++---------- pnpm-workspace.yaml | 11 +++- 4 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 apps/web/src/components/clerk/electronPasskeys.test.ts diff --git a/apps/web/src/components/clerk/electronPasskeys.test.ts b/apps/web/src/components/clerk/electronPasskeys.test.ts new file mode 100644 index 000000000000..582b801cc50f --- /dev/null +++ b/apps/web/src/components/clerk/electronPasskeys.test.ts @@ -0,0 +1,60 @@ +import { createPasskeys } from "@clerk/electron/passkeys"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; + +const publicKeyOptions = { + allowCredentials: [], + challenge: new Uint8Array([1]), + rpId: "clerk.t3.codes", + timeout: 60_000, + userVerification: "preferred" as const, +}; + +const stubNativePasskeys = () => { + const get = vi.fn().mockResolvedValue({ + ok: false, + error: { code: "cancelled", message: "user cancelled" }, + }); + + vi.stubGlobal("location", { protocol: "t3code:", hostname: "app" }); + vi.stubGlobal("window", { + PublicKeyCredential: vi.fn(), + __clerk_internal_electron_passkeys: { + platform: "darwin", + electronMajor: 41, + get, + }, + }); + + return get; +}; + +describe("Electron passkeys", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("does not send an autofill request to the native bridge", async () => { + const get = stubNativePasskeys(); + const passkeys = createPasskeys(); + + const result = await passkeys.get({ + publicKeyOptions, + conditionalUI: true, + }); + + expect(get).not.toHaveBeenCalled(); + expect(result.error).toMatchObject({ code: "passkey_operation_aborted" }); + }); + + it("sends an explicit passkey request to the native bridge", async () => { + const get = stubNativePasskeys(); + const passkeys = createPasskeys(); + + await passkeys.get({ + publicKeyOptions, + conditionalUI: false, + }); + + expect(get).toHaveBeenCalledOnce(); + }); +}); diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index e92bd6629d7c..6eaaca6f57de 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,7 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { ClerkProvider } from "@clerk/react"; -import { passkeys as electronPasskeys } from "@clerk/electron/passkeys"; +import { passkeys } from "@clerk/electron/passkeys"; import { ClerkProvider as ElectronClerkProvider } from "@clerk/electron/react"; import { createHashHistory, createBrowserHistory } from "@tanstack/react-router"; @@ -30,14 +30,9 @@ if (isElectron) { const clerkPublishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string | undefined; -// @clerk/electron reports passkey autofill as supported but executes the -// "quiet" autofill request as a modal prompt, so Clerk's sign-in form pops an -// OS passkey dialog the moment it mounts. Report autofill as unsupported; the -// explicit "Use passkey" button keeps working. -// Upstream: https://github.com/clerk/javascript/issues/9496 -const passkeys = { - ...electronPasskeys, - isAutoFillSupported: () => Promise.resolve(false), +// First Clerk UI build containing https://github.com/clerk/javascript/pull/9500. +const electronClerkUI = { + __internal_clerkUIVersion: "1.30.5-canary.v20260819050620", }; const app = ; @@ -47,6 +42,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( {clerkPublishableKey && hasCloudPublicConfig() ? ( isElectron ? ( @solana/wallet-adapter-react': '-' '@clerk/clerk-js>@solana/wallet-standard': '-' '@clerk/clerk-js>@wallet-standard/core': '-' - '@clerk/electron': 0.0.33 - '@clerk/electron-passkeys': 0.0.3 + '@clerk/electron': 0.0.34-canary.v20260819050620 + '@clerk/electron-passkeys': 0.0.4-canary.v20260819050620 '@clerk/expo': 4.2.0 '@clerk/react': 6.14.4 '@clerk/shared': 4.29.2 @@ -114,11 +114,11 @@ importers: apps/desktop: dependencies: '@clerk/electron': - specifier: 0.0.33 - version: 0.0.33(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: 0.0.34-canary.v20260819050620 + version: 0.0.34-canary.v20260819050620(@clerk/electron-passkeys@0.0.4-canary.v20260819050620)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/electron-passkeys': - specifier: 0.0.3 - version: 0.0.3 + specifier: 0.0.4-canary.v20260819050620 + version: 0.0.4-canary.v20260819050620 '@effect/platform-node': specifier: 4.0.0-beta.103 version: 4.0.0-beta.103(bufferutil@4.1.0)(effect@4.0.0-beta.103(patch_hash=af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb6))(ioredis@5.11.0)(utf-8-validate@6.0.6) @@ -520,8 +520,8 @@ importers: specifier: ^1.4.1 version: 1.5.0(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/electron': - specifier: 0.0.33 - version: 0.0.33(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: 0.0.34-canary.v20260819050620 + version: 0.0.34-canary.v20260819050620(@clerk/electron-passkeys@0.0.4-canary.v20260819050620)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/react': specifier: 6.14.4 version: 6.14.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -1721,35 +1721,35 @@ packages: resolution: {integrity: sha512-4i4RE+ZQ0hKDDFSRKCISQPBy07SfFeFAhBUhNj2j01Nx2n4pqV+5iyg2Vf27+NKFNkgq7kdmZGvtug24D++8WQ==} engines: {node: '>=20.9.0'} - '@clerk/electron-passkeys-darwin-arm64@0.0.3': - resolution: {integrity: sha512-oAlrd+GLqP0oREP3hNRc6yNZhXGaQgJss9iCWRQqXjpw1yZD1ZU+L+E2Gfg84vMj/NSUhxbU0Djy0RXhmeUwgg==} + '@clerk/electron-passkeys-darwin-arm64@0.0.4-canary.v20260819050620': + resolution: {integrity: sha512-avxygtEfWBD3YPkxws4lB5mL/75SQvVBMQCg+b1gjFsQInnVOgAqAv4wHR8wgUsEbXY2zEkvqaYjFSnEveC0VA==} cpu: [arm64] os: [darwin] - '@clerk/electron-passkeys-darwin-x64@0.0.3': - resolution: {integrity: sha512-0UPAWQEni7o8gjWwd2sP97fPshJmT2w5b8eV/jOPVSP8NqIcFCm5yIFSEx1SfB53LGfzB01A4iEGi2pxiCHqyQ==} + '@clerk/electron-passkeys-darwin-x64@0.0.4-canary.v20260819050620': + resolution: {integrity: sha512-clShldbwDcC2ek6gJT85QKsMfaPyOhEgfz61u2n3YKivbeui33XjJu261d+LO20mqDjXZy3flLGPTIh/Kii80g==} cpu: [x64] os: [darwin] - '@clerk/electron-passkeys-win32-arm64-msvc@0.0.3': - resolution: {integrity: sha512-bbRvifm9A/gfPkwC1UyMyF9sYGEs0aen7J2kCcaOIW3K0PXcAbHtsR/gnx4dT0bjFdd21vSa2zlIdERvHc1BiA==} + '@clerk/electron-passkeys-win32-arm64-msvc@0.0.4-canary.v20260819050620': + resolution: {integrity: sha512-alIfXJSxyeulnNeHoT4X7+qO0o9/EBhiRWMhCXwKJIbWfwzqnnVrRH1P7HfYRnDepKhqRzsUaBuYTqQzisHG+A==} cpu: [arm64] os: [win32] - '@clerk/electron-passkeys-win32-x64-msvc@0.0.3': - resolution: {integrity: sha512-gFaVqKlOKTF2SJ3lMwkZ0oonycQn93p1qSrp5kzzeZBeQ+tGB3gQnjXecrhQy21AzDUyWqVJJ8KrP2HRAwifWg==} + '@clerk/electron-passkeys-win32-x64-msvc@0.0.4-canary.v20260819050620': + resolution: {integrity: sha512-QSKKMCdV9AhOt74ls8ExdwR+2ikoLGgN2LdWIjBgNk6epJVs2L1FXVZOM0ZqfVHFSl5y7vLmlOACR6WyJ2eoLg==} cpu: [x64] os: [win32] - '@clerk/electron-passkeys@0.0.3': - resolution: {integrity: sha512-OHhIe88qDL+FxyBalXdXNHAS5eEramr6Rerp+6iNkfkjqT8rx4hHNmfpmjg5/T1/am8QfknbOBZkqoXZlCrjPg==} + '@clerk/electron-passkeys@0.0.4-canary.v20260819050620': + resolution: {integrity: sha512-zOoFEsvOrArv7p4ERNAdkGbYoD86ragDhysLG1NdxJm2jnfw+AfwxUv0FkOPZ7iNuQ0rbJaR/urxdvEwVcuKRA==} engines: {node: '>=20.9.0'} - '@clerk/electron@0.0.33': - resolution: {integrity: sha512-lP2B7wGHwKWrCAh4zPWPfRUIHoxQ1rwG8iiFeLmI8jBjSGcSoPX1N9bIKUCWL+Z9POmC2aRn6yXvL7e9yzfEVw==} + '@clerk/electron@0.0.34-canary.v20260819050620': + resolution: {integrity: sha512-sfKWkH2SigKcxtR+h36G1NGnmMI5xOlwvqHpr3JYcacS/uokQbeAjCCQzEeV/9zqWYtRL4et5wCD13+gWv4b6w==} engines: {node: '>=20.9.0'} peerDependencies: - '@clerk/electron-passkeys': 0.0.3 + '@clerk/electron-passkeys': 0.0.4-canary.v20260819050620 electron: '>=28' electron-store: ^8.2.0 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0 @@ -11580,26 +11580,26 @@ snapshots: - react - react-dom - '@clerk/electron-passkeys-darwin-arm64@0.0.3': + '@clerk/electron-passkeys-darwin-arm64@0.0.4-canary.v20260819050620': optional: true - '@clerk/electron-passkeys-darwin-x64@0.0.3': + '@clerk/electron-passkeys-darwin-x64@0.0.4-canary.v20260819050620': optional: true - '@clerk/electron-passkeys-win32-arm64-msvc@0.0.3': + '@clerk/electron-passkeys-win32-arm64-msvc@0.0.4-canary.v20260819050620': optional: true - '@clerk/electron-passkeys-win32-x64-msvc@0.0.3': + '@clerk/electron-passkeys-win32-x64-msvc@0.0.4-canary.v20260819050620': optional: true - '@clerk/electron-passkeys@0.0.3': + '@clerk/electron-passkeys@0.0.4-canary.v20260819050620': optionalDependencies: - '@clerk/electron-passkeys-darwin-arm64': 0.0.3 - '@clerk/electron-passkeys-darwin-x64': 0.0.3 - '@clerk/electron-passkeys-win32-arm64-msvc': 0.0.3 - '@clerk/electron-passkeys-win32-x64-msvc': 0.0.3 + '@clerk/electron-passkeys-darwin-arm64': 0.0.4-canary.v20260819050620 + '@clerk/electron-passkeys-darwin-x64': 0.0.4-canary.v20260819050620 + '@clerk/electron-passkeys-win32-arm64-msvc': 0.0.4-canary.v20260819050620 + '@clerk/electron-passkeys-win32-x64-msvc': 0.0.4-canary.v20260819050620 - '@clerk/electron@0.0.33(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@clerk/electron@0.0.34-canary.v20260819050620(@clerk/electron-passkeys@0.0.4-canary.v20260819050620)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@clerk/clerk-js': 6.29.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@clerk/react': 6.14.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -11608,7 +11608,7 @@ snapshots: react: 19.2.6 tslib: 2.8.1 optionalDependencies: - '@clerk/electron-passkeys': 0.0.3 + '@clerk/electron-passkeys': 0.0.4-canary.v20260819050620 electron-store: 8.2.0 react-dom: 19.2.6(react@19.2.6) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 64274f598ac9..7ec8faf701f6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -25,8 +25,8 @@ allowBuilds: catalog: "@clerk/backend": 3.14.0 "@clerk/clerk-js": 6.29.2 - "@clerk/electron": 0.0.33 - "@clerk/electron-passkeys": 0.0.3 + "@clerk/electron": 0.0.34-canary.v20260819050620 + "@clerk/electron-passkeys": 0.0.4-canary.v20260819050620 "@clerk/expo": 4.2.0 "@clerk/react": 6.14.4 "@clerk/shared": 4.29.2 @@ -55,7 +55,8 @@ catalog: minimumReleaseAgeExclude: - "@clerk/backend@3.14.0" - "@clerk/clerk-js@6.29.2" - - "@clerk/electron@0.0.33" + - "@clerk/electron@0.0.34-canary.v20260819050620" + - "@clerk/electron-passkeys@0.0.4-canary.v20260819050620" - "@clerk/expo@4.2.0" - "@clerk/react@6.14.4" - "@clerk/shared@4.29.2" @@ -76,6 +77,10 @@ minimumReleaseAgeExclude: - alchemy@2.0.0-beta.65 - effect@4.0.0-beta.103 - "@legendapp/list@3.3.5" + - "@clerk/electron-passkeys-darwin-arm64@0.0.4-canary.v20260819050620" + - "@clerk/electron-passkeys-darwin-x64@0.0.4-canary.v20260819050620" + - "@clerk/electron-passkeys-win32-arm64-msvc@0.0.4-canary.v20260819050620" + - "@clerk/electron-passkeys-win32-x64-msvc@0.0.4-canary.v20260819050620" overrides: "@clerk/backend": "catalog:" From aed2fc85ad1cfc40b396c5b69c85b9e1c25ec90a Mon Sep 17 00:00:00 2001 From: sheehanmunim Date: Wed, 19 Aug 2026 13:03:12 -0400 Subject: [PATCH 4/4] feat: choose the app icon, and name the artwork scenes Artwork options are Night sky, Blueprint, None, and your own uploads; "Match the build" is gone and Night sky is the default. An explicit pick now outranks the environment-identification mode and the palette heuristic, which is why choosing a scene did nothing on a release build. App icon adds the MT mark, a light variant, a dark variant, or your own image. It is applied to the running app - Dock tile on macOS, window icon elsewhere - because rewriting the installed bundle's icon would break the code signature and take every macOS permission grant with it. Co-Authored-By: Claude Opus 5 (1M context) --- apps/desktop/src/appIcon/appIcon.ts | 87 ++++++++++++ apps/desktop/src/ipc/DesktopIpcHandlers.ts | 2 + apps/desktop/src/ipc/channels.ts | 1 + apps/desktop/src/ipc/methods/appIcon.ts | 21 +++ apps/desktop/src/preload.ts | 1 + apps/web/src/AppRoot.tsx | 12 ++ .../components/SidebarStageBackdrop.test.tsx | 16 +++ .../src/components/SidebarStageBackdrop.tsx | 11 +- .../components/settings/SettingsPanels.tsx | 129 +++++++++++++++++- .../src/components/settings/settingsSearch.ts | 5 + apps/web/src/hooks/useAppIcon.ts | 28 ++++ assets/munim/munim-icon-dark-1024.png | Bin 0 -> 37166 bytes assets/munim/munim-icon-light-1024.png | Bin 0 -> 34930 bytes packages/contracts/src/ipc.ts | 6 + packages/contracts/src/settings.ts | 41 +++++- scripts/build-desktop-artifact.ts | 8 ++ 16 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/appIcon/appIcon.ts create mode 100644 apps/desktop/src/ipc/methods/appIcon.ts create mode 100644 apps/web/src/hooks/useAppIcon.ts create mode 100644 assets/munim/munim-icon-dark-1024.png create mode 100644 assets/munim/munim-icon-light-1024.png diff --git a/apps/desktop/src/appIcon/appIcon.ts b/apps/desktop/src/appIcon/appIcon.ts new file mode 100644 index 000000000000..3d11af9cdd9b --- /dev/null +++ b/apps/desktop/src/appIcon/appIcon.ts @@ -0,0 +1,87 @@ +// @effect-diagnostics nodeBuiltinImport:off - resolves icon files on disk from the Electron main process, outside any Effect runtime. +/** + * Runtime app icon. + * + * The installed bundle's icon cannot be rewritten without breaking the code + * signature — and a broken signature takes every macOS permission grant with + * it (see the TCC repair in computerUse/permissions). So the icon the user + * picks is applied to the *running* app instead: the Dock tile on macOS, the + * window icon elsewhere. + * + * @module appIcon/appIcon + */ +import * as NodeFS from "node:fs"; +import * as NodePath from "node:path"; + +import * as Electron from "electron"; + +/** Built-in icons, resolved from the assets shipped beside the app. */ +const BUILT_IN_FILES: Readonly> = { + default: "munim-macos-1024.png", + light: "munim-icon-light-1024.png", + dark: "munim-icon-dark-1024.png", +}; + +function iconSearchRoots(): readonly string[] { + return [ + // Packaged: extraResources copies the assets to Resources/app-icons. + NodePath.join(process.resourcesPath ?? "", "app-icons"), + // Checkout: run straight from the repo's assets. + NodePath.join(Electron.app.getAppPath(), "..", "..", "assets", "munim"), + NodePath.join(process.cwd(), "assets", "munim"), + ]; +} + +function resolveBuiltInIconPath(id: string): string | null { + const file = BUILT_IN_FILES[id]; + if (file === undefined) return null; + for (const root of iconSearchRoots()) { + if (root.length === 0) continue; + const candidate = NodePath.join(root, file); + try { + if (NodeFS.existsSync(candidate)) return candidate; + } catch { + // Keep looking. + } + } + return null; +} + +/** + * Apply an icon to the running app. `image` is a `data:` URL for a user's own + * icon; otherwise `id` names a built-in. Returns whether anything was applied, + * so a caller can leave the shipped icon alone rather than clearing it. + */ +export function applyAppIcon(input: { + readonly id: string; + readonly image?: string | undefined; +}): boolean { + const icon = + input.image !== undefined && input.image.startsWith("data:image/") + ? Electron.nativeImage.createFromDataURL(input.image) + : (() => { + const path = resolveBuiltInIconPath(input.id); + return path === null ? null : Electron.nativeImage.createFromPath(path); + })(); + if (icon === null || icon.isEmpty()) return false; + + if (process.platform === "darwin") { + try { + Electron.app.dock?.setIcon(icon); + return true; + } catch { + return false; + } + } + + let applied = false; + for (const window of Electron.BrowserWindow.getAllWindows()) { + try { + window.setIcon(icon); + applied = true; + } catch { + // A window that is closing cannot take an icon; the rest still can. + } + } + return applied; +} diff --git a/apps/desktop/src/ipc/DesktopIpcHandlers.ts b/apps/desktop/src/ipc/DesktopIpcHandlers.ts index 38ffc9682159..97d91213d7b8 100644 --- a/apps/desktop/src/ipc/DesktopIpcHandlers.ts +++ b/apps/desktop/src/ipc/DesktopIpcHandlers.ts @@ -23,6 +23,7 @@ import { getComputerUsePermissions, openComputerUsePrivacySettings, } from "./methods/computerUse.ts"; +import { setAppIcon } from "./methods/appIcon.ts"; import { clearComputerHistory, deleteComputerHistoryMemory, @@ -111,6 +112,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers" yield* ipc.handle(probeRemoteEditors); yield* ipc.handle(getComputerUsePermissions); yield* ipc.handle(openComputerUsePrivacySettings); + yield* ipc.handle(setAppIcon); yield* ipc.handle(getComputerHistoryStatus); yield* ipc.handle(getComputerHistoryTimeline); yield* ipc.handle(patchComputerHistorySettings); diff --git a/apps/desktop/src/ipc/channels.ts b/apps/desktop/src/ipc/channels.ts index 8ee79d6557ae..5cf54bbce217 100644 --- a/apps/desktop/src/ipc/channels.ts +++ b/apps/desktop/src/ipc/channels.ts @@ -4,6 +4,7 @@ export const SET_THEME_CHANNEL = "desktop:set-theme"; export const CONTEXT_MENU_CHANNEL = "desktop:context-menu"; export const OPEN_EXTERNAL_CHANNEL = "desktop:open-external"; export const PROBE_REMOTE_EDITORS_CHANNEL = "desktop:probe-remote-editors"; +export const SET_APP_ICON_CHANNEL = "desktop:set-app-icon"; export const GET_COMPUTER_USE_PERMISSIONS_CHANNEL = "desktop:get-computer-use-permissions"; export const OPEN_COMPUTER_USE_PRIVACY_SETTINGS_CHANNEL = "desktop:open-computer-use-privacy-settings"; diff --git a/apps/desktop/src/ipc/methods/appIcon.ts b/apps/desktop/src/ipc/methods/appIcon.ts new file mode 100644 index 000000000000..b262c33ab55f --- /dev/null +++ b/apps/desktop/src/ipc/methods/appIcon.ts @@ -0,0 +1,21 @@ +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; + +import { applyAppIcon } from "../../appIcon/appIcon.ts"; +import * as IpcChannels from "../channels.ts"; +import * as DesktopIpc from "../DesktopIpc.ts"; + +export const setAppIcon = DesktopIpc.makeIpcMethod({ + channel: IpcChannels.SET_APP_ICON_CHANNEL, + payload: Schema.Struct({ + id: Schema.String, + image: Schema.optional(Schema.String), + }), + result: Schema.Boolean, + handler: Effect.fn("desktop.ipc.appIcon.setAppIcon")(function* (input) { + return applyAppIcon({ + id: input.id, + ...(input.image === undefined ? {} : { image: input.image }), + }); + }), +}); diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 7b5fc126875f..9a9ba8b8a8c6 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -137,6 +137,7 @@ contextBridge.exposeInMainWorld("desktopBridge", { }), openExternal: (url: string) => ipcRenderer.invoke(IpcChannels.OPEN_EXTERNAL_CHANNEL, url), probeRemoteEditors: () => ipcRenderer.invoke(IpcChannels.PROBE_REMOTE_EDITORS_CHANNEL, undefined), + setAppIcon: (input) => ipcRenderer.invoke(IpcChannels.SET_APP_ICON_CHANNEL, input), getComputerUsePermissions: () => ipcRenderer.invoke(IpcChannels.GET_COMPUTER_USE_PERMISSIONS_CHANNEL, undefined), openComputerUsePrivacySettings: (pane) => diff --git a/apps/web/src/AppRoot.tsx b/apps/web/src/AppRoot.tsx index 3552c4b7ced3..4b854cc1700f 100644 --- a/apps/web/src/AppRoot.tsx +++ b/apps/web/src/AppRoot.tsx @@ -6,6 +6,7 @@ import { PreviewAutomationHosts } from "./components/preview/PreviewAutomationHo import { QuitHoldOverlay } from "./components/QuitHoldOverlay"; import { AppAtomRegistryProvider } from "./rpc/atomRegistry"; import { VoiceSessionProvider } from "./components/voice/VoiceSession"; +import { useAppIcon } from "./hooks/useAppIcon"; import type { AppRouter } from "./router"; /** @@ -16,6 +17,7 @@ import type { AppRouter } from "./router"; export function AppRoot({ router }: { readonly router: AppRouter }) { return ( + @@ -26,3 +28,13 @@ export function AppRoot({ router }: { readonly router: AppRouter }) { ); } + +/** + * Applies the account's app-icon choice to the running app. Rendered inside the + * atom registry because the setting lives in server settings, and as a + * component rather than a hook call so it re-runs on every settings change. + */ +function AppIconSync() { + useAppIcon(); + return null; +} diff --git a/apps/web/src/components/SidebarStageBackdrop.test.tsx b/apps/web/src/components/SidebarStageBackdrop.test.tsx index 4feb4b2f21c7..51c65c7c6f01 100644 --- a/apps/web/src/components/SidebarStageBackdrop.test.tsx +++ b/apps/web/src/components/SidebarStageBackdrop.test.tsx @@ -111,3 +111,19 @@ describe("resolveSidebarArtwork", () => { ).toBeNull(); }); }); + +describe("named artwork defaults", () => { + it("draws the named scene even when the environment mode would hide artwork", () => { + // The picker is the user's answer; the palette/mode heuristics upstream + // uses to decide whether art suits the theme must not override it. + expect( + resolveSidebarArtwork({ selection: "night", stageLabel: "", custom: [], enabled: true }), + ).toEqual(NIGHTLY_BACKDROP); + }); + + it("keeps the legacy auto value working for older settings files", () => { + expect( + resolveSidebarArtwork({ selection: "auto", stageLabel: "Dev", custom: [] }), + ).toEqual(DEV_BACKDROP); + }); +}); diff --git a/apps/web/src/components/SidebarStageBackdrop.tsx b/apps/web/src/components/SidebarStageBackdrop.tsx index d45a77884bd2..a742691f3c24 100644 --- a/apps/web/src/components/SidebarStageBackdrop.tsx +++ b/apps/web/src/components/SidebarStageBackdrop.tsx @@ -33,12 +33,14 @@ export function resolveSidebarArtwork(input: { if (selection === "none") return null; if (selection === "night") return NIGHTLY_BACKDROP; if (selection === "day") return DEV_BACKDROP; - if (selection !== "auto" && selection.length > 0) { + if (selection.length > 0 && selection !== "auto") { const artwork = input.custom.find((entry) => entry.id === selection); // A deleted artwork leaves the header bare rather than silently falling // back to a scene the user did not choose. return artwork ? { kind: "custom", image: artwork.image, name: artwork.name } : null; } + // Legacy "auto" (and an empty value) defer to the build channel, which is + // how this setting behaved before the scenes were named. return resolveSidebarStageBackdropVariant(input.stageLabel); } @@ -91,11 +93,16 @@ export function useSidebarStageBackdropVariant(enabled = true): SidebarStageBack selection: settings.sidebarArtwork, custom: settings.customSidebarArtworks, })); + // An explicit pick is the user telling us what to draw, so it outranks the + // environment-identification mode and the palette heuristic that upstream + // uses to decide whether artwork suits the current theme. `enabled` still + // governs the legacy "auto" value, which follows the build channel. + const explicit = artwork.selection.length > 0 && artwork.selection !== "auto"; return resolveSidebarArtwork({ selection: artwork.selection, stageLabel, custom: artwork.custom, - enabled, + enabled: explicit ? true : enabled, }); } diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 282ab27156fb..b144a4310962 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -19,7 +19,9 @@ import { } from "@t3tools/client-runtime/state/runtime"; import { DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE, + DEFAULT_APP_ICON_SELECTION, DEFAULT_SIDEBAR_ARTWORK_SELECTION, + MAX_CUSTOM_APP_ICON_BYTES, MAX_CUSTOM_SIDEBAR_ARTWORK_BYTES, DEFAULT_UNIFIED_SETTINGS, type EnvironmentIdentificationMode, @@ -1158,6 +1160,7 @@ export function AppearanceSettingsPanel() { ) : null} + @@ -1466,10 +1469,9 @@ function SidebarArtworkRow() { const options = useMemo( () => [ - { value: "auto", label: "Match the build" }, - { value: "none", label: "None" }, { value: "night", label: "Night sky" }, { value: "day", label: "Blueprint" }, + { value: "none", label: "None" }, ...custom.map((artwork) => ({ value: artwork.id, label: artwork.name })), ], [custom], @@ -1503,7 +1505,7 @@ function SidebarArtworkRow() { const removeArtwork = (id: string) => { updateSettings({ customSidebarArtworks: custom.filter((artwork) => artwork.id !== id), - ...(selection === id ? { sidebarArtwork: "auto" } : {}), + ...(selection === id ? { sidebarArtwork: DEFAULT_SIDEBAR_ARTWORK_SELECTION } : {}), }); }; @@ -1570,6 +1572,127 @@ function SidebarArtworkRow() { ); } +/** + * App icon: the built-in mark, a light or dark variant, or your own image. + * + * The installed bundle keeps its own icon — rewriting that would break the + * code signature and take every macOS permission grant with it — so the pick + * is applied to the running app's Dock tile (or window icon off macOS). + */ +function AppIconRow() { + const settings = usePrimarySettings(); + const updateSettings = useUpdatePrimarySettings(); + const fileInputRef = useRef(null); + const [addError, setAddError] = useState(null); + const custom = settings.customAppIcons; + const selection = settings.appIcon; + + const options = useMemo( + () => [ + { value: "default", label: "MT Code" }, + { value: "light", label: "Light — black mark" }, + { value: "dark", label: "Dark — white mark" }, + ...custom.map((icon) => ({ value: icon.id, label: icon.name })), + ], + [custom], + ); + + const onPickFile = async (file: File) => { + setAddError(null); + if (file.size > MAX_CUSTOM_APP_ICON_BYTES * 0.7) { + setAddError("That image is too large. Pick one under about 350 KB."); + return; + } + const image = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : null); + reader.onerror = () => resolve(null); + reader.readAsDataURL(file); + }); + // Electron's nativeImage reads PNG and JPEG only, so an SVG would save + // fine here and then silently fail to draw. + if (image === null || !/^data:image\/(png|jpeg|jpg)/.test(image)) { + setAddError("Use a square PNG or JPEG."); + return; + } + const id = `icon_${Math.random().toString(36).slice(2, 10)}`; + const name = file.name.replace(/\.[^.]+$/, "").slice(0, 60) || "Icon"; + updateSettings({ + customAppIcons: [...custom, { id, name, image, createdAt: new Date().toISOString() }], + appIcon: id, + }); + }; + + const removeIcon = (id: string) => { + updateSettings({ + customAppIcons: custom.filter((icon) => icon.id !== id), + ...(selection === id ? { appIcon: DEFAULT_APP_ICON_SELECTION } : {}), + }); + }; + + return ( + updateSettings({ appIcon: DEFAULT_APP_ICON_SELECTION })} + /> + ) : null + } + control={ +
+ +
+ {custom.some((icon) => icon.id === selection) ? ( + + ) : null} + +
+ { + const file = event.target.files?.[0]; + event.target.value = ""; + if (file) void onPickFile(file); + }} + /> + {addError !== null ? ( + {addError} + ) : null} +
+ } + /> + ); +} + function TypographySection() { const [advanced, setAdvanced] = useLocalStorage( TYPOGRAPHY_ADVANCED_STORAGE_KEY, diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index 3377aa73e1b2..d2eab451be95 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -217,6 +217,11 @@ export const SETTINGS_SEARCH_ITEMS = [ title: "Sidebar artwork", to: "/settings/appearance", }, + { + id: "app-icon", + title: "App icon", + to: "/settings/appearance", + }, { id: "keybindings", title: "Keybindings", diff --git a/apps/web/src/hooks/useAppIcon.ts b/apps/web/src/hooks/useAppIcon.ts new file mode 100644 index 000000000000..9a0488c050cb --- /dev/null +++ b/apps/web/src/hooks/useAppIcon.ts @@ -0,0 +1,28 @@ +import { useEffect } from "react"; + +import { usePrimarySettings } from "./useSettings"; + +/** + * Keeps the running app's icon in step with the account's choice. + * + * The installed bundle keeps its own icon on disk — rewriting that would break + * the code signature, and with it every macOS permission grant — so the pick is + * applied to the live Dock tile (or window icon off macOS) on load and on every + * change. Browsers have no local API and simply skip this. + */ +export function useAppIcon(): void { + const { selection, custom } = usePrimarySettings((settings) => ({ + selection: settings.appIcon, + custom: settings.customAppIcons, + })); + + useEffect(() => { + if (typeof window === "undefined") return; + const setIcon = window.desktopBridge?.setAppIcon; + if (setIcon === undefined) return; + const own = custom.find((icon) => icon.id === selection); + void setIcon(own ? { id: own.id, image: own.image } : { id: selection }).catch( + () => undefined, + ); + }, [custom, selection]); +} diff --git a/assets/munim/munim-icon-dark-1024.png b/assets/munim/munim-icon-dark-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..54c75a5d435e94569fb219d49c16daa0a84ff427 GIT binary patch literal 37166 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

Kp$YNKj@MdCt4s7C+aRv~i3y+V`RPU|!lAn6 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 literal 0 HcmV?d00001 diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index 8904b0c825d7..ccf5b90f00a1 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -1263,6 +1263,12 @@ export interface DesktopBridge { * needed so the app appears in the list. */ openComputerUsePrivacySettings?: (pane: DesktopComputerUsePrivacyPane) => Promise; + /** + * Swap the icon the running app wears. The installed bundle keeps its own + * icon: rewriting that would break the signature, and with it every macOS + * permission grant. + */ + setAppIcon?: (input: { readonly id: string; readonly image?: string }) => Promise; /** * Computer History daemon status (recording phase, paths, accessibility). * Optional for older desktop builds. diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 7c4c465a9278..431cb787af7d 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -118,16 +118,15 @@ export const DEFAULT_TERMINAL_FONT_SIZE: TerminalFontSize = 12; * Sidebar header artwork. * * Upstream ties the artwork to the Dev/Nightly channel; MT Code ships one - * release, so the artwork is a choice instead of a side effect of a build - * channel. "auto" keeps the upstream behaviour (art on Dev/Nightly, nothing - * on a release build); the named scenes and any custom artwork are explicit. + * release, so the artwork is a plain choice: a named scene, one of your own, + * or none. */ -export const SIDEBAR_ARTWORK_BUILT_IN_IDS = ["auto", "none", "night", "day"] as const; +export const SIDEBAR_ARTWORK_BUILT_IN_IDS = ["night", "day", "none"] as const; export const SidebarArtworkSelection = TrimmedNonEmptyString.check( Schema.isMaxLength(80), ); export type SidebarArtworkSelection = typeof SidebarArtworkSelection.Type; -export const DEFAULT_SIDEBAR_ARTWORK_SELECTION = "auto"; +export const DEFAULT_SIDEBAR_ARTWORK_SELECTION = "night"; /** Largest artwork a user may store, so settings stay a settings file. */ export const MAX_CUSTOM_SIDEBAR_ARTWORK_BYTES = 512 * 1024; @@ -147,6 +146,30 @@ export const CustomSidebarArtwork = Schema.Struct({ }); export type CustomSidebarArtwork = typeof CustomSidebarArtwork.Type; +/** + * Which icon the app wears in the Dock, taskbar, and window. + * + * The bundle's own icon cannot change at runtime without breaking the code + * signature (and with it every macOS permission grant), so this swaps the + * *running* icon rather than rewriting the installed app. + */ +export const APP_ICON_BUILT_IN_IDS = ["default", "light", "dark"] as const; +export const AppIconSelection = TrimmedNonEmptyString.check(Schema.isMaxLength(80)); +export type AppIconSelection = typeof AppIconSelection.Type; +export const DEFAULT_APP_ICON_SELECTION = "default"; + +/** Same size ceiling as artwork: a settings file, not an asset store. */ +export const MAX_CUSTOM_APP_ICON_BYTES = 512 * 1024; + +export const CustomAppIcon = Schema.Struct({ + id: TrimmedNonEmptyString.check(Schema.isMaxLength(80)), + name: TrimmedNonEmptyString.check(Schema.isMaxLength(60)), + /** `data:` URL for a square PNG or JPEG; Electron cannot load SVG icons. */ + image: TrimmedNonEmptyString.check(Schema.isMaxLength(MAX_CUSTOM_APP_ICON_BYTES)), + createdAt: TrimmedNonEmptyString.check(Schema.isMaxLength(40)), +}); +export type CustomAppIcon = typeof CustomAppIcon.Type; + export const EnvironmentIdentificationMode = Schema.Literals(["artwork", "pill", "none"]); export type EnvironmentIdentificationMode = typeof EnvironmentIdentificationMode.Type; export const DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE: EnvironmentIdentificationMode = "artwork"; @@ -804,6 +827,12 @@ export const ServerSettings = Schema.Struct({ customSidebarArtworks: Schema.Array(CustomSidebarArtwork).pipe( Schema.withDecodingDefault(Effect.succeed([])), ), + /** Which icon the desktop app wears; see AppIconSelection. */ + appIcon: AppIconSelection.pipe( + Schema.withDecodingDefault(Effect.succeed(DEFAULT_APP_ICON_SELECTION)), + ), + /** The account's own icons, synced the same way artwork is. */ + customAppIcons: Schema.Array(CustomAppIcon).pipe(Schema.withDecodingDefault(Effect.succeed([]))), // Legacy token-by-token assistant output. Deliberately a fresh key (was // `enableAssistantStreaming`): decoding drops the old key, so everyone, // including prior opt-ins, resets to the buffered default. @@ -1058,6 +1087,8 @@ export const ServerSettingsPatch = Schema.Struct({ environmentLabel: Schema.optionalKey(TrimmedString.check(Schema.isMaxLength(40))), sidebarArtwork: Schema.optionalKey(SidebarArtworkSelection), customSidebarArtworks: Schema.optionalKey(Schema.Array(CustomSidebarArtwork)), + appIcon: Schema.optionalKey(AppIconSelection), + customAppIcons: Schema.optionalKey(Schema.Array(CustomAppIcon)), enableLegacyTokenStreaming: Schema.optionalKey(Schema.Boolean), enableProviderUpdateChecks: Schema.optionalKey(Schema.Boolean), enableAgentBrowserAccess: Schema.optionalKey(Schema.Boolean), diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 2947f2bc82c8..023fa6efd611 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -894,6 +894,14 @@ export const DESKTOP_EXTRA_RESOURCES = [ from: "apps/desktop/prod-resources/t3-desktop-mcp", to: "t3-desktop-mcp", }, + { + // Alternate app icons the user can switch to at runtime. The bundle's own + // icon cannot change without breaking the signature, so the Dock icon is + // swapped from these instead. + from: "assets/munim", + to: "app-icons", + filter: ["munim-icon-*.png", "munim-macos-1024.png"], + }, ] as const; export interface MacPasskeySigningConfiguration {