authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 11:02:37-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 11:02:37-07:00
log8331511b972242301bdaec328148a88b9b6cfa6c
tree98b8ee7b5a36cbc545c4e51ffd338eae43383285
parent08650a7424f0f4d28c36d50938c0027e5b4cafff
signature Signed by PGP key B5690EEEBB952194

[codex] structure Electron theme source errors (#3294)

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 44 insertions(+), 5 deletions(-)

apps/desktop/src/electron/ElectronTheme.test.ts+22
......@@ -8,6 +8,7 @@ const { onMock, removeListenerMock, themeState } = vi.hoisted(() => ({
88 themeState: {
99 shouldUseDarkColors: true,
1010 themeSource: "system",
11 setSourceError: null as unknown,
1112 },
1213}));
1314
......@@ -17,6 +18,9 @@ vi.mock("electron", () => ({
1718 return themeState.shouldUseDarkColors;
1819 },
1920 set themeSource(value: string) {
21 if (themeState.setSourceError !== null) {
22 throw themeState.setSourceError;
23 }
2024 themeState.themeSource = value;
2125 },
2226 on: onMock,
......@@ -32,6 +36,7 @@ describe("ElectronTheme", () => {
3236 removeListenerMock.mockClear();
3337 themeState.shouldUseDarkColors = true;
3438 themeState.themeSource = "system";
39 themeState.setSourceError = null;
3540 });
3641
3742 it.effect("scopes native theme update listeners", () =>
......@@ -49,4 +54,21 @@ describe("ElectronTheme", () => {
4954 assert.deepEqual(removeListenerMock.mock.calls, [["updated", listener]]);
5055 }).pipe(Effect.provide(ElectronTheme.layer)),
5156 );
57
58 it.effect("preserves the requested source and cause when setting the theme fails", () =>
59 Effect.gen(function* () {
60 const cause = new Error("theme source failed");
61 themeState.setSourceError = cause;
62 const electronTheme = yield* ElectronTheme.ElectronTheme;
63
64 const error = yield* Effect.flip(electronTheme.setSource("dark"));
65
66 assert.instanceOf(error, ElectronTheme.ElectronThemeSetSourceError);
67 assert.isTrue(ElectronTheme.isElectronThemeSetSourceError(error));
68 assert.strictEqual(error.source, "dark");
69 assert.strictEqual(error.cause, cause);
70 assert.include(error.message, "dark");
71 assert.notInclude(error.message, cause.message);
72 }).pipe(Effect.provide(ElectronTheme.layer)),
73 );
5274});
apps/desktop/src/electron/ElectronTheme.ts+22-5
......@@ -1,16 +1,31 @@
1import type { DesktopTheme } from "@t3tools/contracts";
1import { DesktopThemeSchema, type DesktopTheme } from "@t3tools/contracts";
22import * as Context from "effect/Context";
33import * as Effect from "effect/Effect";
44import * as Layer from "effect/Layer";
5import * as Schema from "effect/Schema";
56import * as Scope from "effect/Scope";
67
78import * as Electron from "electron";
89
10export class ElectronThemeSetSourceError extends Schema.TaggedErrorClass<ElectronThemeSetSourceError>()(
11 "ElectronThemeSetSourceError",
12 {
13 source: DesktopThemeSchema,
14 cause: Schema.Defect(),
15 },
16) {
17 override get message(): string {
18 return `Failed to set the Electron theme source to ${this.source}.`;
19 }
20}
21
22export const isElectronThemeSetSourceError = Schema.is(ElectronThemeSetSourceError);
23
924export class ElectronTheme extends Context.Service<
1025 ElectronTheme,
1126 {
1227 readonly shouldUseDarkColors: Effect.Effect<boolean>;
13 readonly setSource: (theme: DesktopTheme) => Effect.Effect<void>;
28 readonly setSource: (theme: DesktopTheme) => Effect.Effect<void, ElectronThemeSetSourceError>;
1429 readonly onUpdated: (listener: () => void) => Effect.Effect<void, never, Scope.Scope>;
1530 }
1631>()("@t3tools/desktop/electron/ElectronTheme") {}
......@@ -18,9 +33,11 @@ export class ElectronTheme extends Context.Service<
1833export const make = ElectronTheme.of({
1934 shouldUseDarkColors: Effect.sync(() => Electron.nativeTheme.shouldUseDarkColors),
2035 setSource: (theme) =>
21 Effect.suspend(() => {
22 Electron.nativeTheme.themeSource = theme;
23 return Effect.void;
36 Effect.try({
37 try: () => {
38 Electron.nativeTheme.themeSource = theme;
39 },
40 catch: (cause) => new ElectronThemeSetSourceError({ source: theme, cause }),
2441 }),
2542 onUpdated: (listener) =>
2643 Effect.acquireRelease(