From d3c6cdc515c6718dfb7deb565e9641c5bdf80707 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 23 Aug 2026 10:02:25 -0700 Subject: [PATCH 1/3] Add tests for onboarding copy before a workbench exists (CL-6780) Pin preparing-account/agent headlines and an honest skip path when no credential is present, so the wait never claims a workbench is ready. --- apps/web/test/home-page.test.tsx | 45 ++++++++++++++++++++++++++++++- apps/web/test/onboarding.test.tsx | 15 ++++++++--- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apps/web/test/home-page.test.tsx b/apps/web/test/home-page.test.tsx index 1b9eeab47..bf8feb8a2 100644 --- a/apps/web/test/home-page.test.tsx +++ b/apps/web/test/home-page.test.tsx @@ -215,6 +215,12 @@ describe("the wait right after connecting a provider", () => { if (path.endsWith("/chat/workbenches") && method === "GET") { return json({ items: [] }); } + // CL-6780: while Myra is still coming online we also confirm a + // credential exists — without one the drain never starts, so the + // wait must not pretend a workbench is "getting ready". + if (path === "/api/tenants/tnt_1/credentials") { + return json({ data: [{ status: "active" }], nextCursor: null }); + } if (path === "/api/onboarding/provisioning-status") { statusReads += 1; state.statusCalls += 1; @@ -265,13 +271,50 @@ describe("the wait right after connecting a provider", () => { } const text = container?.textContent ?? ""; - expect(text).toContain("Getting your workbench ready"); + // CL-6780: zero workbenches yet — this wait is for the agent, not a + // workbench that does not exist. + expect(text).toContain("Preparing your agent"); + expect(text).not.toContain("Getting your workbench ready"); expect(text).toContain("Tip:"); expect(text).not.toMatch(/\d+ of \d+/); expect(text).not.toMatch(/\d/); expect(navigated).toEqual([]); }); + test("skip with no credential lands on an honest next step instead of stuck workbench-ready copy (CL-6780)", async () => { + stubFetch((path, method) => { + if (path === "/api/me/principals") return json(PRINCIPALS_RESPONSE); + if (path.endsWith("/chat/workbenches") && method === "GET") { + return json({ items: [] }); + } + if (path === "/api/tenants/tnt_1/credentials") { + return json({ data: [], nextCursor: null }); + } + if (path === "/api/onboarding/provisioning-status") { + return json({ + kind: "provisioning", + setupAgentReady: false, + deployed: [], + pending: ["assistant"], + }); + } + throw new Error(`unexpected fetch: ${method} ${path}`); + }); + + const navigated: string[] = []; + await renderHome({ retryMs: 10, stallAfterMs: 10_000, navigated }); + for (let i = 0; i < 40; i++) { + await settle(); + const text = container?.textContent ?? ""; + if (text.includes("Connect a provider") || navigated.length > 0) break; + } + + const text = container?.textContent ?? ""; + expect(text).not.toContain("Getting your workbench ready"); + expect(text).toContain("Connect a provider"); + expect(navigated).toEqual([]); + }); + test("lands the moment Myra can answer, without waiting on the rest of the seeds", async () => { // The status route still says "provisioning" — other workflows are // mid-deploy — and the land happens anyway. diff --git a/apps/web/test/onboarding.test.tsx b/apps/web/test/onboarding.test.tsx index 41d997833..c6e79985e 100644 --- a/apps/web/test/onboarding.test.tsx +++ b/apps/web/test/onboarding.test.tsx @@ -404,7 +404,10 @@ describe("App at the onboarding path", () => { // default name derived from the account, before the credential step. // Onboarding is down to a single screen at this point (CL-6104) — no // stepper needed for one step. - expect(markup).toContain("Setting up your workbench"); + // CL-6780: this prepares the account; no workbench exists yet. + expect(markup).toContain("Preparing your account"); + expect(markup).not.toContain("Getting your workbench ready"); + expect(markup).not.toContain("Setting up your workbench"); }); }); @@ -754,11 +757,14 @@ describe("the OpenRouter connect card", () => { // OAuth callback only stored the key, so the checklist showing // "confirmed running" before the deploy step even started would be // a lie. + // CL-6780: finishing-setup prepares the agent; no workbench exists yet. const markup = renderOnboardingAt( "/onboarding?connect=openrouter&outcome=connected&tenantSlug=ada-user1", ); - expect(markup).toContain("Setting up your workbench"); + expect(markup).toContain("Preparing your agent"); + expect(markup).not.toContain("Getting your workbench ready"); + expect(markup).not.toContain("Setting up your workbench"); expect(markup).not.toContain("Your workbench is ready"); }); @@ -1045,7 +1051,10 @@ describe("the Hugging Face connect card", () => { "/onboarding?connect=huggingface&outcome=connected&tenantSlug=ada-user1", ); - expect(markup).toContain("Setting up your workbench"); + // CL-6780: finishing-setup prepares the agent; no workbench exists yet. + expect(markup).toContain("Preparing your agent"); + expect(markup).not.toContain("Getting your workbench ready"); + expect(markup).not.toContain("Setting up your workbench"); expect(markup).not.toContain("Your workbench is ready"); }); From 87d37ea7bf550bacd03265cd1c272e911762b405 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 23 Aug 2026 10:02:29 -0700 Subject: [PATCH 2/3] Honest onboarding wait copy before a workbench exists (CL-6780) Use preparing-account/agent language during onboarding, and on `/` wait for the agent (not a missing workbench). Skip without a credential lands on connect-a-provider instead of spinning forever. --- apps/web/src/pages/home-page.tsx | 56 +++++++++++++++++++++++--- apps/web/src/pages/onboarding-page.tsx | 8 ++-- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/apps/web/src/pages/home-page.tsx b/apps/web/src/pages/home-page.tsx index 7f93b6302..f33dd6c0d 100644 --- a/apps/web/src/pages/home-page.tsx +++ b/apps/web/src/pages/home-page.tsx @@ -21,6 +21,11 @@ // behind us. Readiness is read only to tell a wait from a genuine failure, // never to draw a progress number: a seed count is an implementation // detail, and "0 of 5" told a waiting person nothing. +// +// CL-6780: that wait is for the agent, never a workbench that does not +// exist yet — so the loader says "Preparing your agent", and a skip with +// no credential stops pretending anything is "getting ready" and offers +// the honest next step (connect a provider) instead of spinning forever. import { Button, EmptyState, PageShell } from "@corbits/react-ui"; import { Clock, WarningCircle } from "@corbits/icons"; @@ -29,26 +34,38 @@ import { useEffect, useState } from "react"; import { listAllWorkbenches, WorkbenchLoadingState } from "@corbits/chat-ui"; import { describeApiError } from "@corbits/api-query"; -import { fetchAgentReadiness } from "../onboarding"; +import { fetchAgentReadiness, hasActiveCredential } from "../onboarding"; import { useBench } from "../bench-context"; import { workbenchPath } from "../workbench-path"; import { ensureMyraWorkbench } from "../myra-workbench"; import { useNavigate } from "../navigation"; -import { NEW_WORKBENCH_PATH } from "../routes"; +import { NEW_WORKBENCH_PATH, ONBOARDING_PATH } from "../routes"; type LandState = /** Working on it: the warm loader, whether we are reading the bench's * workbenches or waiting for Myra to finish coming online. Both are * the same thing to the person waiting. */ | { readonly kind: "opening" } + /** Zero workbenches, credential present, Myra not ready yet — the + * post-connect wait. Headline names the agent, never a workbench that + * does not exist (CL-6780). */ + | { readonly kind: "waiting-for-agent" } /** Myra has taken long enough that silence would read as a hang. Says * so plainly and offers another go — never a frozen number. */ | { readonly kind: "slow" } + /** Zero workbenches and no active credential: the drain never starts, + * so waiting on "ready" would spin forever. Offer the connect step. */ + | { readonly kind: "needs-provider" } | { readonly kind: "error"; readonly message: string }; + const LAND_RETRY_MS = 3_000; const LAND_STALL_MS = 45_000; +/** Warm-loader headline for the post-onboarding wait on `/` when no + * workbench exists yet — Myra is coming online, not a workbench. */ +const PREPARING_AGENT_TITLE = "Preparing your agent"; + export function HomeRoute({ retryMs = LAND_RETRY_MS, stallAfterMs = LAND_STALL_MS, @@ -105,7 +122,9 @@ export function HomeRoute({ // the person to the picker rather than minting anything ourselves — // "she can't start yet" and "here, go create your first workbench" // are different messages, and only the readiness check tells them - // apart. + // apart. Without a credential the drain never starts (CL-6780), so a + // not-ready status with no credential is an honest next step, not a + // forever spin on "getting ready". const awaitFirstWorkbench = () => { void fetchAgentReadiness().then((readiness) => { if (cancelled) return; @@ -113,7 +132,17 @@ export function HomeRoute({ navigate(NEW_WORKBENCH_PATH); return; } - waitAndRetry(); + void hasActiveCredential(selectedTenantId).then((hasCredential) => { + if (cancelled) return; + if (!hasCredential) { + setState({ kind: "needs-provider" }); + return; + } + setState((current) => + current.kind === "slow" ? current : { kind: "waiting-for-agent" }, + ); + waitAndRetry(); + }); }); }; @@ -202,6 +231,23 @@ export function HomeRoute({ ); } + if (state.kind === "needs-provider") { + return ( + + } + title="Connect a provider" + description="Agents need a provider before they can come online. Connect one to finish setup." + action={ + + } + /> + + ); + } + if (state.kind === "slow") { return ( @@ -221,7 +267,7 @@ export function HomeRoute({ return (
- +
); } diff --git a/apps/web/src/pages/onboarding-page.tsx b/apps/web/src/pages/onboarding-page.tsx index b261bd147..5af67afd8 100644 --- a/apps/web/src/pages/onboarding-page.tsx +++ b/apps/web/src/pages/onboarding-page.tsx @@ -381,12 +381,12 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) { return (
-

Setting up your workbench

+

Preparing your account

One moment.

@@ -398,14 +398,14 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) { return (
-

Setting up your workbench…

+

Preparing your agent

Hooking up your agents. This takes about ten seconds.

From a7a0de6794e47362eb7748b2c980df2bca680ca2 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 23 Aug 2026 10:05:55 -0700 Subject: [PATCH 3/3] Fix lint: prettier for CL-6780 onboarding copy --- apps/web/src/pages/home-page.tsx | 1 - apps/web/src/pages/onboarding-page.tsx | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/web/src/pages/home-page.tsx b/apps/web/src/pages/home-page.tsx index f33dd6c0d..c3782e4cc 100644 --- a/apps/web/src/pages/home-page.tsx +++ b/apps/web/src/pages/home-page.tsx @@ -58,7 +58,6 @@ type LandState = | { readonly kind: "needs-provider" } | { readonly kind: "error"; readonly message: string }; - const LAND_RETRY_MS = 3_000; const LAND_STALL_MS = 45_000; diff --git a/apps/web/src/pages/onboarding-page.tsx b/apps/web/src/pages/onboarding-page.tsx index 5af67afd8..bb2ff382c 100644 --- a/apps/web/src/pages/onboarding-page.tsx +++ b/apps/web/src/pages/onboarding-page.tsx @@ -403,10 +403,7 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) { Hooking up your agents. This takes about ten seconds.

- +