diff --git a/ui/src/components/OnboardingWizard.step.test.tsx b/ui/src/components/OnboardingWizard.step.test.tsx index 2cd21b29f6..ceb0d8a400 100644 --- a/ui/src/components/OnboardingWizard.step.test.tsx +++ b/ui/src/components/OnboardingWizard.step.test.tsx @@ -229,6 +229,53 @@ describe("OnboardingWizard — which step it lands on", () => { vi.clearAllMocks(); }); + /** + * The progress strip counts the walk the customer is actually on, and the two + * runs that enter on the agent step are on different walks. + * + * Both have a company already, so `entryStep` cannot tell them apart. What + * does is `enableManagedSandboxOnly` — the cloud-tenant shape. A cloud tenant + * was asked for its organization's name by Cloud, one screen earlier, so its + * walk is four and this is the second. A self-hosted company that simply has + * no agents yet was asked nothing before this, so its walk is three. + */ + describe("progress strip length", () => { + function announcedCount(): string | null { + return ( + [...document.querySelectorAll(".sr-only")] + .map((element) => element.textContent?.trim() ?? "") + .find((text) => /^Step \d+ of \d+$/.test(text)) ?? null + ); + } + + it("counts four on a cloud tenant, continuing the count Cloud started", async () => { + mockInstanceSettingsApi.getExperimental.mockResolvedValue({ + enableManagedSandboxOnly: true, + }); + routerState.pathname = "/PC1/onboarding"; + await render(); + await settle(); + + expect(currentStep()).toBe("agent"); + expect(announcedCount()).toBe("Step 2 of 4"); + }); + + it("counts three on a self-hosted company that has no agents yet", async () => { + // Nothing was asked before this step here, so a fourth segment would be + // one the run can never fill — and it would credit the customer with a + // step they never walked. + mockInstanceSettingsApi.getExperimental.mockResolvedValue({ + enableManagedSandboxOnly: false, + }); + routerState.pathname = "/PC1/onboarding"; + await render(); + await settle(); + + expect(currentStep()).toBe("agent"); + expect(announcedCount()).toBe("Step 1 of 3"); + }); + }); + it("opens a company that already has its mission on the agent step", async () => { // The point of the change: Cloud collected the mission at signup and the // seed wrote it as a company-level goal, so asking for it again asks a diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index ea13f62506..e71b581ad7 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -890,10 +890,13 @@ function OnboardingWizardInner({ queryFn: () => instanceSettingsApi.get(), enabled: effectiveOnboardingOpen && step === 4, }); + // Wanted across the whole arc, not just the connect step. The progress strip + // reads it too — see `enteredFromCloud` — and a value fetched only on step 4 + // would let the strip change length as the customer walked through it. const { data: experimentalSettingsForLogin } = useQuery({ queryKey: queryKeys.instance.experimentalSettings, queryFn: () => instanceSettingsApi.getExperimental(), - enabled: effectiveOnboardingOpen && step === 4, + enabled: effectiveOnboardingOpen && step >= 3 && step <= 5, }); const resolvedLoginEnvironmentId = useMemo(() => { try { @@ -2004,7 +2007,21 @@ function OnboardingWizardInner({ } const isAgentArcStep = agentArcStepFor(step) !== null; - const showsAgentArcStepper = isAgentArcStep && entryStep >= 3; + /** + * True when the organization was named in Cloud rather than here. + * + * `enableManagedSandboxOnly` is the cloud-tenant shape — the connect step + * already resolves its login environment through it. A tenant wearing it did + * not ask for the organization's name, because Cloud did, so the walk the + * customer is on is four steps and this is the second. + * + * A self-hosted run that enters at the agent step is a different case with + * the same `entryStep`: an existing company that has no agents yet. There was + * no naming screen before it, so its walk really is three, and it keeps the + * shorter strip. + */ + const enteredFromCloud = experimentalSettingsForLogin?.enableManagedSandboxOnly === true; + const showsAgentArcStepper = isAgentArcStep && entryStep >= 3 && !enteredFromCloud; const launchStateIncomplete = step === 5 && (!createdCompanyId || !createdAgentId); const visibleError = error ?? (launchStateIncomplete ? INCOMPLETE_ONBOARDING_STATE_MESSAGE : null);