diff --git a/apps/desktop/src/updates/DesktopUpdates.test.ts b/apps/desktop/src/updates/DesktopUpdates.test.ts index 887f754c81042..68002f826d421 100644 --- a/apps/desktop/src/updates/DesktopUpdates.test.ts +++ b/apps/desktop/src/updates/DesktopUpdates.test.ts @@ -932,6 +932,32 @@ describe("DesktopUpdates", () => { ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); }); + it.effect("keeps the current track's offers when a track switch fails to save", () => { + const harness = makeHarness({ + setUpdateChannelError: new DesktopAppSettings.DesktopSettingsWriteError({ + operation: "replace-settings-file", + path: "desktop-settings.json", + cause: new Error("rename failed"), + }), + }); + + return Effect.scoped( + Effect.gen(function* () { + const updates = yield* DesktopUpdates.DesktopUpdates; + yield* updates.configure; + const error = yield* updates + .setChannel("nightly", { minimumVersion: "1.2.5-nightly.20261028.1" }) + .pipe(Effect.flip); + assert.equal(error._tag, "DesktopUpdateChannelPersistenceError"); + assert.equal((yield* updates.getState).channel, "latest"); + + harness.emit("update-available", { version: "1.2.4" }); + yield* flushCallbacks; + assert.equal((yield* updates.getState).availableVersion, "1.2.4"); + }), + ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); + }); + it.effect("keeps a saved Stable track across a restart without offering an older release", () => { // A Nightly build whose switch to Stable did not finish before the app quit. const harness = makeHarness({ appVersion: "1.2.4-nightly.20261027.1", savedChannel: "latest" }); diff --git a/apps/desktop/src/updates/DesktopUpdates.ts b/apps/desktop/src/updates/DesktopUpdates.ts index 14860c238ab14..0f52ec249c2c4 100644 --- a/apps/desktop/src/updates/DesktopUpdates.ts +++ b/apps/desktop/src/updates/DesktopUpdates.ts @@ -1229,7 +1229,10 @@ export const make = Effect.gen(function* () { } return yield* Effect.gen(function* () { - yield* Ref.set(releaseFloorRef, Option.fromNullishOr(options?.minimumVersion)); + const previousFloor = yield* Ref.getAndSet( + releaseFloorRef, + Option.fromNullishOr(options?.minimumVersion), + ); const state = yield* Ref.get(updateStateRef); const retained = state.downloadedVersion ?? state.availableVersion; // A build from another track re-checks, so retrying a track switch finds an older release. @@ -1242,13 +1245,12 @@ export const make = Effect.gen(function* () { return state; } - yield* desktopSettings - .setUpdateChannel(nextChannel) - .pipe( - Effect.mapError( - (cause) => new DesktopUpdateChannelPersistenceError({ channel: nextChannel, cause }), - ), - ); + yield* desktopSettings.setUpdateChannel(nextChannel).pipe( + Effect.onError(() => Ref.set(releaseFloorRef, previousFloor)), + Effect.mapError( + (cause) => new DesktopUpdateChannelPersistenceError({ channel: nextChannel, cause }), + ), + ); const disabledReason = yield* resolveDisabledReason; yield* setState(yield* baseUpdateState(nextChannel, disabledReason));