From e007696e541a111519d87815e342f918c315684d Mon Sep 17 00:00:00 2001 From: Shreyasshukla957 Date: Fri, 9 Oct 2026 22:45:55 +0530 Subject: [PATCH 1/3] feat(analytics): track onboarding app browsing --- packages/react/src/api/analytics.test.ts | 4 ++++ packages/react/src/api/analytics.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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; From a731d45107ab88c6beab29b90fd6eeabf55e262b Mon Sep 17 00:00:00 2001 From: Shreyasshukla957 Date: Fri, 9 Oct 2026 22:45:56 +0530 Subject: [PATCH 2/3] feat(onboarding): guide users to their first app --- apps/cloud/src/web/pages/setup-mcp.tsx | 92 ++++++++++++++++++++------ 1 file changed, 70 insertions(+), 22 deletions(-) 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 */} + + +
+ + )} ); From 812f3a5cc157b93b53bca159d53dab150d25b50f Mon Sep 17 00:00:00 2001 From: Shreyasshukla957 Date: Fri, 9 Oct 2026 23:28:32 +0530 Subject: [PATCH 3/3] test(onboarding): follow the skip path in cloud flows --- e2e/cloud/auth-routing-flow.test.ts | 2 +- e2e/cloud/org-delete.test.ts | 2 +- e2e/cloud/org-limit.test.ts | 4 ++-- e2e/cloud/org-multitab-cookie.test.ts | 2 +- e2e/cloud/org-switcher.test.ts | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) 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