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 {
77} from "@t3tools/contracts";
88import * as Cause from "effect/Cause";
99import * as Context from "effect/Context";
10import * as Data from "effect/Data";
1110import * as DateTime from "effect/DateTime";
1211import * as Duration from "effect/Duration";
1312import * as Effect from "effect/Effect";
......@@ -60,23 +59,26 @@ const decodeDownloadProgressInfo = Schema.decodeUnknownEffect(DownloadProgressIn
6059
6160const currentIsoTimestamp = DateTime.now.pipe(Effect.map(DateTime.formatIso));
6261
63export class DesktopUpdateActionInProgressError extends Data.TaggedError(
62export class DesktopUpdateActionInProgressError extends Schema.TaggedErrorClass<DesktopUpdateActionInProgressError>()(
6463 "DesktopUpdateActionInProgressError",
65)<{
66 readonly action: "check" | "download" | "install";
67}> {
68 override get message() {
64 {
65 action: Schema.Literals(["check", "download", "install"]),
66 },
67) {
68 override get message(): string {
6969 return `Cannot change update tracks while an update ${this.action} action is in progress.`;
7070 }
7171}
7272
73export class DesktopUpdatePersistenceError extends Data.TaggedError(
73export class DesktopUpdatePersistenceError extends Schema.TaggedErrorClass<DesktopUpdatePersistenceError>()(
7474 "DesktopUpdatePersistenceError",
75)<{
76 readonly cause: DesktopAppSettings.DesktopSettingsWriteError;
77}> {
78 override get message() {
79 return "Failed to persist desktop update settings.";
75 {
76 cause: Schema.Defect(),
77 },
78) {
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}`;
8082 }
8183}
8284
......@@ -86,22 +88,21 @@ export type DesktopUpdateSetChannelError =
8688 | DesktopUpdateActionInProgressError
8789 | DesktopUpdatePersistenceError;
8890
89export interface DesktopUpdatesShape {
90 readonly getState: Effect.Effect<DesktopUpdateState>;
91 readonly emitState: Effect.Effect<void>;
92 readonly disabledReason: Effect.Effect<Option.Option<string>>;
93 readonly configure: Effect.Effect<void, DesktopUpdateConfigureError, Scope.Scope>;
94 readonly setChannel: (
95 channel: DesktopUpdateChannel,
96 ) => Effect.Effect<DesktopUpdateState, DesktopUpdateSetChannelError>;
97 readonly check: (reason: string) => Effect.Effect<DesktopUpdateCheckResult>;
98 readonly download: Effect.Effect<DesktopUpdateActionResult>;
99 readonly install: Effect.Effect<DesktopUpdateActionResult>;
100}
101
102export class DesktopUpdates extends Context.Service<DesktopUpdates, DesktopUpdatesShape>()(
103 "@t3tools/desktop/updates/DesktopUpdates",
104) {}
91export class DesktopUpdates extends Context.Service<
92 DesktopUpdates,
93 {
94 readonly getState: Effect.Effect<DesktopUpdateState>;
95 readonly emitState: Effect.Effect<void>;
96 readonly disabledReason: Effect.Effect<Option.Option<string>>;
97 readonly configure: Effect.Effect<void, DesktopUpdateConfigureError, Scope.Scope>;
98 readonly setChannel: (
99 channel: DesktopUpdateChannel,
100 ) => Effect.Effect<DesktopUpdateState, DesktopUpdateSetChannelError>;
101 readonly check: (reason: string) => Effect.Effect<DesktopUpdateCheckResult>;
102 readonly download: Effect.Effect<DesktopUpdateActionResult>;
103 readonly install: Effect.Effect<DesktopUpdateActionResult>;
104 }
105>()("@t3tools/desktop/updates/DesktopUpdates") {}
105106
106107const {
107108 logInfo: logUpdaterInfo,
......@@ -185,7 +186,7 @@ function isArm64HostRunningIntelBuild(runtimeInfo: DesktopRuntimeInfo): boolean
185186 return runtimeInfo.hostArch === "arm64" && runtimeInfo.appArch === "x64";
186187}
187188
188const make = Effect.gen(function* () {
189export const make = Effect.gen(function* () {
189190 const config = yield* DesktopConfig.DesktopConfig;
190191 const backendManager = yield* DesktopBackendManager.DesktopBackendManager;
191192 const desktopState = yield* DesktopState.DesktopState;
apps/desktop/src/window/DesktopApplicationMenu.test.ts+2-2
......@@ -64,7 +64,7 @@ const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, {
6464 check: () => Effect.die("unexpected check"),
6565 download: Effect.die("unexpected download"),
6666 install: Effect.die("unexpected install"),
67} satisfies DesktopUpdates.DesktopUpdatesShape);
67} satisfies DesktopUpdates.DesktopUpdates["Service"]);
6868
6969const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>
7070 Layer.succeed(DesktopWindow.DesktopWindow, {
......@@ -76,7 +76,7 @@ const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>
7676 handleBackendReady: Effect.void,
7777 dispatchMenuAction: (action) => Deferred.succeed(selectedAction, action).pipe(Effect.asVoid),
7878 syncAppearance: Effect.void,
79 } satisfies DesktopWindow.DesktopWindowShape);
79 } satisfies DesktopWindow.DesktopWindow["Service"]);
8080
8181const makeElectronMenuLayer = (
8282 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";
1414import * as DesktopUpdates from "../updates/DesktopUpdates.ts";
1515import * as DesktopWindow from "./DesktopWindow.ts";
1616
17export interface DesktopApplicationMenuShape {
18 readonly configure: Effect.Effect<void>;
19}
20
2117export class DesktopApplicationMenu extends Context.Service<
2218 DesktopApplicationMenu,
23 DesktopApplicationMenuShape
19 {
20 readonly configure: Effect.Effect<void>;
21 }
2422>()("@t3tools/desktop/window/DesktopApplicationMenu") {}
2523
2624type DesktopApplicationMenuRuntimeServices =
......@@ -94,7 +92,7 @@ const handleCheckForUpdatesMenuClick: Effect.Effect<
9492 yield* checkForUpdatesFromMenu;
9593}).pipe(Effect.withSpan("desktop.menu.handleCheckForUpdatesClick"));
9694
97const make = Effect.gen(function* () {
95export const make = Effect.gen(function* () {
9896 const electronApp = yield* ElectronApp.ElectronApp;
9997 const electronMenu = yield* ElectronMenu.ElectronMenu;
10098 const environment = yield* DesktopEnvironment.DesktopEnvironment;
apps/desktop/src/window/DesktopWindow.ts+14-15
......@@ -42,20 +42,19 @@ export type DesktopWindowError =
4242 | ElectronWindow.ElectronWindowCreateError
4343 | PreviewManager.PreviewManagerError;
4444
45export interface DesktopWindowShape {
46 readonly createMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
47 readonly ensureMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
48 readonly revealOrCreateMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
49 readonly activate: Effect.Effect<void, DesktopWindowError>;
50 readonly createMainIfBackendReady: Effect.Effect<void, DesktopWindowError>;
51 readonly handleBackendReady: Effect.Effect<void, DesktopWindowError>;
52 readonly dispatchMenuAction: (action: string) => Effect.Effect<void, DesktopWindowError>;
53 readonly syncAppearance: Effect.Effect<void>;
54}
55
56export class DesktopWindow extends Context.Service<DesktopWindow, DesktopWindowShape>()(
57 "@t3tools/desktop/window/DesktopWindow",
58) {}
45export class DesktopWindow extends Context.Service<
46 DesktopWindow,
47 {
48 readonly createMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
49 readonly ensureMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
50 readonly revealOrCreateMain: Effect.Effect<Electron.BrowserWindow, DesktopWindowError>;
51 readonly activate: Effect.Effect<void, DesktopWindowError>;
52 readonly createMainIfBackendReady: Effect.Effect<void, DesktopWindowError>;
53 readonly handleBackendReady: Effect.Effect<void, DesktopWindowError>;
54 readonly dispatchMenuAction: (action: string) => Effect.Effect<void, DesktopWindowError>;
55 readonly syncAppearance: Effect.Effect<void>;
56 }
57>()("@t3tools/desktop/window/DesktopWindow") {}
5958
6059const { logInfo: logWindowInfo, logWarning: logWindowWarning } =
6160 DesktopObservability.makeComponentLogger("desktop-window");
......@@ -143,7 +142,7 @@ function bindFirstRevealTrigger(
143142 }
144143}
145144
146const make = Effect.gen(function* () {
145export const make = Effect.gen(function* () {
147146 const environment = yield* DesktopEnvironment.DesktopEnvironment;
148147 const assets = yield* DesktopAssets.DesktopAssets;
149148 const electronMenu = yield* ElectronMenu.ElectronMenu;