From c1ea12332459aa9243b11e8b9279bfdd13919a26 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 20 Jun 2026 04:15:21 -0700 Subject: [PATCH] [codex] structure Electron theme source errors Co-authored-by: codex --- .../src/electron/ElectronTheme.test.ts | 22 +++++++++++++++ apps/desktop/src/electron/ElectronTheme.ts | 27 +++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/electron/ElectronTheme.test.ts b/apps/desktop/src/electron/ElectronTheme.test.ts index 0ba7482aace..4b81943eff2 100644 --- a/apps/desktop/src/electron/ElectronTheme.test.ts +++ b/apps/desktop/src/electron/ElectronTheme.test.ts @@ -8,6 +8,7 @@ const { onMock, removeListenerMock, themeState } = vi.hoisted(() => ({ themeState: { shouldUseDarkColors: true, themeSource: "system", + setSourceError: null as unknown, }, })); @@ -17,6 +18,9 @@ vi.mock("electron", () => ({ return themeState.shouldUseDarkColors; }, set themeSource(value: string) { + if (themeState.setSourceError !== null) { + throw themeState.setSourceError; + } themeState.themeSource = value; }, on: onMock, @@ -32,6 +36,7 @@ describe("ElectronTheme", () => { removeListenerMock.mockClear(); themeState.shouldUseDarkColors = true; themeState.themeSource = "system"; + themeState.setSourceError = null; }); it.effect("scopes native theme update listeners", () => @@ -49,4 +54,21 @@ describe("ElectronTheme", () => { assert.deepEqual(removeListenerMock.mock.calls, [["updated", listener]]); }).pipe(Effect.provide(ElectronTheme.layer)), ); + + it.effect("preserves the requested source and cause when setting the theme fails", () => + Effect.gen(function* () { + const cause = new Error("theme source failed"); + themeState.setSourceError = cause; + const electronTheme = yield* ElectronTheme.ElectronTheme; + + const error = yield* Effect.flip(electronTheme.setSource("dark")); + + assert.instanceOf(error, ElectronTheme.ElectronThemeSetSourceError); + assert.isTrue(ElectronTheme.isElectronThemeSetSourceError(error)); + assert.strictEqual(error.source, "dark"); + assert.strictEqual(error.cause, cause); + assert.include(error.message, "dark"); + assert.notInclude(error.message, cause.message); + }).pipe(Effect.provide(ElectronTheme.layer)), + ); }); diff --git a/apps/desktop/src/electron/ElectronTheme.ts b/apps/desktop/src/electron/ElectronTheme.ts index ef99a31067a..ef47e3d0954 100644 --- a/apps/desktop/src/electron/ElectronTheme.ts +++ b/apps/desktop/src/electron/ElectronTheme.ts @@ -1,16 +1,31 @@ -import type { DesktopTheme } from "@t3tools/contracts"; +import { DesktopThemeSchema, type DesktopTheme } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Electron from "electron"; +export class ElectronThemeSetSourceError extends Schema.TaggedErrorClass()( + "ElectronThemeSetSourceError", + { + source: DesktopThemeSchema, + cause: Schema.Defect(), + }, +) { + override get message(): string { + return `Failed to set the Electron theme source to ${this.source}.`; + } +} + +export const isElectronThemeSetSourceError = Schema.is(ElectronThemeSetSourceError); + export class ElectronTheme extends Context.Service< ElectronTheme, { readonly shouldUseDarkColors: Effect.Effect; - readonly setSource: (theme: DesktopTheme) => Effect.Effect; + readonly setSource: (theme: DesktopTheme) => Effect.Effect; readonly onUpdated: (listener: () => void) => Effect.Effect; } >()("@t3tools/desktop/electron/ElectronTheme") {} @@ -18,9 +33,11 @@ export class ElectronTheme extends Context.Service< export const make = ElectronTheme.of({ shouldUseDarkColors: Effect.sync(() => Electron.nativeTheme.shouldUseDarkColors), setSource: (theme) => - Effect.suspend(() => { - Electron.nativeTheme.themeSource = theme; - return Effect.void; + Effect.try({ + try: () => { + Electron.nativeTheme.themeSource = theme; + }, + catch: (cause) => new ElectronThemeSetSourceError({ source: theme, cause }), }), onUpdated: (listener) => Effect.acquireRelease(