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.