| author | |
| committer | |
| log | fccecd8749056d4f811962a1523229500e7a4e72 |
| tree | b4280023f149d091d854c0b2b54b9912237755f3 |
| parent | 30a084c463acb7b8b75550a117b9bd82e98c5ac4 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 71 insertions(+), 13 deletions(-)
apps/desktop/src/app/DesktopDetachedActionErrors.test.ts created+37| ... | ... | @@ -0,0 +1,37 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | |
| 2 | import * as Cause from "effect/Cause"; | |
| 3 | ||
| 4 | import { DesktopLifecycleRelaunchError } from "./DesktopLifecycle.ts"; | |
| 5 | import { DesktopApplicationMenuActionError } from "../window/DesktopApplicationMenu.ts"; | |
| 6 | ||
| 7 | describe("desktop detached action errors", () => { | |
| 8 | it("preserves the complete relaunch failure cause and reason", () => { | |
| 9 | const cause = Cause.combine( | |
| 10 | Cause.fail(new Error("shutdown failed")), | |
| 11 | Cause.die(new Error("relaunch defect")), | |
| 12 | ); | |
| 13 | const error = new DesktopLifecycleRelaunchError({ | |
| 14 | reason: "apply update", | |
| 15 | cause, | |
| 16 | }); | |
| 17 | ||
| 18 | assert.strictEqual(error.cause, cause); | |
| 19 | assert.equal(error.reason, "apply update"); | |
| 20 | assert.equal(error.message, 'Desktop relaunch failed for reason "apply update".'); | |
| 21 | }); | |
| 22 | ||
| 23 | it("preserves the complete menu action failure cause and action", () => { | |
| 24 | const cause = Cause.combine( | |
| 25 | Cause.fail(new Error("window unavailable")), | |
| 26 | Cause.die(new Error("dispatch defect")), | |
| 27 | ); | |
| 28 | const error = new DesktopApplicationMenuActionError({ | |
| 29 | action: "open-settings", | |
| 30 | cause, | |
| 31 | }); | |
| 32 | ||
| 33 | assert.strictEqual(error.cause, cause); | |
| 34 | assert.equal(error.action, "open-settings"); | |
| 35 | assert.equal(error.message, 'Desktop menu action "open-settings" failed.'); | |
| 36 | }); | |
| 37 | }); |
apps/desktop/src/app/DesktopLifecycle.ts+17-6| ... | ... | @@ -1,8 +1,8 @@ |
| 1 | import * as Cause from "effect/Cause"; | |
| 2 | 1 | import * as Context from "effect/Context"; |
| 3 | 2 | import * as Effect from "effect/Effect"; |
| 4 | 3 | import * as Layer from "effect/Layer"; |
| 5 | 4 | import * as Ref from "effect/Ref"; |
| 5 | import * as Schema from "effect/Schema"; | |
| 6 | 6 | import * as Scope from "effect/Scope"; |
| 7 | 7 | |
| 8 | 8 | import type * as Electron from "electron"; |
| ... | ... | @@ -15,6 +15,18 @@ import * as ElectronTheme from "../electron/ElectronTheme.ts"; |
| 15 | 15 | import * as DesktopState from "./DesktopState.ts"; |
| 16 | 16 | import * as DesktopWindow from "../window/DesktopWindow.ts"; |
| 17 | 17 | |
| 18 | export class DesktopLifecycleRelaunchError extends Schema.TaggedErrorClass<DesktopLifecycleRelaunchError>()( | |
| 19 | "DesktopLifecycleRelaunchError", | |
| 20 | { | |
| 21 | reason: Schema.String, | |
| 22 | cause: Schema.Defect(), | |
| 23 | }, | |
| 24 | ) { | |
| 25 | override get message(): string { | |
| 26 | return `Desktop relaunch failed for reason "${this.reason}".`; | |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 18 | 30 | export type DesktopLifecycleRuntimeServices = |
| 19 | 31 | | DesktopEnvironment.DesktopEnvironment |
| 20 | 32 | | DesktopShutdown.DesktopShutdown |
| ... | ... | @@ -142,11 +154,10 @@ export const make = DesktopLifecycle.of({ |
| 142 | 154 | }); |
| 143 | 155 | yield* electronApp.exit(0); |
| 144 | 156 | }).pipe( |
| 145 | Effect.catchCause((cause) => | |
| 146 | logLifecycleError("desktop relaunch failed", { | |
| 147 | cause: Cause.pretty(cause), | |
| 148 | }), | |
| 149 | ), | |
| 157 | Effect.catchCause((cause) => { | |
| 158 | const error = new DesktopLifecycleRelaunchError({ reason, cause }); | |
| 159 | return logLifecycleError(error.message, { error }); | |
| 160 | }), | |
| 150 | 161 | Effect.forkDetach, |
| 151 | 162 | Effect.asVoid, |
| 152 | 163 | ); |
apps/desktop/src/window/DesktopApplicationMenu.ts+17-7| ... | ... | @@ -1,8 +1,8 @@ |
| 1 | import * as Cause from "effect/Cause"; | |
| 2 | 1 | import * as Context from "effect/Context"; |
| 3 | 2 | import * as Effect from "effect/Effect"; |
| 4 | 3 | import * as Layer from "effect/Layer"; |
| 5 | 4 | import * as Option from "effect/Option"; |
| 5 | import * as Schema from "effect/Schema"; | |
| 6 | 6 | |
| 7 | 7 | import type * as Electron from "electron"; |
| 8 | 8 | |
| ... | ... | @@ -14,6 +14,18 @@ import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; |
| 14 | 14 | import * as DesktopUpdates from "../updates/DesktopUpdates.ts"; |
| 15 | 15 | import * as DesktopWindow from "./DesktopWindow.ts"; |
| 16 | 16 | |
| 17 | export class DesktopApplicationMenuActionError extends Schema.TaggedErrorClass<DesktopApplicationMenuActionError>()( | |
| 18 | "DesktopApplicationMenuActionError", | |
| 19 | { | |
| 20 | action: Schema.String, | |
| 21 | cause: Schema.Defect(), | |
| 22 | }, | |
| 23 | ) { | |
| 24 | override get message(): string { | |
| 25 | return `Desktop menu action "${this.action}" failed.`; | |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 17 | 29 | export class DesktopApplicationMenu extends Context.Service< |
| 18 | 30 | DesktopApplicationMenu, |
| 19 | 31 | { |
| ... | ... | @@ -100,12 +112,10 @@ export const make = Effect.gen(function* () { |
| 100 | 112 | effect.pipe( |
| 101 | 113 | Effect.annotateLogs({ action }), |
| 102 | 114 | Effect.withSpan("desktop.menu.action"), |
| 103 | Effect.catchCause((cause) => | |
| 104 | logMenuError("desktop menu action failed", { | |
| 105 | action, | |
| 106 | cause: Cause.pretty(cause), | |
| 107 | }), | |
| 108 | ), | |
| 115 | Effect.catchCause((cause) => { | |
| 116 | const error = new DesktopApplicationMenuActionError({ action, cause }); | |
| 117 | return logMenuError(error.message, { error }); | |
| 118 | }), | |
| 109 | 119 | ), |
| 110 | 120 | ); |
| 111 | 121 | }; |