History

Move connect-agent above sources and preload plugin chunks (#494)

- Sources page: render McpInstallCard above the connected sources list so agent-pairing is the primary affordance on an empty workspace. - Add optional `preload` to SourcePlugin and call it from ExecutorPluginsProvider on mount, so navigating into a plugin's add/edit page no longer trips Suspense waiting on the lazy chunk. - Drop the visible "Loading…" Suspense fallback in sources-add — the preload now covers warm sessions, and a flash of nothing is better than a layout-shifting label on cold ones.

Rhys Sullivan committed 6 months ago

Commit b849f921 parent c627c487 files · +71 −21Browse files

Unified · Split

7 files
  1. packages/core/sdk/src/client.ts +10 −0
  2. packages/plugins/google-discovery/src/react/source-plugin.ts +15 −4
  3. packages/plugins/graphql/src/react/source-plugin.ts +15 −4
  4. packages/plugins/mcp/src/react/source-plugin.tsx +12 −3
  5. packages/plugins/openapi/src/react/source-plugin.ts +12 −3
  6. packages/react/src/pages/sources-add.tsx +1 −1
  7. packages/react/src/pages/sources.tsx +6 −6
packages/core/sdk/src/client.ts+10−0
@@ -13,6 +13,7 @@ import { · 12 unmodified lines
13 createContext,13 createContext,
14 createElement,14 createElement,
15 useContext,15 useContext,
16 useEffect,
16 useMemo,17 useMemo,
17 type ComponentType,18 type ComponentType,
18 type ReactNode,19 type ReactNode,
@@ -130,6 +131,10 @@ export interface SourcePlugin { · 111 unmodified lines
130 readonly sourceId: string;131 readonly sourceId: string;
131 }>;132 }>;
132 readonly presets?: readonly SourcePreset[];133 readonly presets?: readonly SourcePreset[];
134 /** Trigger early download of the plugin's lazy component chunks (add/edit/etc.).
135 * Call from the host on intent (hover/focus) so the chunks land before the
136 * user navigates into the add page. Idempotent. */
137 readonly preload?: () => void;
133}138}
134 139
135// ---------------------------------------------------------------------------140// ---------------------------------------------------------------------------
@@ -269,6 +274,11 @@ export function ExecutorPluginsProvider( · 133 unmodified lines
269 }),274 }),
270 [plugins],275 [plugins],
271 );276 );
277 // Kick off lazy chunk downloads for every source plugin once the host
278 // mounts, so navigating into an add/edit page doesn't suspend.
279 useEffect(() => {
280 for (const sp of value.sourcePlugins) sp.preload?.();
281 }, [value.sourcePlugins]);
272 return createElement(ExecutorPluginsContext.Provider, { value }, children);282 return createElement(ExecutorPluginsContext.Provider, { value }, children);
273}283}
274 284
packages/plugins/google-discovery/src/react/source-plugin.ts+15−4
@@ -2,12 +2,23 @@ import { lazy } from "react"; · 1 unmodified line
2import type { SourcePlugin } from "@executor-js/sdk/client";2import type { SourcePlugin } from "@executor-js/sdk/client";
3import { googleDiscoveryPresets } from "../sdk/presets";3import { googleDiscoveryPresets } from "../sdk/presets";
4 4
5const importAdd = () => import("./AddGoogleDiscoverySource");
6const importEdit = () => import("./EditGoogleDiscoverySource");
7const importSummary = () => import("./GoogleDiscoverySourceSummary");
8const importSignIn = () => import("./GoogleDiscoverySignInButton");
9
5export const googleDiscoverySourcePlugin: SourcePlugin = {10export const googleDiscoverySourcePlugin: SourcePlugin = {
6 key: "googleDiscovery",11 key: "googleDiscovery",
7 label: "Google Discovery",12 label: "Google Discovery",
8 add: lazy(() => import("./AddGoogleDiscoverySource")),13 add: lazy(importAdd),
9 edit: lazy(() => import("./EditGoogleDiscoverySource")),14 edit: lazy(importEdit),
10 summary: lazy(() => import("./GoogleDiscoverySourceSummary")),15 summary: lazy(importSummary),
11 signIn: lazy(() => import("./GoogleDiscoverySignInButton")),16 signIn: lazy(importSignIn),
12 presets: googleDiscoveryPresets,17 presets: googleDiscoveryPresets,
18 preload: () => {
19 void importAdd();
20 void importEdit();
21 void importSummary();
22 void importSignIn();
23 },
13};24};
packages/plugins/graphql/src/react/source-plugin.ts+15−4
@@ -2,12 +2,23 @@ import { lazy } from "react"; · 1 unmodified line
2import type { SourcePlugin } from "@executor-js/sdk/client";2import type { SourcePlugin } from "@executor-js/sdk/client";
3import { graphqlPresets } from "../sdk/presets";3import { graphqlPresets } from "../sdk/presets";
4 4
5const importAdd = () => import("./AddGraphqlSource");
6const importEdit = () => import("./EditGraphqlSource");
7const importSummary = () => import("./GraphqlSourceSummary");
8const importSignIn = () => import("./GraphqlSignInButton");
9
5export const graphqlSourcePlugin: SourcePlugin = {10export const graphqlSourcePlugin: SourcePlugin = {
6 key: "graphql",11 key: "graphql",
7 label: "GraphQL",12 label: "GraphQL",
8 add: lazy(() => import("./AddGraphqlSource")),13 add: lazy(importAdd),
9 edit: lazy(() => import("./EditGraphqlSource")),14 edit: lazy(importEdit),
10 summary: lazy(() => import("./GraphqlSourceSummary")),15 summary: lazy(importSummary),
11 signIn: lazy(() => import("./GraphqlSignInButton")),16 signIn: lazy(importSignIn),
12 presets: graphqlPresets,17 presets: graphqlPresets,
18 preload: () => {
19 void importAdd();
20 void importEdit();
21 void importSummary();
22 void importSignIn();
23 },
13};24};
packages/plugins/mcp/src/react/source-plugin.tsx+12−3
@@ -2,9 +2,13 @@ import { lazy, type ComponentProps, type ComponentType } from "react"; · 1 unmodified line
2import type { SourcePlugin } from "@executor-js/sdk/client";2import type { SourcePlugin } from "@executor-js/sdk/client";
3import { mcpPresets } from "../sdk/presets";3import { mcpPresets } from "../sdk/presets";
4 4
5const LazyAddMcpSource = lazy(() => import("./AddMcpSource"));5const importAdd = () => import("./AddMcpSource");
6const LazyEditMcpSource = lazy(() => import("./EditMcpSource"));6const importEdit = () => import("./EditMcpSource");
7const LazyMcpSignInButton = lazy(() => import("./McpSignInButton"));7const importSignIn = () => import("./McpSignInButton");
8
9const LazyAddMcpSource = lazy(importAdd);
10const LazyEditMcpSource = lazy(importEdit);
11const LazyMcpSignInButton = lazy(importSignIn);
8 12
9type AddProps = ComponentProps<SourcePlugin["add"]>;13type AddProps = ComponentProps<SourcePlugin["add"]>;
10 14
@@ -41,6 +45,11 @@ export const createMcpSourcePlugin = ( · 30 unmodified lines
41 edit: LazyEditMcpSource,45 edit: LazyEditMcpSource,
42 signIn: LazyMcpSignInButton,46 signIn: LazyMcpSignInButton,
43 presets,47 presets,
48 preload: () => {
49 void importAdd();
50 void importEdit();
51 void importSignIn();
52 },
44 };53 };
45};54};
46 55
packages/plugins/openapi/src/react/source-plugin.ts+12−3
@@ -2,11 +2,20 @@ import { lazy } from "react"; · 1 unmodified line
2import type { SourcePlugin } from "@executor-js/sdk/client";2import type { SourcePlugin } from "@executor-js/sdk/client";
3import { openApiPresets } from "../sdk/presets";3import { openApiPresets } from "../sdk/presets";
4 4
5const importAdd = () => import("./AddOpenApiSource");
6const importEdit = () => import("./EditOpenApiSource");
7const importSummary = () => import("./OpenApiSourceSummary");
8
5export const openApiSourcePlugin: SourcePlugin = {9export const openApiSourcePlugin: SourcePlugin = {
6 key: "openapi",10 key: "openapi",
7 label: "OpenAPI",11 label: "OpenAPI",
8 add: lazy(() => import("./AddOpenApiSource")),12 add: lazy(importAdd),
9 edit: lazy(() => import("./EditOpenApiSource")),13 edit: lazy(importEdit),
10 summary: lazy(() => import("./OpenApiSourceSummary")),14 summary: lazy(importSummary),
11 presets: openApiPresets,15 presets: openApiPresets,
16 preload: () => {
17 void importAdd();
18 void importEdit();
19 void importSummary();
20 },
12};21};
packages/react/src/pages/sources-add.tsx+1−1
@@ -46,7 +46,7 @@ export function SourcesAddPage(props: { · 45 unmodified lines
46 return (46 return (
47 <div className="relative min-h-0 flex-1 overflow-y-auto">47 <div className="relative min-h-0 flex-1 overflow-y-auto">
48 <div className="mx-auto flex min-h-full max-w-4xl flex-col px-6 py-10 lg:px-10 lg:py-14">48 <div className="mx-auto flex min-h-full max-w-4xl flex-col px-6 py-10 lg:px-10 lg:py-14">
49 <Suspense fallback={<p className="text-sm text-muted-foreground">Loading…</p>}>49 <Suspense fallback={null}>
50 <AddComponent50 <AddComponent
51 initialUrl={url}51 initialUrl={url}
52 initialPreset={preset}52 initialPreset={preset}
packages/react/src/pages/sources.tsx+6−6
@@ -81,6 +81,12 @@ export function SourcesPage() { · 80 unmodified lines
81 </Button>81 </Button>
82 </div>82 </div>
83 83
84 <div className="mb-8">
85 <McpInstallCard />
86 </div>
87
88 <div className="mb-8 border-t border-border/50" />
89
84 {AsyncResult.match(sources, {90 {AsyncResult.match(sources, {
85 onInitial: () => <SourcesGridSkeleton />,91 onInitial: () => <SourcesGridSkeleton />,
86 onFailure: () => <p className="text-sm text-destructive">Failed to load sources</p>,92 onFailure: () => <p className="text-sm text-destructive">Failed to load sources</p>,
@@ -106,12 +112,6 @@ export function SourcesPage() { · 19 unmodified lines
106 );112 );
107 },113 },
108 })}114 })}
109
110 <div className="mb-8 border-t border-border/50" />
111
112 <div className="mb-8">
113 <McpInstallCard />
114 </div>
115 </div>115 </div>
116 116
117 <ConnectDialog open={connectOpen} onOpenChange={setConnectOpen} />117 <ConnectDialog open={connectOpen} onOpenChange={setConnectOpen} />