From 42bf4d0ff6577ded5e1c50751fb05b493e14e932 Mon Sep 17 00:00:00 2001 From: Bilal Irfan Date: Fri, 6 Nov 2026 18:49:12 +0500 Subject: [PATCH 1/2] fix(web): switching update track works when Local environment is off Co-Authored-By: Claude Sonnet 5.5 --- .../sidebar/updateEverything.logic.test.ts | 14 ++++++++++++++ .../components/sidebar/updateEverything.logic.ts | 9 +++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index 58328dbc1eeeb..36e36fa066bd3 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -272,6 +272,20 @@ describe("localSwitchBlockReason", () => { expect( localSwitchBlockReason(nightly, stable, [{ connected: false, databaseMigration: 69 }], 1), ).toBe("its server is not connected, so its database cannot be checked"); + // The inventory counts a backend that has no client entry at all. + expect(localSwitchBlockReason(nightly, stable, [], 1)).toBe( + "its server is not connected, so its database cannot be checked", + ); + // With Local environment off the app hosts nothing, so the reinstall restarts no database. + expect(localSwitchBlockReason(nightly, stable, [], 0)).toBeNull(); + expect( + localSwitchBlockReason( + "0.1.3", + { channel: "nightly", version: nightly, databaseMigration: 69 }, + [], + 0, + ), + ).toBeNull(); expect(localSwitchBlockReason("0.1.3", stable, [], 1)).toBeNull(); }); }); diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index cbbd3baa7c776..f00e691a40c4f 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -176,7 +176,8 @@ function channelSwitchBlockReason( /** This app's reinstall restarts every server it hosts, so each of their databases must allow the * switch. `backendCount` is the desktop's own inventory: a backend still starting has no client - * entry yet. A cached config can outlive its connection, so only a connected server's head counts. */ + * entry yet. A cached config can outlive its connection, so only a connected server's head counts. + * An app that hosts nothing, such as one with Local environment off, restarts no database. */ export function localSwitchBlockReason( appVersion: string, release: ChannelSwitchRelease, @@ -187,11 +188,7 @@ export function localSwitchBlockReason( backendCount: number, ): string | null { if (!crossesTrack(appVersion, release)) return null; - if ( - hosted.length === 0 || - hosted.length < backendCount || - hosted.some((server) => !server.connected) - ) { + if (hosted.length < backendCount || hosted.some((server) => !server.connected)) { return "its server is not connected, so its database cannot be checked"; } return ( From bc0732259d54c57951d4fb09e52c447529a4a0c4 Mon Sep 17 00:00:00 2001 From: Bilal Irfan Date: Fri, 6 Nov 2026 19:38:54 +0500 Subject: [PATCH 2/2] fix(web): track switch still checks the database when Local environment is on but its backend is retrying --- .../settings/UpdateTrackSwitchDialog.tsx | 2 + .../sidebar/updateEverything.logic.test.ts | 37 +++++++++++++------ .../sidebar/updateEverything.logic.ts | 14 ++++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/apps/web/src/components/settings/UpdateTrackSwitchDialog.tsx b/apps/web/src/components/settings/UpdateTrackSwitchDialog.tsx index f5db42c452c14..ce36f8ccd7e5c 100644 --- a/apps/web/src/components/settings/UpdateTrackSwitchDialog.tsx +++ b/apps/web/src/components/settings/UpdateTrackSwitchDialog.tsx @@ -7,6 +7,7 @@ import { useState } from "react"; import { AsyncResult } from "effect/reactivity"; import { APP_VERSION } from "~/branding"; +import { isLocalEnvironmentDisabled } from "~/localEnvironment"; import { useDesktopUpdateState } from "~/state/desktopUpdate"; import { useEnvironments } from "~/state/environments"; import { npmReleaseAtom, npmReleaseFailureMessage } from "~/state/npmRelease"; @@ -91,6 +92,7 @@ function SwitchPlan({ databaseMigration: environment.serverConfig?.environment.databaseMigration, })), window.desktopBridge?.getLocalEnvironmentBootstraps().length ?? 0, + !isLocalEnvironmentDisabled(), ); const run = async () => { diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index 36e36fa066bd3..7d4b50118527d 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -252,9 +252,10 @@ describe("localSwitchBlockReason", () => { it("checks every server this app hosts, and only while connected", () => { const stable = { channel: "latest" as const, version: "0.1.3", databaseMigration: 69 }; const nightly = "0.1.4-nightly.20261027.3796"; + const notConnected = "its server is not connected, so its database cannot be checked"; const windows = { connected: true, databaseMigration: 69 }; - expect(localSwitchBlockReason(nightly, stable, [windows], 1)).toBeNull(); + expect(localSwitchBlockReason(nightly, stable, [windows], 1, true)).toBeNull(); // A WSL server's newer migration blocks the reinstall that would restart it. expect( localSwitchBlockReason( @@ -262,31 +263,45 @@ describe("localSwitchBlockReason", () => { stable, [windows, { connected: true, databaseMigration: 72 }], 2, + true, ), ).toContain("does not support yet"); // A WSL backend still starting has no client entry, so its database is unchecked. - expect(localSwitchBlockReason(nightly, stable, [windows], 2)).toBe( - "its server is not connected, so its database cannot be checked", - ); + expect(localSwitchBlockReason(nightly, stable, [windows], 2, true)).toBe(notConnected); // A cached head from a disconnected server proves nothing. expect( - localSwitchBlockReason(nightly, stable, [{ connected: false, databaseMigration: 69 }], 1), - ).toBe("its server is not connected, so its database cannot be checked"); - // The inventory counts a backend that has no client entry at all. - expect(localSwitchBlockReason(nightly, stable, [], 1)).toBe( + localSwitchBlockReason( + nightly, + stable, + [{ connected: false, databaseMigration: 69 }], + 1, + true, + ), + ).toBe(notConnected); + expect(localSwitchBlockReason(nightly, stable, [], 1, true)).toBe(notConnected); + expect(localSwitchBlockReason("0.1.3", stable, [], 1, true)).toBeNull(); + }); + + it("expects a server only while Local environment is enabled", () => { + const stable = { channel: "latest" as const, version: "0.1.3", databaseMigration: 69 }; + const nightly = "0.1.4-nightly.20261027.3796"; + + // The desktop leaves its primary backend out of the inventory while a preflight retry is + // pending, so an enabled app can show no backend and no client entry yet still own a database. + expect(localSwitchBlockReason(nightly, stable, [], 0, true)).toBe( "its server is not connected, so its database cannot be checked", ); - // With Local environment off the app hosts nothing, so the reinstall restarts no database. - expect(localSwitchBlockReason(nightly, stable, [], 0)).toBeNull(); + // Local environment off: the app hosts nothing, so the reinstall restarts no database. + expect(localSwitchBlockReason(nightly, stable, [], 0, false)).toBeNull(); expect( localSwitchBlockReason( "0.1.3", { channel: "nightly", version: nightly, databaseMigration: 69 }, [], 0, + false, ), ).toBeNull(); - expect(localSwitchBlockReason("0.1.3", stable, [], 1)).toBeNull(); }); }); diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index f00e691a40c4f..1f91ebf500d5f 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -174,10 +174,10 @@ function channelSwitchBlockReason( : null; } -/** This app's reinstall restarts every server it hosts, so each of their databases must allow the - * switch. `backendCount` is the desktop's own inventory: a backend still starting has no client - * entry yet. A cached config can outlive its connection, so only a connected server's head counts. - * An app that hosts nothing, such as one with Local environment off, restarts no database. */ +/** This app's reinstall restarts every server it hosts, so each of their databases must allow it. + * `backendCount` is the desktop's inventory, which omits the primary backend during a preflight + * retry; with Local environment on, empty lists still mean an unchecked server. Only a connected + * server's head counts, since a cached config can outlive its connection. */ export function localSwitchBlockReason( appVersion: string, release: ChannelSwitchRelease, @@ -186,9 +186,13 @@ export function localSwitchBlockReason( readonly databaseMigration: number | undefined; }>, backendCount: number, + localEnvironmentEnabled: boolean, ): string | null { if (!crossesTrack(appVersion, release)) return null; - if (hosted.length < backendCount || hosted.some((server) => !server.connected)) { + if ( + hosted.length < Math.max(backendCount, localEnvironmentEnabled ? 1 : 0) || + hosted.some((server) => !server.connected) + ) { return "its server is not connected, so its database cannot be checked"; } return (