Skip to content

Commit 2fbdfe8

Browse files
fix(web): onboarding Continue no longer locks on computers that won't connect
Continue required every selected computer to be connected, but the wizard auto-selects every saved computer, including switched-off, offline, failing, and unsupported ones, and hides saved T3 Connect entries the relay no longer reports. A single such computer disabled Continue with no way to deselect it. Continue now enables once a selected computer is connected and no first connection attempt is still settling; computers that will not connect on their own are skipped. Switched-off computers are no longer auto-selected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 4d976d1 commit 2fbdfe8

6 files changed

Lines changed: 75 additions & 16 deletions

File tree

‎apps/web/src/components/onboarding/WelcomeWizard.import.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ vi.mock("../../state/environments", () => {
6464
environmentId,
6565
label: environmentId,
6666
connection: { phase: "connected" },
67-
entry: { target: { _tag: "DirectConnectionTarget" } },
67+
entry: { enabled: true, target: { _tag: "DirectConnectionTarget" } },
6868
});
6969
const primary = environment("primary");
7070
return {

‎apps/web/src/components/onboarding/WelcomeWizard.terminal.test.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ const primaryEnvironment = {
151151
label: "This computer",
152152
connection: { phase: "connected" },
153153
entry: {
154+
enabled: true,
154155
target: new PrimaryConnectionTarget({
155156
environmentId: primaryId,
156157
label: "This computer",
@@ -164,6 +165,7 @@ const remoteEnvironment = {
164165
label: "Paired computer",
165166
connection: { phase: "connected" },
166167
entry: {
168+
enabled: true,
167169
target: new BearerConnectionTarget({
168170
environmentId: remoteId,
169171
label: "Paired computer",

‎apps/web/src/components/onboarding/WelcomeWizard.test.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ vi.mock("../../state/environments", () => {
3838
environmentId: "test-env",
3939
label: "Computer",
4040
connection: { phase: "connected" },
41+
entry: { enabled: true },
4142
};
4243
return {
4344
useEnvironments: () => ({ environments: [environment] }),

‎apps/web/src/components/onboarding/WelcomeWizard.tsx‎

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ import { newProjectId, randomUUID } from "../../lib/utils";
5858
import { agentSessionImport } from "../../state/agentSessions";
5959
import { readProjects, useProjects } from "../../state/entities";
6060
import { useEnvironments, usePrimaryEnvironment } from "../../state/environments";
61-
import { isOnboardingRelayEnvironment } from "../../onboarding/targetEnvironment.logic";
61+
import {
62+
isOnboardingRelayEnvironment,
63+
resolveOnboardingSetup,
64+
} from "../../onboarding/targetEnvironment.logic";
6265
import { useProjectScans } from "../../onboarding/useProjectScans";
6366
import { projectEnvironment } from "../../state/projects";
6467
import { serverEnvironment } from "../../state/server";
@@ -141,11 +144,14 @@ export function WelcomeWizard({
141144
for (const environment of newComputers) {
142145
autoSelectedComputers.current.add(environment.environmentId);
143146
}
147+
// A computer the user switched off stays unselected until they pick it.
148+
const enabledComputers = newComputers.filter((environment) => environment.entry.enabled);
149+
if (enabledComputers.length === 0) return;
144150
setSelection(
145151
(current) =>
146152
new Set([
147153
...(current ?? []),
148-
...newComputers.map((environment) => environment.environmentId),
154+
...enabledComputers.map((environment) => environment.environmentId),
149155
]),
150156
);
151157
}, [environments]);
@@ -262,11 +268,7 @@ export function WelcomeWizard({
262268
})
263269
}
264270
onContinue={() =>
265-
startSetup(
266-
environments
267-
.filter((environment) => selectedIds.has(environment.environmentId))
268-
.map((environment) => environment.environmentId),
269-
)
271+
startSetup(resolveOnboardingSetup(environments, selectedIds).environmentIds)
270272
}
271273
onPaired={(environmentId) => {
272274
setSelection(new Set([...selectedIds, environmentId]));
@@ -315,14 +317,7 @@ function ConnectionStep({
315317
);
316318
const [pairingOpen, setPairingOpen] = useState(expandPairingInitially);
317319
const [isPairing, setIsPairing] = useState(false);
318-
const ready =
319-
selectedIds.size > 0 &&
320-
[...selectedIds].every((id) =>
321-
environments.some(
322-
(environment) =>
323-
environment.environmentId === id && environment.connection.phase === "connected",
324-
),
325-
);
320+
const { ready } = resolveOnboardingSetup(environments, selectedIds);
326321
const continueRef = useRef<HTMLButtonElement>(null);
327322
useEffect(() => {
328323
if (

‎apps/web/src/onboarding/targetEnvironment.logic.test.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { describe, expect, it } from "vite-plus/test";
99

1010
import {
1111
isOnboardingRelayEnvironment,
12+
resolveOnboardingSetup,
1213
resolveOnboardingTargetEnvironment,
1314
} from "./targetEnvironment.logic";
1415

@@ -209,3 +210,45 @@ describe("isOnboardingRelayEnvironment", () => {
209210
).toEqual([olderRelay]);
210211
});
211212
});
213+
214+
describe("resolveOnboardingSetup", () => {
215+
const computer = (id: string, phase: string) => ({
216+
environmentId: EnvironmentId.make(id),
217+
connection: { phase },
218+
});
219+
const local = computer("local", "connected");
220+
221+
it("skips selected computers that will not connect on their own", () => {
222+
const environments = [
223+
local,
224+
computer("switched-off", "available"),
225+
computer("offline", "offline"),
226+
computer("failing", "reconnecting"),
227+
computer("old-client", "unsupported"),
228+
];
229+
expect(
230+
resolveOnboardingSetup(
231+
environments,
232+
new Set(environments.map((environment) => environment.environmentId)),
233+
),
234+
).toEqual({ ready: true, environmentIds: [local.environmentId] });
235+
});
236+
237+
it("waits for a first connection attempt to settle", () => {
238+
const connecting = computer("new", "connecting");
239+
expect(
240+
resolveOnboardingSetup(
241+
[local, connecting],
242+
new Set([local.environmentId, connecting.environmentId]),
243+
).ready,
244+
).toBe(false);
245+
});
246+
247+
it("is not ready without a connected selection", () => {
248+
expect(resolveOnboardingSetup([local], new Set()).ready).toBe(false);
249+
expect(
250+
resolveOnboardingSetup([computer("off", "available")], new Set([EnvironmentId.make("off")]))
251+
.ready,
252+
).toBe(false);
253+
});
254+
});

‎apps/web/src/onboarding/targetEnvironment.logic.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,21 @@ export function resolveOnboardingTargetEnvironment<TEnvironment extends Onboardi
4747

4848
return mode === "local" ? null : (connectedRelayEnvironments[0] ?? null);
4949
}
50+
51+
/**
52+
* The computers the wizard sets up from the user's selection. Continue waits
53+
* only on a first connection attempt, which settles on its own. Selected
54+
* computers that are switched off, offline, failing, or unsupported are skipped
55+
* so they can never lock the user out of onboarding.
56+
*/
57+
export function resolveOnboardingSetup(
58+
environments: ReadonlyArray<Pick<OnboardingEnvironment, "environmentId" | "connection">>,
59+
selectedIds: ReadonlySet<EnvironmentId>,
60+
): { readonly ready: boolean; readonly environmentIds: ReadonlyArray<EnvironmentId> } {
61+
const selected = environments.filter((environment) => selectedIds.has(environment.environmentId));
62+
const environmentIds = selected
63+
.filter((environment) => environment.connection.phase === "connected")
64+
.map((environment) => environment.environmentId);
65+
const settling = selected.some((environment) => environment.connection.phase === "connecting");
66+
return { ready: environmentIds.length > 0 && !settling, environmentIds };
67+
}

0 commit comments

Comments
 (0)