From 3d37769ebc04d8f7146d45b658738cd53557e5bd Mon Sep 17 00:00:00 2001 From: Fran Zekan Date: Sun, 23 Aug 2026 11:00:24 +0200 Subject: [PATCH 1/4] feat: add sidebar integration connect shortcut --- .changeset/sidebar-integration-connect.md | 5 ++++ e2e/scenarios/provider-plugins-ui.test.ts | 8 ++++--- packages/react/src/multiplayer/shell.tsx | 28 +++++++++++++++++++++-- packages/react/src/pages/integrations.tsx | 2 +- 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 .changeset/sidebar-integration-connect.md diff --git a/.changeset/sidebar-integration-connect.md b/.changeset/sidebar-integration-connect.md new file mode 100644 index 0000000000..6e658a994b --- /dev/null +++ b/.changeset/sidebar-integration-connect.md @@ -0,0 +1,5 @@ +--- +"@executor-js/react": patch +--- + +Add a shortcut beside the sidebar integration list that opens the existing integration picker. diff --git a/e2e/scenarios/provider-plugins-ui.test.ts b/e2e/scenarios/provider-plugins-ui.test.ts index add10ccd15..483de886c9 100644 --- a/e2e/scenarios/provider-plugins-ui.test.ts +++ b/e2e/scenarios/provider-plugins-ui.test.ts @@ -14,10 +14,12 @@ scenario( const identity = yield* target.newIdentity(); yield* browser.session(identity, async ({ page, step }) => { - await step("Open the integrations picker", async () => { - await visit(page, "/integrations"); + await step("Open the integrations picker from the sidebar", async () => { + await visit(page, "/policies"); await clickToReveal( - page.getByRole("button", { name: "Connect" }), + page + .getByRole("navigation") + .getByRole("button", { name: "Connect an integration", exact: true }), page.getByRole("dialog", { name: "Connect an integration" }), ); }); diff --git a/packages/react/src/multiplayer/shell.tsx b/packages/react/src/multiplayer/shell.tsx index 9a92cd0ff9..6981b5d440 100644 --- a/packages/react/src/multiplayer/shell.tsx +++ b/packages/react/src/multiplayer/shell.tsx @@ -2,7 +2,7 @@ import { Link, Outlet, useLocation, useParams } from "@tanstack/react-router"; import { useEffect, useRef, useState, type ReactNode } from "react"; import { useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; -import { BookOpen, Command, ExternalLink } from "lucide-react"; +import { BookOpen, Command, ExternalLink, PlusIcon } from "lucide-react"; import type { Integration } from "@executor-js/sdk/shared"; import { integrationsOptimisticAtom } from "../api/atoms"; import { trackEvent } from "../api/analytics"; @@ -23,6 +23,7 @@ import { } from "../components/integration-favicon"; import { CommandPalette } from "../components/command-palette"; import { Wordmark } from "../components/wordmark"; +import { ConnectDialog } from "../pages/integrations"; import { useClientPlugins, useIntegrationPlugins } from "@executor-js/sdk/client"; import { useAuth } from "./auth-context"; @@ -347,6 +348,7 @@ function SidebarContent( onNavigate?: () => void; showBrand?: boolean; onOpenCommands: () => void; + onOpenIntegrationConnect: () => void; }, ) { const plugins = useClientPlugins(); @@ -382,8 +384,19 @@ function SidebarContent( /> ))} -
+
Integrations +
@@ -415,6 +428,7 @@ export function Shell(props: ShellProps) { const lastPathname = useRef(pathname); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [commandPaletteOpen, setCommandPaletteOpen] = useState(false); + const [connectIntegrationOpen, setConnectIntegrationOpen] = useState(false); if (lastPathname.current !== pathname) { lastPathname.current = pathname; if (mobileSidebarOpen) setMobileSidebarOpen(false); @@ -432,12 +446,17 @@ export function Shell(props: ShellProps) { return (
+ {/* Desktop sidebar */} @@ -481,6 +500,11 @@ export function Shell(props: ShellProps) { setMobileSidebarOpen(false); setCommandPaletteOpen(true); }} + onOpenIntegrationConnect={() => { + setMobileSidebarOpen(false); + setConnectIntegrationOpen(true); + trackEvent("integration_connect_dialog_opened"); + }} />
diff --git a/packages/react/src/pages/integrations.tsx b/packages/react/src/pages/integrations.tsx index f1780b75cb..cab03dca55 100644 --- a/packages/react/src/pages/integrations.tsx +++ b/packages/react/src/pages/integrations.tsx @@ -152,7 +152,7 @@ const looksLikeUrl = (raw: string): boolean => { return false; }; -function ConnectDialog(props: { open: boolean; onOpenChange: (open: boolean) => void }) { +export function ConnectDialog(props: { open: boolean; onOpenChange: (open: boolean) => void }) { const integrationPlugins = useIntegrationPlugins(); const doDetect = useAtomSet(detectIntegration, { mode: "promiseExit" }); const navigate = useNavigate(); From 7d892a2242d9c3296a166bf4271b759d038400a1 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:09:21 -0700 Subject: [PATCH 2/4] Bump the changeset to the executor package Only the published CLI is managed directly by Changesets (.changeset/README.md); a package-scoped entry does not reach the Version Packages PR. Rewrite the body as the changelog entry it becomes. --- .changeset/sidebar-integration-connect.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/sidebar-integration-connect.md b/.changeset/sidebar-integration-connect.md index 6e658a994b..84a2b8a240 100644 --- a/.changeset/sidebar-integration-connect.md +++ b/.changeset/sidebar-integration-connect.md @@ -1,5 +1,15 @@ --- -"@executor-js/react": patch +"executor": patch --- -Add a shortcut beside the sidebar integration list that opens the existing integration picker. +**Connect an integration from the sidebar** + +The sidebar lists your integrations on every console route, but connecting +another one meant navigating back to the integrations page to reach its Connect +action — the picker state was owned by that page, so the shared shell could +render the list without being able to open the flow behind it. + +The connect dialog now belongs to the shell. A labelled plus button sits beside +the sidebar's Integrations heading and opens the same picker, records the same +event, and leaves the current route in place behind it. On mobile the navigation +drawer closes first so the dialog gets the full viewport. From 01e475faed2bcc8c640d636141729e9b424ac3c8 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:58:43 -0700 Subject: [PATCH 3/4] Name the sidebar integration shortcut Add integration The shortcut used the accessible name "Connect an integration". Playwright matches an accessible name as a case-insensitive substring by default, so every existing scenario that targets getByRole("button", { name: "Connect" }) now matched the sidebar button as well as the integrations page action, and failed strict mode on four e2e shards. Give the shortcut its own verb and noun, per the design guidance, so the two controls no longer share a name. --- e2e/scenarios/provider-plugins-ui.test.ts | 2 +- packages/react/src/multiplayer/shell.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/scenarios/provider-plugins-ui.test.ts b/e2e/scenarios/provider-plugins-ui.test.ts index be14eadc47..0ff1059312 100644 --- a/e2e/scenarios/provider-plugins-ui.test.ts +++ b/e2e/scenarios/provider-plugins-ui.test.ts @@ -19,7 +19,7 @@ scenario( await clickToReveal( page .getByRole("navigation") - .getByRole("button", { name: "Connect an integration", exact: true }), + .getByRole("button", { name: "Add integration", exact: true }), page.getByRole("dialog", { name: "Connect an integration" }), ); }); diff --git a/packages/react/src/multiplayer/shell.tsx b/packages/react/src/multiplayer/shell.tsx index 6981b5d440..bb5ce10c84 100644 --- a/packages/react/src/multiplayer/shell.tsx +++ b/packages/react/src/multiplayer/shell.tsx @@ -390,8 +390,8 @@ function SidebarContent( type="button" variant="ghost" size="icon-xs" - aria-label="Connect an integration" - title="Connect an integration" + aria-label="Add integration" + title="Add integration" onClick={props.onOpenIntegrationConnect} className="-my-1 text-muted-foreground hover:bg-sidebar-active/60 hover:text-foreground" > From 16bb88eed3c7251bf4085f1d3b7b18a744936ec2 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:24:30 -0700 Subject: [PATCH 4/4] Rename the sidebar shortcut to a unique accessible name --- e2e/scenarios/provider-plugins-ui.test.ts | 2 +- packages/react/src/multiplayer/shell.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/scenarios/provider-plugins-ui.test.ts b/e2e/scenarios/provider-plugins-ui.test.ts index 0ff1059312..81ba31e2c2 100644 --- a/e2e/scenarios/provider-plugins-ui.test.ts +++ b/e2e/scenarios/provider-plugins-ui.test.ts @@ -19,7 +19,7 @@ scenario( await clickToReveal( page .getByRole("navigation") - .getByRole("button", { name: "Add integration", exact: true }), + .getByRole("button", { name: "Browse integrations", exact: true }), page.getByRole("dialog", { name: "Connect an integration" }), ); }); diff --git a/packages/react/src/multiplayer/shell.tsx b/packages/react/src/multiplayer/shell.tsx index bb5ce10c84..795e16716a 100644 --- a/packages/react/src/multiplayer/shell.tsx +++ b/packages/react/src/multiplayer/shell.tsx @@ -390,8 +390,8 @@ function SidebarContent( type="button" variant="ghost" size="icon-xs" - aria-label="Add integration" - title="Add integration" + aria-label="Browse integrations" + title="Browse integrations" onClick={props.onOpenIntegrationConnect} className="-my-1 text-muted-foreground hover:bg-sidebar-active/60 hover:text-foreground" >