authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-19 21:17:56-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-19 21:17:56-07:00
log93fd9721086d4325794830a29a7df0592d120683
treea3cc79870cd7682171dcf0a46b2f25f2eb16dd0d
parentdf1540f3ac8fd70877a83e91bd0be182ebf5022c
signature Signed by PGP key B5690EEEBB952194

[codex] Refactor desktop window and update Effect services (#3202)

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

4 files changed, 50 insertions(+), 52 deletions(-)

apps/desktop/src/updates/DesktopUpdates.ts+30-29
...@@ -7,7 +7,6 @@ import type {...@@ -7,7 +7,6 @@ import type {
7} from "@t3tools/contracts";7} from "@t3tools/contracts";
8import * as Cause from "effect/Cause";8import * as Cause from "effect/Cause";
9import * as Context from "effect/Context";9import * as Context from "effect/Context";
10import * as Data from "effect/Data";
11import * as DateTime from "effect/DateTime";10import * as DateTime from "effect/DateTime";
12import * as Duration from "effect/Duration";11import * as Duration from "effect/Duration";
13import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
...@@ -60,23 +59,26 @@ const decodeDownloadProgressInfo = Schema.decodeUnknownEffect(DownloadProgressIn...@@ -60,23 +59,26 @@ const decodeDownloadProgressInfo = Schema.decodeUnknownEffect(DownloadProgressIn
6059
61const currentIsoTimestamp = DateTime.now.pipe(Effect.map(DateTime.formatIso));60const currentIsoTimestamp = DateTime.now.pipe(Effect.map(DateTime.formatIso));
6261
63export class DesktopUpdateActionInProgressError extends Data.TaggedError(62export class DesktopUpdateActionInProgressError extends Schema.TaggedErrorClass<DesktopUpdateActionInProgressError>()(
64 "DesktopUpdateActionInProgressError",63 "DesktopUpdateActionInProgressError",
65)<{64 {
66 readonly action: "check" | "download" | "install";65 action: Schema.Literals(["check", "download", "install"]),
67}> {66 },
68 override get message() {67) {
68 override get message(): string {
69 return `Cannot change update tracks while an update ${this.action} action is in progress.`;69 return `Cannot change update tracks while an update ${this.action} action is in progress.`;
70 }70 }
71}71}
7272
73export class DesktopUpdatePersistenceError extends Data.TaggedError(73export class DesktopUpdatePersistenceError extends Schema.TaggedErrorClass<DesktopUpdatePersistenceError>()(
74 "DesktopUpdatePersistenceError",74 "DesktopUpdatePersistenceError",
75)<{75 {
76 readonly cause: DesktopAppSettings.DesktopSettingsWriteError;76 cause: Schema.Defect(),
77}> {77 },
78 override get message() {78) {
79 return "Failed to persist desktop update settings.";79 override get message(): string {
80 const detail = this.cause instanceof Error ? this.cause.message : String(this.cause);
81 return `Failed to persist desktop update settings: ${detail}`;
80 }82 }
81}83}
8284
...@@ -86,22 +88,21 @@ export type DesktopUpdateSetChannelError =...@@ -86,22 +88,21 @@ export type DesktopUpdateSetChannelError =
86 | DesktopUpdateActionInProgressError88 | DesktopUpdateActionInProgressError
87 | DesktopUpdatePersistenceError;89 | DesktopUpdatePersistenceError;
8890
89export interface DesktopUpdatesShape {91export class DesktopUpdates extends Context.Service<
90 readonly getState: Effect.Effect<DesktopUpdateState>;92 DesktopUpdates,
91 readonly emitState: Effect.Effect<void>;93 {
92 readonly disabledReason: Effect.Effect<Option.Option<string>>;94 readonly getState: Effect.Effect<DesktopUpdateState>;
93 readonly configure: Effect.Effect<void, DesktopUpdateConfigureError, Scope.Scope>;95 readonly emitState: Effect.Effect<void>;
94 readonly setChannel: (96 readonly disabledReason: Effect.Effect<Option.Option<string>>;
95 channel: DesktopUpdateChannel,97 readonly configure: Effect.Effect<void, DesktopUpdateConfigureError, Scope.Scope>;
96 ) => Effect.Effect<DesktopUpdateState, DesktopUpdateSetChannelError>;98 readonly setChannel: (
97 readonly check: (reason: string) => Effect.Effect<DesktopUpdateCheckResult>;99 channel: DesktopUpdateChannel,
98 readonly download: Effect.Effect<DesktopUpdateActionResult>;100 ) => Effect.Effect<DesktopUpdateState, DesktopUpdateSetChannelError>;
99 readonly install: Effect.Effect<DesktopUpdateActionResult>;101 readonly check: (reason: string) => Effect.Effect<DesktopUpdateCheckResult>;
100}102 readonly download: Effect.Effect<DesktopUpdateActionResult>;
101103 readonly install: Effect.Effect<DesktopUpdateActionResult>;
102export class DesktopUpdates extends Context.Service<DesktopUpdates, DesktopUpdatesShape>()(104 }
103 "@t3tools/desktop/updates/DesktopUpdates",105>()("@t3tools/desktop/updates/DesktopUpdates") {}
104) {}
105106
106const {107const {
107 logInfo: logUpdaterInfo,108 logInfo: logUpdaterInfo,
...@@ -185,7 +186,7 @@ function isArm64HostRunningIntelBuild(runtimeInfo: DesktopRuntimeInfo): boolean...@@ -185,7 +186,7 @@ function isArm64HostRunningIntelBuild(runtimeInfo: DesktopRuntimeInfo): boolean
185 return runtimeInfo.hostArch === "arm64" && runtimeInfo.appArch === "x64";186 return runtimeInfo.hostArch === "arm64" && runtimeInfo.appArch === "x64";
186}187}
187188
188const make = Effect.gen(function* () {189export const make = Effect.gen(function* () {
189 const config = yield* DesktopConfig.DesktopConfig;190 const config = yield* DesktopConfig.DesktopConfig;
190 const backendManager = yield* DesktopBackendManager.DesktopBackendManager;191 const backendManager = yield* DesktopBackendManager.DesktopBackendManager;
191 const desktopState = yield* DesktopState.DesktopState;192 const desktopState = yield* DesktopState.DesktopState;
apps/desktop/src/window/DesktopApplicationMenu.test.ts+2-2
...@@ -64,7 +64,7 @@ const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, {...@@ -64,7 +64,7 @@ const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, {
64 check: () => Effect.die("unexpected check"),64 check: () => Effect.die("unexpected check"),
65 download: Effect.die("unexpected download"),65 download: Effect.die("unexpected download"),
66 install: Effect.die("unexpected install"),66 install: Effect.die("unexpected install"),
67} satisfies DesktopUpdates.DesktopUpdatesShape);67} satisfies DesktopUpdates.DesktopUpdates["Service"]);
6868
69const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>69const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>
70 Layer.succeed(DesktopWindow.DesktopWindow, {70 Layer.succeed(DesktopWindow.DesktopWindow, {
...@@ -76,7 +76,7 @@ const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>...@@ -76,7 +76,7 @@ const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>
76 handleBackendReady: Effect.void,76 handleBackendReady: Effect.void,
77 dispatchMenuAction: (action) => Deferred.succeed(selectedAction, action).pipe(Effect.asVoid),77 dispatchMenuAction: (action) => Deferred.succeed(selectedAction, action).pipe(Effect.asVoid),
78 syncAppearance: Effect.void,78 syncAppearance: Effect.void,
79 } satisfies DesktopWindow.DesktopWindowShape);79 } satisfies DesktopWindow.DesktopWindow["Service"]);
8080
81const makeElectronMenuLayer = (81const makeElectronMenuLayer = (
82 applicationMenuTemplate: Deferred.Deferred<readonly Electron.MenuItemConstructorOptions[]>,82 applicationMenuTemplate: Deferred.Deferred<readonly Electron.MenuItemConstructorOptions[]>,
apps/desktop/src/window/DesktopApplicationMenu.ts+4-6
...@@ -14,13 +14,11 @@ import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";...@@ -14,13 +14,11 @@ import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
14import * as DesktopUpdates from "../updates/DesktopUpdates.ts";14import * as DesktopUpdates from "../updates/DesktopUpdates.ts";
15import * as DesktopWindow from "./DesktopWindow.ts";15import * as DesktopWindow from "./DesktopWindow.ts";
1616
17export interface DesktopApplicationMenuShape {
18 readonly configure: Effect.Effect<void>;
19}
20
21export class DesktopApplicationMenu extends Context.Service<17export class DesktopApplicationMenu extends Context.Service<
22 DesktopApplicationMenu,18 DesktopApplicationMenu,
23 DesktopApplicationMenuShape19 {
20 readonly configure: Effect.Effect<void>;
21 }
24>()("@t3tools/desktop/window/DesktopApplicationMenu") {}22>()("@t3tools/desktop/window/DesktopApplicationMenu") {}
2523
26type DesktopApplicationMenuRuntimeServices =24type DesktopApplicationMenuRuntimeServices =
...@@ -94,7 +92,7 @@ const handleCheckForUpdatesMenuClick: Effect.Effect<...@@ -94,7 +92,7 @@ const handleCheckForUpdatesMenuClick: Effect.Effect<
94 yield* checkForUpdatesFromMenu;92 yield* checkForUpdatesFromMenu;
95}).pipe(Effect.withSpan("desktop.menu.handleCheckForUpdatesClick"));93}).pipe(Effect.withSpan("desktop.menu.handleCheckForUpdatesClick"));
9694
97const make = Effect.gen(function* () {95export const make = Effect.gen(function* () {
98 const electronApp = yield* ElectronApp.ElectronApp;96 const electronApp = yield* ElectronApp.ElectronApp;
99 const electronMenu = yield* ElectronMenu.ElectronMenu;97 const electronMenu = yield* ElectronMenu.ElectronMenu;
100 const environment = yield* DesktopEnvironment.DesktopEnvironment;98 const environment = yield* DesktopEnvironment.DesktopEnvironment;
apps/desktop/src/window/DesktopWindow.ts+14-15
...@@ -42,20 +42,19 @@ export type DesktopWindowError =...@@ -42,20 +42,19 @@ export type DesktopWindowError =
42 | ElectronWindow.ElectronWindowCreateError42 | ElectronWindow.ElectronWindowCreateError
43 | PreviewManager.PreviewManagerError;43 | PreviewManager.PreviewManagerError;
4444
45export interface DesktopWindowShape {45export class DesktopWindow extends Context.Service<
46 readonly createMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;46 DesktopWindow,
47 readonly ensureMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;47 {
48 readonly revealOrCreateMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;48 readonly createMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
49 readonly activate: Effect.Effect<void, DesktopWindowError>;49 readonly ensureMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
50 readonly createMainIfBackendReady: Effect.Effect<void, DesktopWindowError>;50 readonly revealOrCreateMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
51 readonly handleBackendReady: Effect.Effect<void, DesktopWindowError>;51 readonly activate: Effect.Effect<void, DesktopWindowError>;
52 readonly dispatchMenuAction: (action: string) => Effect.Effect<void, DesktopWindowError>;52 readonly createMainIfBackendReady: Effect.Effect<void, DesktopWindowError>;
53 readonly syncAppearance: Effect.Effect<void>;53 readonly handleBackendReady: Effect.Effect<void, DesktopWindowError>;
54}54 readonly dispatchMenuAction: (action: string) => Effect.Effect<void, DesktopWindowError>;
5555 readonly syncAppearance: Effect.Effect<void>;
56export class DesktopWindow extends Context.Service<DesktopWindow, DesktopWindowShape>()(56 }
57 "@t3tools/desktop/window/DesktopWindow",57>()("@t3tools/desktop/window/DesktopWindow") {}
58) {}
5958
60const { logInfo: logWindowInfo, logWarning: logWindowWarning } =59const { logInfo: logWindowInfo, logWarning: logWindowWarning } =
61 DesktopObservability.makeComponentLogger("desktop-window");60 DesktopObservability.makeComponentLogger("desktop-window");
...@@ -143,7 +142,7 @@ function bindFirstRevealTrigger(...@@ -143,7 +142,7 @@ function bindFirstRevealTrigger(
143 }142 }
144}143}
145144
146const make = Effect.gen(function* () {145export const make = Effect.gen(function* () {
147 const environment = yield* DesktopEnvironment.DesktopEnvironment;146 const environment = yield* DesktopEnvironment.DesktopEnvironment;
148 const assets = yield* DesktopAssets.DesktopAssets;147 const assets = yield* DesktopAssets.DesktopAssets;
149 const electronMenu = yield* ElectronMenu.ElectronMenu;148 const electronMenu = yield* ElectronMenu.ElectronMenu;