diff --git a/apps/cloud/src/web/pages/setup-mcp.tsx b/apps/cloud/src/web/pages/setup-mcp.tsx index a9e7bd2660..09e4e65521 100644 --- a/apps/cloud/src/web/pages/setup-mcp.tsx +++ b/apps/cloud/src/web/pages/setup-mcp.tsx @@ -29,8 +29,14 @@ export const SetupMcpPage = () => { // window where the shell paints over this still-mounted onboarding page. const goToApp = () => navigate({ to: "/{-$orgSlug}", params: { orgSlug: organizationSlug ?? undefined } }); + const goToIntegrationBrowse = () => + navigate({ + to: "/{-$orgSlug}/integrations/browse", + params: { orgSlug: organizationSlug ?? undefined }, + }); const [origin, setOrigin] = useState(null); const [advancedOpen, setAdvancedOpen] = useState(false); + const [agentConnected, setAgentConnected] = useState(false); const [elicitationMode, setElicitationMode] = useState("model"); useEffect(() => { @@ -60,7 +66,7 @@ export const SetupMcpPage = () => {

- Step 2 of 2 + Step 2 of 3

Connect your MCP client

@@ -150,28 +156,70 @@ export const SetupMcpPage = () => {

Adds the server to a supported agent.

-
- {/* oxlint-disable-next-line react/forbid-elements */} - - + +
+ ) : ( +
- Continue to app - -
+
+

+ Step 3 of 3 +

+

+ Add your first integration +

+

+ Choose an app you already use, such as Gmail, and connect it so your agent can start + helping with real work. +

+
+
+ {/* oxlint-disable-next-line react/forbid-elements */} + + +
+ + )} ); diff --git a/e2e/cloud/auth-routing-flow.test.ts b/e2e/cloud/auth-routing-flow.test.ts index 30efb17cdd..144e5a585a 100644 --- a/e2e/cloud/auth-routing-flow.test.ts +++ b/e2e/cloud/auth-routing-flow.test.ts @@ -70,7 +70,7 @@ scenario( ); await page.getByRole("button", { name: "Create organization" }).click(); await page.getByText("Connect your MCP client").waitFor({ timeout: 30_000 }); - await page.getByRole("button", { name: "Continue to app" }).click(); + await page.getByRole("button", { name: "Skip for now" }).click(); // The bare landing canonicalizes onto the new org's slug. await page.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), { timeout: 30_000 }); await page.getByText("Integrations").first().waitFor({ timeout: 30_000 }); diff --git a/e2e/cloud/org-delete.test.ts b/e2e/cloud/org-delete.test.ts index c096c2ca5b..20b5edb1b6 100644 --- a/e2e/cloud/org-delete.test.ts +++ b/e2e/cloud/org-delete.test.ts @@ -28,7 +28,7 @@ scenario( await page.getByPlaceholder("Northwind Labs").fill(ORG); await page.getByRole("button", { name: "Create organization" }).click(); await page.getByText("Connect your MCP client").waitFor(); - await page.getByRole("button", { name: "Continue to app" }).click(); + await page.getByRole("button", { name: "Skip for now" }).click(); await page.getByText("Integrations").first().waitFor(); // The console canonicalizes onto the org's URL slug (/doomed-org). await page.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), { diff --git a/e2e/cloud/org-limit.test.ts b/e2e/cloud/org-limit.test.ts index dcdb570936..6126f94516 100644 --- a/e2e/cloud/org-limit.test.ts +++ b/e2e/cloud/org-limit.test.ts @@ -35,8 +35,8 @@ scenario( await page.getByText("Connect your MCP client").waitFor(); }); - await step("Continue into the app", async () => { - await page.getByRole("button", { name: "Continue to app" }).click(); + await step("Skip onboarding and open the app", async () => { + await page.getByRole("button", { name: "Skip for now" }).click(); await page.getByText("Integrations").first().waitFor(); // Let the router navigation fully settle (slow on a cold dev server) // before opening menus — a late remount closes them mid-interaction. diff --git a/e2e/cloud/org-multitab-cookie.test.ts b/e2e/cloud/org-multitab-cookie.test.ts index 75125828ba..94d6b1c2d9 100644 --- a/e2e/cloud/org-multitab-cookie.test.ts +++ b/e2e/cloud/org-multitab-cookie.test.ts @@ -58,7 +58,7 @@ scenario( await tab1.getByPlaceholder("Northwind Labs").fill("Multitab A"); await tab1.getByRole("button", { name: "Create organization" }).click(); await tab1.getByText("Connect your MCP client").waitFor({ timeout: 30_000 }); - await tab1.getByRole("button", { name: "Continue to app" }).click(); + await tab1.getByRole("button", { name: "Skip for now" }).click(); await tab1.waitForURL((url) => /^\/[a-z0-9-]+\/?$/.test(url.pathname), { timeout: 30_000 }); await tab1.getByText("Integrations").first().waitFor({ timeout: 30_000 }); slugA = slugOf(tab1); diff --git a/e2e/cloud/org-switcher.test.ts b/e2e/cloud/org-switcher.test.ts index 62f77bd4ed..8187e7d61c 100644 --- a/e2e/cloud/org-switcher.test.ts +++ b/e2e/cloud/org-switcher.test.ts @@ -35,8 +35,8 @@ scenario( await page.getByText("Connect your MCP client").waitFor(); }); - await step("Continue into the app", async () => { - await page.getByRole("button", { name: "Continue to app" }).click(); + await step("Skip onboarding and open the app", async () => { + await page.getByRole("button", { name: "Skip for now" }).click(); await page.getByText("Integrations").first().waitFor(); // Let the router navigation fully settle before opening menus — a late // remount closes them mid-interaction. The console canonicalizes onto diff --git a/packages/react/src/api/analytics.test.ts b/packages/react/src/api/analytics.test.ts index c301d0c486..abdbb2a35c 100644 --- a/packages/react/src/api/analytics.test.ts +++ b/packages/react/src/api/analytics.test.ts @@ -16,6 +16,10 @@ describe("analytics seam", () => { expect(() => trackEvent("integration_browse_opened", { via: "header" })).not.toThrow(); }); + it("accepts the onboarding integration entry point", () => { + expect(() => trackEvent("integration_browse_opened", { via: "onboarding" })).not.toThrow(); + }); + it("forwards name and properties to the mounted client", () => { const seen: Array<{ name: AnalyticsEventName; properties: unknown }> = []; setAnalyticsClient((name, properties) => { diff --git a/packages/react/src/api/analytics.tsx b/packages/react/src/api/analytics.tsx index 1161bbb5fc..e397d7692f 100644 --- a/packages/react/src/api/analytics.tsx +++ b/packages/react/src/api/analytics.tsx @@ -34,7 +34,7 @@ export interface AnalyticsEvents { /** The full-page picker was opened. Replaces the connect dialog, whose * `integration_connect_dialog_opened` this supersedes — keep both readable * in dashboards spanning the change. */ - integration_browse_opened: { via: "header" | "empty-state" | "sidebar" }; + integration_browse_opened: { via: "header" | "empty-state" | "sidebar" | "onboarding" }; integration_detect_submitted: { success: boolean; detected_kind?: string;