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 58328dbc1eeeb..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,17 +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( + 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, + 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", ); - // A cached head from a disconnected server proves nothing. + // Local environment off: the app hosts nothing, so the reinstall restarts no database. + expect(localSwitchBlockReason(nightly, stable, [], 0, false)).toBeNull(); expect( - localSwitchBlockReason(nightly, stable, [{ connected: false, databaseMigration: 69 }], 1), - ).toBe("its server is not connected, so its database cannot be checked"); - expect(localSwitchBlockReason("0.1.3", stable, [], 1)).toBeNull(); + localSwitchBlockReason( + "0.1.3", + { channel: "nightly", version: nightly, databaseMigration: 69 }, + [], + 0, + false, + ), + ).toBeNull(); }); }); diff --git a/apps/web/src/components/sidebar/updateEverything.logic.ts b/apps/web/src/components/sidebar/updateEverything.logic.ts index cbbd3baa7c776..1f91ebf500d5f 100644 --- a/apps/web/src/components/sidebar/updateEverything.logic.ts +++ b/apps/web/src/components/sidebar/updateEverything.logic.ts @@ -174,9 +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. */ +/** 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, @@ -185,11 +186,11 @@ export function localSwitchBlockReason( readonly databaseMigration: number | undefined; }>, backendCount: number, + localEnvironmentEnabled: boolean, ): string | null { if (!crossesTrack(appVersion, release)) return null; if ( - hosted.length === 0 || - hosted.length < backendCount || + 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";