From bfb35ad23d9188450fcfa441b2f84e3cdaa8791c Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 6 Nov 2026 13:43:02 +0000 Subject: [PATCH 1/3] test(web): reproduce #32018 track switch blocked with Local environment off An app with Local environment off hosts no server, so crossing tracks should not wait on a database check. With Local environment on, a primary backend that is still starting stays blocked. Co-Authored-By: Claude Opus 5.5 --- .../sidebar/updateEverything.logic.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/web/src/components/sidebar/updateEverything.logic.test.ts b/apps/web/src/components/sidebar/updateEverything.logic.test.ts index 58328dbc1eeeb..fe09bfdc8fb1b 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -274,6 +274,21 @@ describe("localSwitchBlockReason", () => { ).toBe("its server is not connected, so its database cannot be checked"); expect(localSwitchBlockReason("0.1.3", stable, [], 1)).toBeNull(); }); + + it("does not block an app with Local environment off, which hosts no server", () => { + const nightly = { + channel: "nightly" as const, + version: "0.1.8-nightly.20261029.1", + databaseMigration: 72, + }; + const stable = "0.1.7"; + + expect(localSwitchBlockReason(stable, nightly, [], 0, false)).toBeNull(); + // With Local environment on, a primary backend still starting has no entry or bootstrap yet. + expect(localSwitchBlockReason(stable, nightly, [], 0, true)).toBe( + "its server is not connected, so its database cannot be checked", + ); + }); }); describe("collectRestartingMachines", () => { From 5c9c228b90792396da99713d3d6a5b75cd4320e4 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 6 Nov 2026 13:54:40 +0000 Subject: [PATCH 2/3] fix(web): track switch no longer blocked when Local environment is off With Local environment off the desktop app hosts no server, but the track switch dialog still refused with "its server is not connected, so its database cannot be checked". The reinstall restarts no server then, so skip the local database check. Local environment on with no server yet (the primary backend still starting) stays blocked. Co-Authored-By: Claude Opus 5.5 --- .../settings/UpdateTrackSwitchDialog.tsx | 2 ++ .../sidebar/updateEverything.logic.test.ts | 15 +++++++++++---- .../components/sidebar/updateEverything.logic.ts | 6 ++++-- 3 files changed, 17 insertions(+), 6 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 fe09bfdc8fb1b..8018410cb0181 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.test.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.test.ts @@ -254,7 +254,7 @@ describe("localSwitchBlockReason", () => { const nightly = "0.1.4-nightly.20261027.3796"; 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,17 +262,24 @@ 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( + expect(localSwitchBlockReason(nightly, stable, [windows], 2, true)).toBe( "its server is not connected, so its database cannot be checked", ); // A cached head from a disconnected server proves nothing. expect( - localSwitchBlockReason(nightly, stable, [{ connected: false, databaseMigration: 69 }], 1), + localSwitchBlockReason( + nightly, + stable, + [{ connected: false, databaseMigration: 69 }], + 1, + true, + ), ).toBe("its server is not connected, so its database cannot be checked"); - expect(localSwitchBlockReason("0.1.3", stable, [], 1)).toBeNull(); + expect(localSwitchBlockReason("0.1.3", stable, [], 1, true)).toBeNull(); }); it("does not block an app with Local environment off, which hosts no server", () => { diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index cbbd3baa7c776..fce04e396167a 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. + * With Local environment off this app hosts no server, so there is no database to check. */ export function localSwitchBlockReason( appVersion: string, release: ChannelSwitchRelease, @@ -185,8 +186,9 @@ export function localSwitchBlockReason( readonly databaseMigration: number | undefined; }>, backendCount: number, + localEnvironmentEnabled: boolean, ): string | null { - if (!crossesTrack(appVersion, release)) return null; + if (!localEnvironmentEnabled || !crossesTrack(appVersion, release)) return null; if ( hosted.length === 0 || hosted.length < backendCount || From 768cd92d6c9eea89ee23a42f00908b6e8b92fa0e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 6 Nov 2026 14:34:38 +0000 Subject: [PATCH 3/3] fix(web): say the track-switch guard skips restarts, not databases, with Local environment off Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/sidebar/updateEverything.logic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index fce04e396167a..efc5ad6612e99 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -177,7 +177,7 @@ 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. - * With Local environment off this app hosts no server, so there is no database to check. */ + * With Local environment off this app runs no server, so its reinstall restarts none. */ export function localSwitchBlockReason( appVersion: string, release: ChannelSwitchRelease,