- 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.
Unified · Split
| @@ -13,6 +13,7 @@ import { · 12 unmodified lines | ||
| 13 | 13 | createContext, |
| 14 | 14 | createElement, |
| 15 | 15 | useContext, |
| 16 | + useEffect, | |
| 16 | 17 | useMemo, |
| 17 | 18 | type ComponentType, |
| 18 | 19 | type ReactNode, |
| @@ -130,6 +131,10 @@ export interface SourcePlugin { · 111 unmodified lines | ||
| 130 | 131 | readonly sourceId: string; |
| 131 | 132 | }>; |
| 132 | 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 | 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 | 282 | return createElement(ExecutorPluginsContext.Provider, { value }, children); |
| 273 | 283 | } |
| 274 | 284 | |
| @@ -2,12 +2,23 @@ import { lazy } from "react"; · 1 unmodified line | ||
| 2 | 2 | import type { SourcePlugin } from "@executor-js/sdk/client"; |
| 3 | 3 | import { googleDiscoveryPresets } from "../sdk/presets"; |
| 4 | 4 | |
| 5 | +const importAdd = () => import("./AddGoogleDiscoverySource"); | |
| 6 | +const importEdit = () => import("./EditGoogleDiscoverySource"); | |
| 7 | +const importSummary = () => import("./GoogleDiscoverySourceSummary"); | |
| 8 | +const importSignIn = () => import("./GoogleDiscoverySignInButton"); | |
| 9 | + | |
| 5 | 10 | export const googleDiscoverySourcePlugin: SourcePlugin = { |
| 6 | 11 | key: "googleDiscovery", |
| 7 | 12 | label: "Google Discovery", |
| 8 | − add: lazy(() => import("./AddGoogleDiscoverySource")), | |
| 9 | − edit: lazy(() => import("./EditGoogleDiscoverySource")), | |
| 10 | − summary: lazy(() => import("./GoogleDiscoverySourceSummary")), | |
| 11 | − signIn: lazy(() => import("./GoogleDiscoverySignInButton")), | |
| 13 | + add: lazy(importAdd), | |
| 14 | + edit: lazy(importEdit), | |
| 15 | + summary: lazy(importSummary), | |
| 16 | + signIn: lazy(importSignIn), | |
| 12 | 17 | presets: googleDiscoveryPresets, |
| 18 | + preload: () => { | |
| 19 | + void importAdd(); | |
| 20 | + void importEdit(); | |
| 21 | + void importSummary(); | |
| 22 | + void importSignIn(); | |
| 23 | + }, | |
| 13 | 24 | }; |
| @@ -2,12 +2,23 @@ import { lazy } from "react"; · 1 unmodified line | ||
| 2 | 2 | import type { SourcePlugin } from "@executor-js/sdk/client"; |
| 3 | 3 | import { graphqlPresets } from "../sdk/presets"; |
| 4 | 4 | |
| 5 | +const importAdd = () => import("./AddGraphqlSource"); | |
| 6 | +const importEdit = () => import("./EditGraphqlSource"); | |
| 7 | +const importSummary = () => import("./GraphqlSourceSummary"); | |
| 8 | +const importSignIn = () => import("./GraphqlSignInButton"); | |
| 9 | + | |
| 5 | 10 | export const graphqlSourcePlugin: SourcePlugin = { |
| 6 | 11 | key: "graphql", |
| 7 | 12 | label: "GraphQL", |
| 8 | − add: lazy(() => import("./AddGraphqlSource")), | |
| 9 | − edit: lazy(() => import("./EditGraphqlSource")), | |
| 10 | − summary: lazy(() => import("./GraphqlSourceSummary")), | |
| 11 | − signIn: lazy(() => import("./GraphqlSignInButton")), | |
| 13 | + add: lazy(importAdd), | |
| 14 | + edit: lazy(importEdit), | |
| 15 | + summary: lazy(importSummary), | |
| 16 | + signIn: lazy(importSignIn), | |
| 12 | 17 | presets: graphqlPresets, |
| 18 | + preload: () => { | |
| 19 | + void importAdd(); | |
| 20 | + void importEdit(); | |
| 21 | + void importSummary(); | |
| 22 | + void importSignIn(); | |
| 23 | + }, | |
| 13 | 24 | }; |
| @@ -2,9 +2,13 @@ import { lazy, type ComponentProps, type ComponentType } from "react"; · 1 unmodified line | ||
| 2 | 2 | import type { SourcePlugin } from "@executor-js/sdk/client"; |
| 3 | 3 | import { mcpPresets } from "../sdk/presets"; |
| 4 | 4 | |
| 5 | −const LazyAddMcpSource = lazy(() => import("./AddMcpSource")); | |
| 6 | −const LazyEditMcpSource = lazy(() => import("./EditMcpSource")); | |
| 7 | −const LazyMcpSignInButton = lazy(() => import("./McpSignInButton")); | |
| 5 | +const importAdd = () => import("./AddMcpSource"); | |
| 6 | +const importEdit = () => import("./EditMcpSource"); | |
| 7 | +const importSignIn = () => import("./McpSignInButton"); | |
| 8 | + | |
| 9 | +const LazyAddMcpSource = lazy(importAdd); | |
| 10 | +const LazyEditMcpSource = lazy(importEdit); | |
| 11 | +const LazyMcpSignInButton = lazy(importSignIn); | |
| 8 | 12 | |
| 9 | 13 | type AddProps = ComponentProps<SourcePlugin["add"]>; |
| 10 | 14 | |
| @@ -41,6 +45,11 @@ export const createMcpSourcePlugin = ( · 30 unmodified lines | ||
| 41 | 45 | edit: LazyEditMcpSource, |
| 42 | 46 | signIn: LazyMcpSignInButton, |
| 43 | 47 | presets, |
| 48 | + preload: () => { | |
| 49 | + void importAdd(); | |
| 50 | + void importEdit(); | |
| 51 | + void importSignIn(); | |
| 52 | + }, | |
| 44 | 53 | }; |
| 45 | 54 | }; |
| 46 | 55 | |
| @@ -2,11 +2,20 @@ import { lazy } from "react"; · 1 unmodified line | ||
| 2 | 2 | import type { SourcePlugin } from "@executor-js/sdk/client"; |
| 3 | 3 | import { openApiPresets } from "../sdk/presets"; |
| 4 | 4 | |
| 5 | +const importAdd = () => import("./AddOpenApiSource"); | |
| 6 | +const importEdit = () => import("./EditOpenApiSource"); | |
| 7 | +const importSummary = () => import("./OpenApiSourceSummary"); | |
| 8 | + | |
| 5 | 9 | export const openApiSourcePlugin: SourcePlugin = { |
| 6 | 10 | key: "openapi", |
| 7 | 11 | label: "OpenAPI", |
| 8 | − add: lazy(() => import("./AddOpenApiSource")), | |
| 9 | − edit: lazy(() => import("./EditOpenApiSource")), | |
| 10 | − summary: lazy(() => import("./OpenApiSourceSummary")), | |
| 12 | + add: lazy(importAdd), | |
| 13 | + edit: lazy(importEdit), | |
| 14 | + summary: lazy(importSummary), | |
| 11 | 15 | presets: openApiPresets, |
| 16 | + preload: () => { | |
| 17 | + void importAdd(); | |
| 18 | + void importEdit(); | |
| 19 | + void importSummary(); | |
| 20 | + }, | |
| 12 | 21 | }; |
| @@ -46,7 +46,7 @@ export function SourcesAddPage(props: { · 45 unmodified lines | ||
| 46 | 46 | return ( |
| 47 | 47 | <div className="relative min-h-0 flex-1 overflow-y-auto"> |
| 48 | 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 | 50 | <AddComponent |
| 51 | 51 | initialUrl={url} |
| 52 | 52 | initialPreset={preset} |
| @@ -81,6 +81,12 @@ export function SourcesPage() { · 80 unmodified lines | ||
| 81 | 81 | </Button> |
| 82 | 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 | 90 | {AsyncResult.match(sources, { |
| 85 | 91 | onInitial: () => <SourcesGridSkeleton />, |
| 86 | 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 | 115 | </div> |
| 116 | 116 | |
| 117 | 117 | <ConnectDialog open={connectOpen} onOpenChange={setConnectOpen} /> |