Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/src/components/settings/UpdateTrackSwitchDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -91,6 +92,7 @@ function SwitchPlan({
databaseMigration: environment.serverConfig?.environment.databaseMigration,
})),
window.desktopBridge?.getLocalEnvironmentBootstraps().length ?? 0,
!isLocalEnvironmentDisabled(),
);

const run = async () => {
Expand Down
41 changes: 35 additions & 6 deletions apps/web/src/components/sidebar/updateEverything.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,27 +252,56 @@ 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(
nightly,
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();
});
});

Expand Down
11 changes: 6 additions & 5 deletions apps/web/src/components/sidebar/updateEverything.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down
Loading