diff --git a/apps/cloud/package.json b/apps/cloud/package.json
index 8abbe215f..e6350881e 100644
--- a/apps/cloud/package.json
+++ b/apps/cloud/package.json
@@ -79,6 +79,7 @@
"@executor-js/cli": "workspace:*",
"@rhyssul/portless": "^0.13.0",
"@tailwindcss/vite": "catalog:",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
diff --git a/apps/cloud/scripts/build.mjs b/apps/cloud/scripts/build.mjs
index 57b58a12b..aeae418e3 100644
--- a/apps/cloud/scripts/build.mjs
+++ b/apps/cloud/scripts/build.mjs
@@ -18,7 +18,13 @@ console.log(`[build] VITE_PUBLIC_ANALYTICS_PATH=${process.env.VITE_PUBLIC_ANALYT
rmSync(new URL("../dist/", import.meta.url), { force: true, recursive: true });
-const steps = ["turbo run build --filter @executor-js/vite-plugin", "vite build"];
+const steps = [
+ // Workspace packages whose exports app code (or this very vite config)
+ // resolves from dist under Node — vite's config loader externalizes bare
+ // imports, so they must be built before vite starts.
+ "turbo run build --filter @executor-js/vite-plugin --filter @executor-js/react",
+ "vite build",
+];
for (const step of steps) {
const result = spawnSync(step, { stdio: "inherit", shell: true });
diff --git a/apps/cloud/src/routeTree.gen.ts b/apps/cloud/src/routeTree.gen.ts
index 96a9354e5..3cbc16262 100644
--- a/apps/cloud/src/routeTree.gen.ts
+++ b/apps/cloud/src/routeTree.gen.ts
@@ -9,25 +9,26 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as ToolsRouteImport } from './routes/tools'
-import { Route as SetupMcpRouteImport } from './routes/setup-mcp'
-import { Route as SecretsRouteImport } from './routes/secrets'
-import { Route as PoliciesRouteImport } from './routes/policies'
-import { Route as OrgRouteImport } from './routes/org'
-import { Route as CreateOrgRouteImport } from './routes/create-org'
-import { Route as BillingRouteImport } from './routes/billing'
-import { Route as ApiKeysRouteImport } from './routes/api-keys'
-import { Route as IndexRouteImport } from './routes/index'
-import { Route as ResumeExecutionIdRouteImport } from './routes/resume.$executionId'
-import { Route as IntegrationsNamespaceRouteImport } from './routes/integrations.$namespace'
-import { Route as BillingPlansRouteImport } from './routes/billing_.plans'
-import { Route as IntegrationsAddPluginKeyRouteImport } from './routes/integrations.add.$pluginKey'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools'
+import { Route as SetupMcpRouteImport } from './routes/app/setup-mcp'
+import { Route as SecretsRouteImport } from './routes/app/secrets'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies'
+import { Route as OrgRouteImport } from './routes/app/org'
+import { Route as CreateOrgRouteImport } from './routes/app/create-org'
+import { Route as BillingRouteImport } from './routes/app/billing'
+import { Route as ApiKeysRouteImport } from './routes/app/api-keys'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index'
+import { Route as ResumeDotexecutionIdRouteImport } from './routes/app/resume.$executionId'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace'
+import { Route as Billing_DotplansRouteImport } from './routes/app/billing_.plans'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey'
-const ToolsRoute = ToolsRouteImport.update({
- id: '/tools',
- path: '/tools',
- getParentRoute: () => rootRouteImport,
-} as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport.update({
+ id: '/tools',
+ path: '/tools',
+ getParentRoute: () => rootRouteImport,
+ } as any)
const SetupMcpRoute = SetupMcpRouteImport.update({
id: '/setup-mcp',
path: '/setup-mcp',
@@ -38,11 +39,12 @@ const SecretsRoute = SecretsRouteImport.update({
path: '/secrets',
getParentRoute: () => rootRouteImport,
} as any)
-const PoliciesRoute = PoliciesRouteImport.update({
- id: '/policies',
- path: '/policies',
- getParentRoute: () => rootRouteImport,
-} as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport.update({
+ id: '/policies',
+ path: '/policies',
+ getParentRoute: () => rootRouteImport,
+ } as any)
const OrgRoute = OrgRouteImport.update({
id: '/org',
path: '/org',
@@ -63,78 +65,84 @@ const ApiKeysRoute = ApiKeysRouteImport.update({
path: '/api-keys',
getParentRoute: () => rootRouteImport,
} as any)
-const IndexRoute = IndexRouteImport.update({
- id: '/',
- path: '/',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const ResumeDotexecutionIdRoute = ResumeDotexecutionIdRouteImport.update({
id: '/resume/$executionId',
path: '/resume/$executionId',
getParentRoute: () => rootRouteImport,
} as any)
-const IntegrationsNamespaceRoute = IntegrationsNamespaceRouteImport.update({
- id: '/integrations/$namespace',
- path: '/integrations/$namespace',
- getParentRoute: () => rootRouteImport,
-} as any)
-const BillingPlansRoute = BillingPlansRouteImport.update({
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport.update(
+ {
+ id: '/integrations/$namespace',
+ path: '/integrations/$namespace',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const Billing_DotplansRoute = Billing_DotplansRouteImport.update({
id: '/billing_/plans',
path: '/billing/plans',
getParentRoute: () => rootRouteImport,
} as any)
-const IntegrationsAddPluginKeyRoute =
- IntegrationsAddPluginKeyRouteImport.update({
- id: '/integrations/add/$pluginKey',
- path: '/integrations/add/$pluginKey',
- getParentRoute: () => rootRouteImport,
- } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update(
+ {
+ id: '/integrations/add/$pluginKey',
+ path: '/integrations/add/$pluginKey',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
export interface FileRoutesByFullPath {
- '/': typeof IndexRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
'/api-keys': typeof ApiKeysRoute
'/billing': typeof BillingRoute
'/create-org': typeof CreateOrgRoute
'/org': typeof OrgRoute
- '/policies': typeof PoliciesRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
'/secrets': typeof SecretsRoute
'/setup-mcp': typeof SetupMcpRoute
- '/tools': typeof ToolsRoute
- '/billing/plans': typeof BillingPlansRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/billing/plans': typeof Billing_DotplansRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof ResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
}
export interface FileRoutesByTo {
- '/': typeof IndexRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
'/api-keys': typeof ApiKeysRoute
'/billing': typeof BillingRoute
'/create-org': typeof CreateOrgRoute
'/org': typeof OrgRoute
- '/policies': typeof PoliciesRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
'/secrets': typeof SecretsRoute
'/setup-mcp': typeof SetupMcpRoute
- '/tools': typeof ToolsRoute
- '/billing/plans': typeof BillingPlansRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/billing/plans': typeof Billing_DotplansRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof ResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
- '/': typeof IndexRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
'/api-keys': typeof ApiKeysRoute
'/billing': typeof BillingRoute
'/create-org': typeof CreateOrgRoute
'/org': typeof OrgRoute
- '/policies': typeof PoliciesRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
'/secrets': typeof SecretsRoute
'/setup-mcp': typeof SetupMcpRoute
- '/tools': typeof ToolsRoute
- '/billing_/plans': typeof BillingPlansRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/billing_/plans': typeof Billing_DotplansRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof ResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -185,19 +193,19 @@ export interface FileRouteTypes {
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
- IndexRoute: typeof IndexRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
ApiKeysRoute: typeof ApiKeysRoute
BillingRoute: typeof BillingRoute
CreateOrgRoute: typeof CreateOrgRoute
OrgRoute: typeof OrgRoute
- PoliciesRoute: typeof PoliciesRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
SecretsRoute: typeof SecretsRoute
SetupMcpRoute: typeof SetupMcpRoute
- ToolsRoute: typeof ToolsRoute
- BillingPlansRoute: typeof BillingPlansRoute
- IntegrationsNamespaceRoute: typeof IntegrationsNamespaceRoute
- ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute
- IntegrationsAddPluginKeyRoute: typeof IntegrationsAddPluginKeyRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ Billing_DotplansRoute: typeof Billing_DotplansRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ ResumeDotexecutionIdRoute: typeof ResumeDotexecutionIdRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
}
declare module '@tanstack/react-router' {
@@ -206,7 +214,7 @@ declare module '@tanstack/react-router' {
id: '/tools'
path: '/tools'
fullPath: '/tools'
- preLoaderRoute: typeof ToolsRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport
parentRoute: typeof rootRouteImport
}
'/setup-mcp': {
@@ -227,7 +235,7 @@ declare module '@tanstack/react-router' {
id: '/policies'
path: '/policies'
fullPath: '/policies'
- preLoaderRoute: typeof PoliciesRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport
parentRoute: typeof rootRouteImport
}
'/org': {
@@ -262,54 +270,59 @@ declare module '@tanstack/react-router' {
id: '/'
path: '/'
fullPath: '/'
- preLoaderRoute: typeof IndexRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/resume/$executionId': {
id: '/resume/$executionId'
path: '/resume/$executionId'
fullPath: '/resume/$executionId'
- preLoaderRoute: typeof ResumeExecutionIdRouteImport
+ preLoaderRoute: typeof ResumeDotexecutionIdRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/$namespace': {
id: '/integrations/$namespace'
path: '/integrations/$namespace'
fullPath: '/integrations/$namespace'
- preLoaderRoute: typeof IntegrationsNamespaceRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport
parentRoute: typeof rootRouteImport
}
'/billing_/plans': {
id: '/billing_/plans'
path: '/billing/plans'
fullPath: '/billing/plans'
- preLoaderRoute: typeof BillingPlansRouteImport
+ preLoaderRoute: typeof Billing_DotplansRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/add/$pluginKey': {
id: '/integrations/add/$pluginKey'
path: '/integrations/add/$pluginKey'
fullPath: '/integrations/add/$pluginKey'
- preLoaderRoute: typeof IntegrationsAddPluginKeyRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
- IndexRoute: IndexRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute,
ApiKeysRoute: ApiKeysRoute,
BillingRoute: BillingRoute,
CreateOrgRoute: CreateOrgRoute,
OrgRoute: OrgRoute,
- PoliciesRoute: PoliciesRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute,
SecretsRoute: SecretsRoute,
SetupMcpRoute: SetupMcpRoute,
- ToolsRoute: ToolsRoute,
- BillingPlansRoute: BillingPlansRoute,
- IntegrationsNamespaceRoute: IntegrationsNamespaceRoute,
- ResumeExecutionIdRoute: ResumeExecutionIdRoute,
- IntegrationsAddPluginKeyRoute: IntegrationsAddPluginKeyRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute,
+ Billing_DotplansRoute: Billing_DotplansRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute,
+ ResumeDotexecutionIdRoute: ResumeDotexecutionIdRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/apps/cloud/src/routes/api-keys.tsx b/apps/cloud/src/routes/app/api-keys.tsx
similarity index 100%
rename from apps/cloud/src/routes/api-keys.tsx
rename to apps/cloud/src/routes/app/api-keys.tsx
diff --git a/apps/cloud/src/routes/billing.tsx b/apps/cloud/src/routes/app/billing.tsx
similarity index 100%
rename from apps/cloud/src/routes/billing.tsx
rename to apps/cloud/src/routes/app/billing.tsx
diff --git a/apps/cloud/src/routes/billing_.plans.tsx b/apps/cloud/src/routes/app/billing_.plans.tsx
similarity index 100%
rename from apps/cloud/src/routes/billing_.plans.tsx
rename to apps/cloud/src/routes/app/billing_.plans.tsx
diff --git a/apps/cloud/src/routes/create-org.tsx b/apps/cloud/src/routes/app/create-org.tsx
similarity index 70%
rename from apps/cloud/src/routes/create-org.tsx
rename to apps/cloud/src/routes/app/create-org.tsx
index e56083a03..8a8e6133d 100644
--- a/apps/cloud/src/routes/create-org.tsx
+++ b/apps/cloud/src/routes/app/create-org.tsx
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
-import { CreateOrgPage } from "../web/pages/create-org";
+import { CreateOrgPage } from "../../web/pages/create-org";
export const Route = createFileRoute("/create-org")({
component: CreateOrgPage,
diff --git a/apps/cloud/src/routes/org.tsx b/apps/cloud/src/routes/app/org.tsx
similarity index 99%
rename from apps/cloud/src/routes/org.tsx
rename to apps/cloud/src/routes/app/org.tsx
index 0f0c336bd..39a57774b 100644
--- a/apps/cloud/src/routes/org.tsx
+++ b/apps/cloud/src/routes/app/org.tsx
@@ -15,7 +15,7 @@ import {
DropdownMenuTrigger,
} from "@executor-js/react/components/dropdown-menu";
import { OrgPage as SharedOrgPage } from "@executor-js/react/pages/org";
-import { orgDomainsAtom, getDomainVerificationLink, deleteDomain } from "../web/org-atoms";
+import { orgDomainsAtom, getDomainVerificationLink, deleteDomain } from "../../web/org-atoms";
// ---------------------------------------------------------------------------
// Cloud organization page. The members / roles / invite / org-name surface is
diff --git a/apps/cloud/src/routes/resume.$executionId.tsx b/apps/cloud/src/routes/app/resume.$executionId.tsx
similarity index 95%
rename from apps/cloud/src/routes/resume.$executionId.tsx
rename to apps/cloud/src/routes/app/resume.$executionId.tsx
index 17dec4d06..d9d8eccc3 100644
--- a/apps/cloud/src/routes/resume.$executionId.tsx
+++ b/apps/cloud/src/routes/app/resume.$executionId.tsx
@@ -7,7 +7,7 @@ import {
ResumeApprovalPageView,
} from "@executor-js/react/pages/resume-approval";
-import { mcpPausedExecutionAtom, resumeMcpExecution } from "../web/mcp-resume-atoms";
+import { mcpPausedExecutionAtom, resumeMcpExecution } from "../../web/mcp-resume-atoms";
const SearchParams = Schema.toStandardSchemaV1(
Schema.Struct({
diff --git a/apps/cloud/src/routes/secrets.tsx b/apps/cloud/src/routes/app/secrets.tsx
similarity index 100%
rename from apps/cloud/src/routes/secrets.tsx
rename to apps/cloud/src/routes/app/secrets.tsx
diff --git a/apps/cloud/src/routes/setup-mcp.tsx b/apps/cloud/src/routes/app/setup-mcp.tsx
similarity index 71%
rename from apps/cloud/src/routes/setup-mcp.tsx
rename to apps/cloud/src/routes/app/setup-mcp.tsx
index 3af902d9f..b249fbfbf 100644
--- a/apps/cloud/src/routes/setup-mcp.tsx
+++ b/apps/cloud/src/routes/app/setup-mcp.tsx
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
-import { SetupMcpPage } from "../web/pages/setup-mcp";
+import { SetupMcpPage } from "../../web/pages/setup-mcp";
export const Route = createFileRoute("/setup-mcp")({
component: SetupMcpPage,
diff --git a/apps/cloud/src/routes/index.tsx b/apps/cloud/src/routes/index.tsx
deleted file mode 100644
index 2d57f82f0..000000000
--- a/apps/cloud/src/routes/index.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationsPage } from "@executor-js/react/pages/integrations";
-
-export const Route = createFileRoute("/")({
- component: IntegrationsPage,
-});
diff --git a/apps/cloud/src/routes/integrations.$namespace.tsx b/apps/cloud/src/routes/integrations.$namespace.tsx
deleted file mode 100644
index 49a458104..000000000
--- a/apps/cloud/src/routes/integrations.$namespace.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail";
-
-export const Route = createFileRoute("/integrations/$namespace")({
- component: () => {
- const { namespace } = Route.useParams();
- return ;
- },
-});
diff --git a/apps/cloud/src/routes/integrations.add.$pluginKey.tsx b/apps/cloud/src/routes/integrations.add.$pluginKey.tsx
deleted file mode 100644
index cdf2b8a8a..000000000
--- a/apps/cloud/src/routes/integrations.add.$pluginKey.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Schema } from "effect";
-import { createFileRoute } from "@tanstack/react-router";
-import { AddIntegrationPage } from "@executor-js/react/pages/integration-add";
-
-const SearchParams = Schema.toStandardSchemaV1(
- Schema.Struct({
- url: Schema.optional(Schema.String),
- preset: Schema.optional(Schema.String),
- namespace: Schema.optional(Schema.String),
- }),
-);
-
-export const Route = createFileRoute("/integrations/add/$pluginKey")({
- validateSearch: SearchParams,
- component: () => {
- const { pluginKey } = Route.useParams();
- const { url, preset, namespace } = Route.useSearch();
- return (
-
- );
- },
-});
diff --git a/apps/cloud/src/routes/policies.tsx b/apps/cloud/src/routes/policies.tsx
deleted file mode 100644
index a9de9ff6f..000000000
--- a/apps/cloud/src/routes/policies.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { PoliciesPage } from "@executor-js/react/pages/policies";
-
-export const Route = createFileRoute("/policies")({
- component: () => ,
-});
diff --git a/apps/cloud/src/routes/tools.tsx b/apps/cloud/src/routes/tools.tsx
deleted file mode 100644
index 25929fd2b..000000000
--- a/apps/cloud/src/routes/tools.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { ToolsPage } from "@executor-js/react/pages/tools";
-
-export const Route = createFileRoute("/tools")({
- component: ToolsPage,
-});
diff --git a/apps/cloud/vite.config.ts b/apps/cloud/vite.config.ts
index 72fd75d69..a8b453cab 100644
--- a/apps/cloud/vite.config.ts
+++ b/apps/cloud/vite.config.ts
@@ -2,6 +2,8 @@ import { fileURLToPath } from "node:url";
import { defineConfig, loadEnv, type Plugin } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
+import { physical, rootRoute } from "@tanstack/virtual-file-routes";
+import { consoleRoutes } from "@executor-js/react/console-routes";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import executorVitePlugin from "@executor-js/vite-plugin";
@@ -67,7 +69,24 @@ export default defineConfig(({ mode }) => {
tailwindcss(),
executorVitePlugin(),
cloudflare({ viteEnvironment: { name: "ssr" }, inspectorPort: false }),
- tanstackStart(),
+ tanstackStart({
+ // Shared console routes come from @executor-js/react (see its
+ // console-routes.ts); cloud owns its root (WorkOS auth + billing
+ // shell) and the cloud-specific routes under src/routes/app.
+ // Excluded shared paths are intentional divergence: cloud's
+ // /secrets redirects to / (credential storage is product plumbing
+ // here), its /resume page is the cloud variant, and client plugin
+ // pages aren't wired up on cloud.
+ router: {
+ virtualRouteConfig: rootRoute("__root.tsx", [
+ ...consoleRoutes({
+ dir: "../../../../packages/react/src/routes",
+ exclude: ["/secrets", "/resume/$executionId", "/plugins/$pluginId/$"],
+ }),
+ physical("", "app"),
+ ]),
+ },
+ }),
react(),
],
};
diff --git a/apps/host-selfhost/package.json b/apps/host-selfhost/package.json
index 11726031c..f52a6b019 100644
--- a/apps/host-selfhost/package.json
+++ b/apps/host-selfhost/package.json
@@ -47,6 +47,7 @@
"@executor-js/vite-plugin": "workspace:*",
"@tailwindcss/vite": "catalog:",
"@tanstack/router-plugin": "^1.167.12",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
diff --git a/apps/host-selfhost/vite.config.ts b/apps/host-selfhost/vite.config.ts
index d921ecb74..7c5bafad9 100644
--- a/apps/host-selfhost/vite.config.ts
+++ b/apps/host-selfhost/vite.config.ts
@@ -5,6 +5,8 @@ import { defineConfig, type Plugin } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
+import { physical, rootRoute } from "@tanstack/virtual-file-routes";
+import { consoleRoutes } from "@executor-js/react/console-routes";
import executorVitePlugin from "@executor-js/vite-plugin";
// Self-host web SPA. Mirrors @executor-js/app's vite plugin bundle, but points
@@ -134,6 +136,14 @@ export default defineConfig({
autoCodeSplitting: true,
routesDirectory: fileURLToPath(new URL("./web/routes", import.meta.url)),
generatedRouteTree: fileURLToPath(new URL("./web/routeTree.gen.ts", import.meta.url)),
+ // Shared console routes come from @executor-js/react (see its
+ // console-routes.ts); this app owns its root (the Better Auth shell)
+ // and the self-host-specific routes under web/routes/app (admin,
+ // api-keys, invite join).
+ virtualRouteConfig: rootRoute("__root.tsx", [
+ ...consoleRoutes({ dir: "../../../../packages/react/src/routes" }),
+ physical("", "app"),
+ ]),
}),
...react(),
],
diff --git a/apps/host-selfhost/web/routeTree.gen.ts b/apps/host-selfhost/web/routeTree.gen.ts
index a53ccbec3..4c5e12f7a 100644
--- a/apps/host-selfhost/web/routeTree.gen.ts
+++ b/apps/host-selfhost/web/routeTree.gen.ts
@@ -9,35 +9,36 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as ToolsRouteImport } from './routes/tools'
-import { Route as SecretsRouteImport } from './routes/secrets'
-import { Route as PoliciesRouteImport } from './routes/policies'
-import { Route as ApiKeysRouteImport } from './routes/api-keys'
-import { Route as AdminRouteImport } from './routes/admin'
-import { Route as IndexRouteImport } from './routes/index'
-import { Route as SourcesNamespaceRouteImport } from './routes/sources.$namespace'
-import { Route as ResumeExecutionIdRouteImport } from './routes/resume.$executionId'
-import { Route as JoinCodeRouteImport } from './routes/join.$code'
-import { Route as IntegrationsNamespaceRouteImport } from './routes/integrations.$namespace'
-import { Route as SourcesAddPluginKeyRouteImport } from './routes/sources.add.$pluginKey'
-import { Route as PluginsPluginIdSplatRouteImport } from './routes/plugins.$pluginId.$'
-import { Route as IntegrationsAddPluginKeyRouteImport } from './routes/integrations.add.$pluginKey'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport } from './../../../packages/react/src/routes/tools'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies'
+import { Route as ApiKeysRouteImport } from './routes/app/api-keys'
+import { Route as AdminRouteImport } from './routes/app/admin'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport } from './../../../packages/react/src/routes/index'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId'
+import { Route as JoinDotcodeRouteImport } from './routes/app/join.$code'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$'
+import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey'
-const ToolsRoute = ToolsRouteImport.update({
- id: '/tools',
- path: '/tools',
- getParentRoute: () => rootRouteImport,
-} as any)
-const SecretsRoute = SecretsRouteImport.update({
- id: '/secrets',
- path: '/secrets',
- getParentRoute: () => rootRouteImport,
-} as any)
-const PoliciesRoute = PoliciesRouteImport.update({
- id: '/policies',
- path: '/policies',
- getParentRoute: () => rootRouteImport,
-} as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport.update({
+ id: '/tools',
+ path: '/tools',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport.update({
+ id: '/secrets',
+ path: '/secrets',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport.update({
+ id: '/policies',
+ path: '/policies',
+ getParentRoute: () => rootRouteImport,
+ } as any)
const ApiKeysRoute = ApiKeysRouteImport.update({
id: '/api-keys',
path: '/api-keys',
@@ -48,93 +49,89 @@ const AdminRoute = AdminRouteImport.update({
path: '/admin',
getParentRoute: () => rootRouteImport,
} as any)
-const IndexRoute = IndexRouteImport.update({
- id: '/',
- path: '/',
- getParentRoute: () => rootRouteImport,
-} as any)
-const SourcesNamespaceRoute = SourcesNamespaceRouteImport.update({
- id: '/sources/$namespace',
- path: '/sources/$namespace',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({
- id: '/resume/$executionId',
- path: '/resume/$executionId',
- getParentRoute: () => rootRouteImport,
-} as any)
-const JoinCodeRoute = JoinCodeRouteImport.update({
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport.update(
+ {
+ id: '/resume/$executionId',
+ path: '/resume/$executionId',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const JoinDotcodeRoute = JoinDotcodeRouteImport.update({
id: '/join/$code',
path: '/join/$code',
getParentRoute: () => rootRouteImport,
} as any)
-const IntegrationsNamespaceRoute = IntegrationsNamespaceRouteImport.update({
- id: '/integrations/$namespace',
- path: '/integrations/$namespace',
- getParentRoute: () => rootRouteImport,
-} as any)
-const SourcesAddPluginKeyRoute = SourcesAddPluginKeyRouteImport.update({
- id: '/sources/add/$pluginKey',
- path: '/sources/add/$pluginKey',
- getParentRoute: () => rootRouteImport,
-} as any)
-const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({
- id: '/plugins/$pluginId/$',
- path: '/plugins/$pluginId/$',
- getParentRoute: () => rootRouteImport,
-} as any)
-const IntegrationsAddPluginKeyRoute =
- IntegrationsAddPluginKeyRouteImport.update({
- id: '/integrations/add/$pluginKey',
- path: '/integrations/add/$pluginKey',
- getParentRoute: () => rootRouteImport,
- } as any)
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport.update(
+ {
+ id: '/integrations/$namespace',
+ path: '/integrations/$namespace',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update(
+ {
+ id: '/plugins/$pluginId/$',
+ path: '/plugins/$pluginId/$',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
+const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute =
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update(
+ {
+ id: '/integrations/add/$pluginKey',
+ path: '/integrations/add/$pluginKey',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
export interface FileRoutesByFullPath {
- '/': typeof IndexRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
'/admin': typeof AdminRoute
'/api-keys': typeof ApiKeysRoute
- '/policies': typeof PoliciesRoute
- '/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/join/$code': typeof JoinCodeRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/sources/$namespace': typeof SourcesNamespaceRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
- '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/join/$code': typeof JoinDotcodeRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRoutesByTo {
- '/': typeof IndexRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
'/admin': typeof AdminRoute
'/api-keys': typeof ApiKeysRoute
- '/policies': typeof PoliciesRoute
- '/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/join/$code': typeof JoinCodeRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/sources/$namespace': typeof SourcesNamespaceRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
- '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/join/$code': typeof JoinDotcodeRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
- '/': typeof IndexRoute
+ '/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
'/admin': typeof AdminRoute
'/api-keys': typeof ApiKeysRoute
- '/policies': typeof PoliciesRoute
- '/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/join/$code': typeof JoinCodeRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/sources/$namespace': typeof SourcesNamespaceRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
- '/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
+ '/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ '/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ '/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/join/$code': typeof JoinDotcodeRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -148,10 +145,8 @@ export interface FileRouteTypes {
| '/integrations/$namespace'
| '/join/$code'
| '/resume/$executionId'
- | '/sources/$namespace'
| '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
- | '/sources/add/$pluginKey'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -163,10 +158,8 @@ export interface FileRouteTypes {
| '/integrations/$namespace'
| '/join/$code'
| '/resume/$executionId'
- | '/sources/$namespace'
| '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
- | '/sources/add/$pluginKey'
id:
| '__root__'
| '/'
@@ -178,26 +171,22 @@ export interface FileRouteTypes {
| '/integrations/$namespace'
| '/join/$code'
| '/resume/$executionId'
- | '/sources/$namespace'
| '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
- | '/sources/add/$pluginKey'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
- IndexRoute: typeof IndexRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute
AdminRoute: typeof AdminRoute
ApiKeysRoute: typeof ApiKeysRoute
- PoliciesRoute: typeof PoliciesRoute
- SecretsRoute: typeof SecretsRoute
- ToolsRoute: typeof ToolsRoute
- IntegrationsNamespaceRoute: typeof IntegrationsNamespaceRoute
- JoinCodeRoute: typeof JoinCodeRoute
- ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute
- SourcesNamespaceRoute: typeof SourcesNamespaceRoute
- IntegrationsAddPluginKeyRoute: typeof IntegrationsAddPluginKeyRoute
- PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute
- SourcesAddPluginKeyRoute: typeof SourcesAddPluginKeyRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute
+ JoinDotcodeRoute: typeof JoinDotcodeRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
declare module '@tanstack/react-router' {
@@ -206,21 +195,21 @@ declare module '@tanstack/react-router' {
id: '/tools'
path: '/tools'
fullPath: '/tools'
- preLoaderRoute: typeof ToolsRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport
parentRoute: typeof rootRouteImport
}
'/secrets': {
id: '/secrets'
path: '/secrets'
fullPath: '/secrets'
- preLoaderRoute: typeof SecretsRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport
parentRoute: typeof rootRouteImport
}
'/policies': {
id: '/policies'
path: '/policies'
fullPath: '/policies'
- preLoaderRoute: typeof PoliciesRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport
parentRoute: typeof rootRouteImport
}
'/api-keys': {
@@ -241,75 +230,67 @@ declare module '@tanstack/react-router' {
id: '/'
path: '/'
fullPath: '/'
- preLoaderRoute: typeof IndexRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/sources/$namespace': {
- id: '/sources/$namespace'
- path: '/sources/$namespace'
- fullPath: '/sources/$namespace'
- preLoaderRoute: typeof SourcesNamespaceRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/resume/$executionId': {
id: '/resume/$executionId'
path: '/resume/$executionId'
fullPath: '/resume/$executionId'
- preLoaderRoute: typeof ResumeExecutionIdRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport
parentRoute: typeof rootRouteImport
}
'/join/$code': {
id: '/join/$code'
path: '/join/$code'
fullPath: '/join/$code'
- preLoaderRoute: typeof JoinCodeRouteImport
+ preLoaderRoute: typeof JoinDotcodeRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/$namespace': {
id: '/integrations/$namespace'
path: '/integrations/$namespace'
fullPath: '/integrations/$namespace'
- preLoaderRoute: typeof IntegrationsNamespaceRouteImport
- parentRoute: typeof rootRouteImport
- }
- '/sources/add/$pluginKey': {
- id: '/sources/add/$pluginKey'
- path: '/sources/add/$pluginKey'
- fullPath: '/sources/add/$pluginKey'
- preLoaderRoute: typeof SourcesAddPluginKeyRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport
parentRoute: typeof rootRouteImport
}
'/plugins/$pluginId/$': {
id: '/plugins/$pluginId/$'
path: '/plugins/$pluginId/$'
fullPath: '/plugins/$pluginId/$'
- preLoaderRoute: typeof PluginsPluginIdSplatRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/add/$pluginKey': {
id: '/integrations/add/$pluginKey'
path: '/integrations/add/$pluginKey'
fullPath: '/integrations/add/$pluginKey'
- preLoaderRoute: typeof IntegrationsAddPluginKeyRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
- IndexRoute: IndexRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute,
AdminRoute: AdminRoute,
ApiKeysRoute: ApiKeysRoute,
- PoliciesRoute: PoliciesRoute,
- SecretsRoute: SecretsRoute,
- ToolsRoute: ToolsRoute,
- IntegrationsNamespaceRoute: IntegrationsNamespaceRoute,
- JoinCodeRoute: JoinCodeRoute,
- ResumeExecutionIdRoute: ResumeExecutionIdRoute,
- SourcesNamespaceRoute: SourcesNamespaceRoute,
- IntegrationsAddPluginKeyRoute: IntegrationsAddPluginKeyRoute,
- PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute,
- SourcesAddPluginKeyRoute: SourcesAddPluginKeyRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute,
+ JoinDotcodeRoute: JoinDotcodeRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute,
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute:
+ DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/apps/host-selfhost/web/routes/admin.tsx b/apps/host-selfhost/web/routes/app/admin.tsx
similarity index 99%
rename from apps/host-selfhost/web/routes/admin.tsx
rename to apps/host-selfhost/web/routes/app/admin.tsx
index 89753082f..4009f5671 100644
--- a/apps/host-selfhost/web/routes/admin.tsx
+++ b/apps/host-selfhost/web/routes/app/admin.tsx
@@ -17,7 +17,7 @@ import {
} from "@executor-js/react/api/account-atoms";
import { orgMemberWriteKeys } from "@executor-js/react/api/reactivity-keys";
-import { createInvite, invitesAtom, inviteWriteKeys, revokeInvite } from "../admin-atoms";
+import { createInvite, invitesAtom, inviteWriteKeys, revokeInvite } from "../../admin-atoms";
export const Route = createFileRoute("/admin")({
component: AdminPage,
diff --git a/apps/host-selfhost/web/routes/api-keys.tsx b/apps/host-selfhost/web/routes/app/api-keys.tsx
similarity index 100%
rename from apps/host-selfhost/web/routes/api-keys.tsx
rename to apps/host-selfhost/web/routes/app/api-keys.tsx
diff --git a/apps/host-selfhost/web/routes/join.$code.tsx b/apps/host-selfhost/web/routes/app/join.$code.tsx
similarity index 98%
rename from apps/host-selfhost/web/routes/join.$code.tsx
rename to apps/host-selfhost/web/routes/app/join.$code.tsx
index f8394bb53..26b757f94 100644
--- a/apps/host-selfhost/web/routes/join.$code.tsx
+++ b/apps/host-selfhost/web/routes/app/join.$code.tsx
@@ -5,7 +5,7 @@ import { Button } from "@executor-js/react/components/button";
import { Input } from "@executor-js/react/components/input";
import { Label } from "@executor-js/react/components/label";
-import { authClient } from "../auth-client";
+import { authClient } from "../../auth-client";
export const Route = createFileRoute("/join/$code")({
component: JoinPage,
diff --git a/apps/host-selfhost/web/routes/index.tsx b/apps/host-selfhost/web/routes/index.tsx
deleted file mode 100644
index 2d57f82f0..000000000
--- a/apps/host-selfhost/web/routes/index.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationsPage } from "@executor-js/react/pages/integrations";
-
-export const Route = createFileRoute("/")({
- component: IntegrationsPage,
-});
diff --git a/apps/host-selfhost/web/routes/integrations.$namespace.tsx b/apps/host-selfhost/web/routes/integrations.$namespace.tsx
deleted file mode 100644
index 49a458104..000000000
--- a/apps/host-selfhost/web/routes/integrations.$namespace.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail";
-
-export const Route = createFileRoute("/integrations/$namespace")({
- component: () => {
- const { namespace } = Route.useParams();
- return ;
- },
-});
diff --git a/apps/host-selfhost/web/routes/integrations.add.$pluginKey.tsx b/apps/host-selfhost/web/routes/integrations.add.$pluginKey.tsx
deleted file mode 100644
index cdf2b8a8a..000000000
--- a/apps/host-selfhost/web/routes/integrations.add.$pluginKey.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Schema } from "effect";
-import { createFileRoute } from "@tanstack/react-router";
-import { AddIntegrationPage } from "@executor-js/react/pages/integration-add";
-
-const SearchParams = Schema.toStandardSchemaV1(
- Schema.Struct({
- url: Schema.optional(Schema.String),
- preset: Schema.optional(Schema.String),
- namespace: Schema.optional(Schema.String),
- }),
-);
-
-export const Route = createFileRoute("/integrations/add/$pluginKey")({
- validateSearch: SearchParams,
- component: () => {
- const { pluginKey } = Route.useParams();
- const { url, preset, namespace } = Route.useSearch();
- return (
-
- );
- },
-});
diff --git a/apps/host-selfhost/web/routes/plugins.$pluginId.$.tsx b/apps/host-selfhost/web/routes/plugins.$pluginId.$.tsx
deleted file mode 100644
index 64f59a40a..000000000
--- a/apps/host-selfhost/web/routes/plugins.$pluginId.$.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createFileRoute, notFound } from "@tanstack/react-router";
-import { useClientPlugins } from "@executor-js/sdk/client";
-
-// /plugins// — mounts pages contributed by client plugins,
-// materialised from `virtual:executor/plugins-client` via the root's
-// ExecutorPluginsProvider. Adding a plugin to executor.config.ts is enough.
-
-export const Route = createFileRoute("/plugins/$pluginId/$")({
- component: PluginRouteComponent,
-});
-
-function normalizePath(input: string): string {
- if (!input || input === "/") return "/";
- return input.startsWith("/") ? input : `/${input}`;
-}
-
-function PluginRouteComponent() {
- const { pluginId, _splat: rest } = Route.useParams();
- const plugins = useClientPlugins();
- const plugin = plugins.find((p) => p.id === pluginId);
- // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router represents not-found from components by throwing notFound()
- if (!plugin) throw notFound();
-
- const target = normalizePath(rest ?? "/");
- const page = plugin.pages?.find((p) => normalizePath(p.path) === target);
- // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router represents not-found from components by throwing notFound()
- if (!page) throw notFound();
-
- const Component = page.component;
- return ;
-}
diff --git a/apps/host-selfhost/web/routes/policies.tsx b/apps/host-selfhost/web/routes/policies.tsx
deleted file mode 100644
index a9de9ff6f..000000000
--- a/apps/host-selfhost/web/routes/policies.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { PoliciesPage } from "@executor-js/react/pages/policies";
-
-export const Route = createFileRoute("/policies")({
- component: () => ,
-});
diff --git a/apps/host-selfhost/web/routes/resume.$executionId.tsx b/apps/host-selfhost/web/routes/resume.$executionId.tsx
deleted file mode 100644
index 32a84347b..000000000
--- a/apps/host-selfhost/web/routes/resume.$executionId.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { useCallback } from "react";
-import { useAtomSet, useAtomValue } from "@effect/atom-react";
-import { Data, Effect, Option, Schema } from "effect";
-import * as Atom from "effect/unstable/reactivity/Atom";
-import { createFileRoute } from "@tanstack/react-router";
-import {
- ResumeApprovalPage,
- ResumeApprovalPageView,
-} from "@executor-js/react/pages/resume-approval";
-import { pausedExecutionAtom } from "@executor-js/react/api/atoms";
-import type { ElicitationAction } from "@executor-js/react/components/elicitation-approval";
-
-const SearchParams = Schema.toStandardSchemaV1(
- Schema.Struct({
- mcp_session_id: Schema.optional(Schema.String),
- }),
-);
-const LocalMcpResumeCompleted = Schema.Struct({
- status: Schema.Literal("completed"),
- text: Schema.String,
- structured: Schema.Unknown,
- isError: Schema.Boolean,
-});
-const LocalMcpResumePaused = Schema.Struct({
- status: Schema.Literal("paused"),
- text: Schema.String,
- structured: Schema.Unknown,
-});
-const LocalMcpResumeResult = Schema.Union([LocalMcpResumeCompleted, LocalMcpResumePaused]);
-const decodeLocalMcpResumeResult = Schema.decodeUnknownOption(LocalMcpResumeResult);
-
-class LocalMcpResumeError extends Data.TaggedError("LocalMcpResumeError")<{
- readonly message: string;
-}> {}
-
-type LocalMcpResumeInput = {
- readonly mcpSessionId: string;
- readonly executionId: string;
- readonly action: ElicitationAction;
- readonly content?: Record;
-};
-
-const resumeLocalMcpExecution = Atom.fn()((input) =>
- Effect.gen(function* () {
- const response = yield* Effect.tryPromise({
- try: () =>
- fetch(
- `/api/mcp-sessions/${encodeURIComponent(input.mcpSessionId)}/executions/${encodeURIComponent(input.executionId)}/resume`,
- {
- method: "POST",
- headers: { "content-type": "application/json" },
- body: JSON.stringify(
- input.action === "accept"
- ? { action: input.action, content: input.content ?? {} }
- : { action: input.action },
- ),
- },
- ),
- catch: () => new LocalMcpResumeError({ message: "Failed to submit approval." }),
- });
-
- if (!response.ok) {
- const body = yield* Effect.tryPromise({
- try: () => response.text(),
- catch: () => "",
- }).pipe(Effect.orElseSucceed(() => ""));
- return yield* new LocalMcpResumeError({
- message: body || `Approval request failed (${response.status}).`,
- });
- }
-
- const body = yield* Effect.tryPromise({
- try: () => response.json(),
- catch: () => new LocalMcpResumeError({ message: "Approval response was not valid JSON." }),
- });
- const result = decodeLocalMcpResumeResult(body);
- if (Option.isNone(result)) {
- return yield* new LocalMcpResumeError({
- message: "Approval response had an unexpected shape.",
- });
- }
- return result.value;
- }),
-);
-
-export const Route = createFileRoute("/resume/$executionId")({
- validateSearch: SearchParams,
- component: RouteComponent,
-});
-
-function RouteComponent() {
- const { executionId } = Route.useParams();
- const { mcp_session_id: mcpSessionId } = Route.useSearch();
- if (mcpSessionId) {
- return ;
- }
- return ;
-}
-
-function LocalMcpResumeApproval(props: { executionId: string; mcpSessionId: string }) {
- const paused = useAtomValue(pausedExecutionAtom(props.executionId));
- const doResume = useAtomSet(resumeLocalMcpExecution, { mode: "promiseExit" });
- const resume = useCallback(
- (executionId: string, action: ElicitationAction, content?: Record) =>
- doResume({ mcpSessionId: props.mcpSessionId, executionId, action, content }),
- [doResume, props.mcpSessionId],
- );
-
- return (
-
- );
-}
diff --git a/apps/host-selfhost/web/routes/secrets.tsx b/apps/host-selfhost/web/routes/secrets.tsx
deleted file mode 100644
index 10bd4a10b..000000000
--- a/apps/host-selfhost/web/routes/secrets.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { SecretsPage } from "@executor-js/react/pages/secrets";
-
-// The Providers/Secrets page lets self-host users inspect their credential
-// backends. Credential entry happens through the per-integration Add Account
-// flow (`connections.createHandoff` → `/integrations/{slug}?addAccount=1`),
-// not here, so this route takes no search params.
-export const Route = createFileRoute("/secrets")({
- component: () => ,
-});
diff --git a/apps/host-selfhost/web/routes/sources.$namespace.tsx b/apps/host-selfhost/web/routes/sources.$namespace.tsx
deleted file mode 100644
index 2df401d30..000000000
--- a/apps/host-selfhost/web/routes/sources.$namespace.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute, redirect } from "@tanstack/react-router";
-
-export const Route = createFileRoute("/sources/$namespace")({
- beforeLoad: ({ params }) => {
- const { namespace } = params;
- // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router redirects are modeled as thrown values
- throw redirect({ to: "/integrations/$namespace", params: { namespace } });
- },
-});
diff --git a/apps/host-selfhost/web/routes/sources.add.$pluginKey.tsx b/apps/host-selfhost/web/routes/sources.add.$pluginKey.tsx
deleted file mode 100644
index ec809b401..000000000
--- a/apps/host-selfhost/web/routes/sources.add.$pluginKey.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Schema } from "effect";
-import { createFileRoute, redirect } from "@tanstack/react-router";
-
-const SearchParams = Schema.toStandardSchemaV1(
- Schema.Struct({
- url: Schema.optional(Schema.String),
- preset: Schema.optional(Schema.String),
- namespace: Schema.optional(Schema.String),
- }),
-);
-
-export const Route = createFileRoute("/sources/add/$pluginKey")({
- validateSearch: SearchParams,
- beforeLoad: ({ params, search }) => {
- const { pluginKey } = params;
- // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router redirects are modeled as thrown values
- throw redirect({ to: "/integrations/add/$pluginKey", params: { pluginKey }, search });
- },
-});
diff --git a/apps/host-selfhost/web/routes/tools.tsx b/apps/host-selfhost/web/routes/tools.tsx
deleted file mode 100644
index 25929fd2b..000000000
--- a/apps/host-selfhost/web/routes/tools.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { ToolsPage } from "@executor-js/react/pages/tools";
-
-export const Route = createFileRoute("/tools")({
- component: ToolsPage,
-});
diff --git a/bun.lock b/bun.lock
index 7f0e200f6..b7cd774ec 100644
--- a/bun.lock
+++ b/bun.lock
@@ -107,6 +107,7 @@
"@executor-js/cli": "workspace:*",
"@rhyssul/portless": "^0.13.0",
"@tailwindcss/vite": "catalog:",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
@@ -230,6 +231,7 @@
"@executor-js/vite-plugin": "workspace:*",
"@tailwindcss/vite": "catalog:",
"@tanstack/router-plugin": "^1.167.12",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
@@ -410,6 +412,7 @@
"devDependencies": {
"@tailwindcss/vite": "catalog:",
"@tanstack/router-plugin": "^1.167.12",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
diff --git a/packages/app/package.json b/packages/app/package.json
index e14e9e288..29d38997f 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -31,6 +31,7 @@
"devDependencies": {
"@tailwindcss/vite": "catalog:",
"@tanstack/router-plugin": "^1.167.12",
+ "@tanstack/virtual-file-routes": "^1.162.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
diff --git a/packages/app/src/routeTree.gen.ts b/packages/app/src/routeTree.gen.ts
index 56432ab7a..e86a8acd1 100644
--- a/packages/app/src/routeTree.gen.ts
+++ b/packages/app/src/routeTree.gen.ts
@@ -9,87 +9,95 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as ToolsRouteImport } from './routes/tools'
-import { Route as SecretsRouteImport } from './routes/secrets'
-import { Route as PoliciesRouteImport } from './routes/policies'
-import { Route as IndexRouteImport } from './routes/index'
-import { Route as ResumeExecutionIdRouteImport } from './routes/resume.$executionId'
-import { Route as IntegrationsNamespaceRouteImport } from './routes/integrations.$namespace'
-import { Route as PluginsPluginIdSplatRouteImport } from './routes/plugins.$pluginId.$'
-import { Route as IntegrationsAddPluginKeyRouteImport } from './routes/integrations.add.$pluginKey'
+import { Route as DotDotDotDotDotDotReactSrcRoutesToolsRouteImport } from './../../react/src/routes/tools'
+import { Route as SecretsRouteImport } from './routes/app/secrets'
+import { Route as DotDotDotDotDotDotReactSrcRoutesPoliciesRouteImport } from './../../react/src/routes/policies'
+import { Route as DotDotDotDotDotDotReactSrcRoutesIndexRouteImport } from './../../react/src/routes/index'
+import { Route as DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../react/src/routes/resume.$executionId'
+import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../react/src/routes/integrations.$namespace'
+import { Route as DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../react/src/routes/plugins.$pluginId.$'
+import { Route as DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../react/src/routes/integrations.add.$pluginKey'
-const ToolsRoute = ToolsRouteImport.update({
- id: '/tools',
- path: '/tools',
- getParentRoute: () => rootRouteImport,
-} as any)
+const DotDotDotDotDotDotReactSrcRoutesToolsRoute =
+ DotDotDotDotDotDotReactSrcRoutesToolsRouteImport.update({
+ id: '/tools',
+ path: '/tools',
+ getParentRoute: () => rootRouteImport,
+ } as any)
const SecretsRoute = SecretsRouteImport.update({
id: '/secrets',
path: '/secrets',
getParentRoute: () => rootRouteImport,
} as any)
-const PoliciesRoute = PoliciesRouteImport.update({
- id: '/policies',
- path: '/policies',
- getParentRoute: () => rootRouteImport,
-} as any)
-const IndexRoute = IndexRouteImport.update({
- id: '/',
- path: '/',
- getParentRoute: () => rootRouteImport,
-} as any)
-const ResumeExecutionIdRoute = ResumeExecutionIdRouteImport.update({
- id: '/resume/$executionId',
- path: '/resume/$executionId',
- getParentRoute: () => rootRouteImport,
-} as any)
-const IntegrationsNamespaceRoute = IntegrationsNamespaceRouteImport.update({
- id: '/integrations/$namespace',
- path: '/integrations/$namespace',
- getParentRoute: () => rootRouteImport,
-} as any)
-const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({
- id: '/plugins/$pluginId/$',
- path: '/plugins/$pluginId/$',
- getParentRoute: () => rootRouteImport,
-} as any)
-const IntegrationsAddPluginKeyRoute =
- IntegrationsAddPluginKeyRouteImport.update({
- id: '/integrations/add/$pluginKey',
- path: '/integrations/add/$pluginKey',
+const DotDotDotDotDotDotReactSrcRoutesPoliciesRoute =
+ DotDotDotDotDotDotReactSrcRoutesPoliciesRouteImport.update({
+ id: '/policies',
+ path: '/policies',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotReactSrcRoutesIndexRoute =
+ DotDotDotDotDotDotReactSrcRoutesIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute =
+ DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport.update({
+ id: '/resume/$executionId',
+ path: '/resume/$executionId',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute =
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport.update({
+ id: '/integrations/$namespace',
+ path: '/integrations/$namespace',
+ getParentRoute: () => rootRouteImport,
+ } as any)
+const DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute =
+ DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update({
+ id: '/plugins/$pluginId/$',
+ path: '/plugins/$pluginId/$',
getParentRoute: () => rootRouteImport,
} as any)
+const DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute =
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update(
+ {
+ id: '/integrations/add/$pluginKey',
+ path: '/integrations/add/$pluginKey',
+ getParentRoute: () => rootRouteImport,
+ } as any,
+ )
export interface FileRoutesByFullPath {
- '/': typeof IndexRoute
- '/policies': typeof PoliciesRoute
+ '/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute
+ '/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute
'/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
+ '/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRoutesByTo {
- '/': typeof IndexRoute
- '/policies': typeof PoliciesRoute
+ '/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute
+ '/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute
'/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
+ '/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
- '/': typeof IndexRoute
- '/policies': typeof PoliciesRoute
+ '/': typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute
+ '/policies': typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute
'/secrets': typeof SecretsRoute
- '/tools': typeof ToolsRoute
- '/integrations/$namespace': typeof IntegrationsNamespaceRoute
- '/resume/$executionId': typeof ResumeExecutionIdRoute
- '/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
- '/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
+ '/tools': typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute
+ '/integrations/$namespace': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute
+ '/resume/$executionId': typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute
+ '/integrations/add/$pluginKey': typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ '/plugins/$pluginId/$': typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -125,14 +133,14 @@ export interface FileRouteTypes {
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
- IndexRoute: typeof IndexRoute
- PoliciesRoute: typeof PoliciesRoute
+ DotDotDotDotDotDotReactSrcRoutesIndexRoute: typeof DotDotDotDotDotDotReactSrcRoutesIndexRoute
+ DotDotDotDotDotDotReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRoute
SecretsRoute: typeof SecretsRoute
- ToolsRoute: typeof ToolsRoute
- IntegrationsNamespaceRoute: typeof IntegrationsNamespaceRoute
- ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute
- IntegrationsAddPluginKeyRoute: typeof IntegrationsAddPluginKeyRoute
- PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute
+ DotDotDotDotDotDotReactSrcRoutesToolsRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolsRoute
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute
+ DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute: typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute
+ DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute: typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute
}
declare module '@tanstack/react-router' {
@@ -141,7 +149,7 @@ declare module '@tanstack/react-router' {
id: '/tools'
path: '/tools'
fullPath: '/tools'
- preLoaderRoute: typeof ToolsRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesToolsRouteImport
parentRoute: typeof rootRouteImport
}
'/secrets': {
@@ -155,56 +163,63 @@ declare module '@tanstack/react-router' {
id: '/policies'
path: '/policies'
fullPath: '/policies'
- preLoaderRoute: typeof PoliciesRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesPoliciesRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
- preLoaderRoute: typeof IndexRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/resume/$executionId': {
id: '/resume/$executionId'
path: '/resume/$executionId'
fullPath: '/resume/$executionId'
- preLoaderRoute: typeof ResumeExecutionIdRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/$namespace': {
id: '/integrations/$namespace'
path: '/integrations/$namespace'
fullPath: '/integrations/$namespace'
- preLoaderRoute: typeof IntegrationsNamespaceRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRouteImport
parentRoute: typeof rootRouteImport
}
'/plugins/$pluginId/$': {
id: '/plugins/$pluginId/$'
path: '/plugins/$pluginId/$'
fullPath: '/plugins/$pluginId/$'
- preLoaderRoute: typeof PluginsPluginIdSplatRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/add/$pluginKey': {
id: '/integrations/add/$pluginKey'
path: '/integrations/add/$pluginKey'
fullPath: '/integrations/add/$pluginKey'
- preLoaderRoute: typeof IntegrationsAddPluginKeyRouteImport
+ preLoaderRoute: typeof DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
- IndexRoute: IndexRoute,
- PoliciesRoute: PoliciesRoute,
+ DotDotDotDotDotDotReactSrcRoutesIndexRoute:
+ DotDotDotDotDotDotReactSrcRoutesIndexRoute,
+ DotDotDotDotDotDotReactSrcRoutesPoliciesRoute:
+ DotDotDotDotDotDotReactSrcRoutesPoliciesRoute,
SecretsRoute: SecretsRoute,
- ToolsRoute: ToolsRoute,
- IntegrationsNamespaceRoute: IntegrationsNamespaceRoute,
- ResumeExecutionIdRoute: ResumeExecutionIdRoute,
- IntegrationsAddPluginKeyRoute: IntegrationsAddPluginKeyRoute,
- PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute,
+ DotDotDotDotDotDotReactSrcRoutesToolsRoute:
+ DotDotDotDotDotDotReactSrcRoutesToolsRoute,
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute:
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotnamespaceRoute,
+ DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute:
+ DotDotDotDotDotDotReactSrcRoutesResumeDotexecutionIdRoute,
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute:
+ DotDotDotDotDotDotReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute,
+ DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute:
+ DotDotDotDotDotDotReactSrcRoutesPluginsDotpluginIdDotsplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/packages/app/src/routes/secrets.tsx b/packages/app/src/routes/app/secrets.tsx
similarity index 100%
rename from packages/app/src/routes/secrets.tsx
rename to packages/app/src/routes/app/secrets.tsx
diff --git a/packages/app/src/routes/index.tsx b/packages/app/src/routes/index.tsx
deleted file mode 100644
index 2d57f82f0..000000000
--- a/packages/app/src/routes/index.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationsPage } from "@executor-js/react/pages/integrations";
-
-export const Route = createFileRoute("/")({
- component: IntegrationsPage,
-});
diff --git a/packages/app/src/routes/integrations.$namespace.tsx b/packages/app/src/routes/integrations.$namespace.tsx
deleted file mode 100644
index 49a458104..000000000
--- a/packages/app/src/routes/integrations.$namespace.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail";
-
-export const Route = createFileRoute("/integrations/$namespace")({
- component: () => {
- const { namespace } = Route.useParams();
- return ;
- },
-});
diff --git a/packages/app/src/routes/integrations.add.$pluginKey.tsx b/packages/app/src/routes/integrations.add.$pluginKey.tsx
deleted file mode 100644
index baada29f3..000000000
--- a/packages/app/src/routes/integrations.add.$pluginKey.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Schema } from "effect";
-import { createFileRoute } from "@tanstack/react-router";
-import { AddIntegrationPage } from "@executor-js/react/pages/integration-add";
-
-const SearchParams = Schema.toStandardSchemaV1(
- Schema.Struct({
- url: Schema.optional(Schema.String),
- preset: Schema.optional(Schema.String),
- }),
-);
-
-export const Route = createFileRoute("/integrations/add/$pluginKey")({
- validateSearch: SearchParams,
- component: () => {
- const { pluginKey } = Route.useParams();
- const { url, preset } = Route.useSearch();
- return ;
- },
-});
diff --git a/packages/app/src/routes/plugins.$pluginId.$.tsx b/packages/app/src/routes/plugins.$pluginId.$.tsx
deleted file mode 100644
index 472ab9768..000000000
--- a/packages/app/src/routes/plugins.$pluginId.$.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { createFileRoute, notFound } from "@tanstack/react-router";
-import { useClientPlugins } from "@executor-js/sdk/client";
-
-// ---------------------------------------------------------------------------
-// /plugins//
-//
-// Mounts pages contributed by client plugins. The host's
-// `` (set up at the root) materialises the
-// list of `ClientPluginSpec` from `virtual:executor/plugins-client`,
-// and this route reads it via `useClientPlugins()` — so adding a
-// plugin to `executor.config.ts` is sufficient for its pages to mount
-// here, with no per-route imports.
-//
-// Match logic is intentionally tiny: exact path equality between the URL
-// remainder and a `PageDecl.path`, with `""` and `/` treated as the
-// same root. Plugins that want parameterized paths can build their own
-// in-component routing for now.
-// ---------------------------------------------------------------------------
-
-export const Route = createFileRoute("/plugins/$pluginId/$")({
- component: PluginRouteComponent,
-});
-
-function normalizePath(input: string): string {
- if (!input || input === "/") return "/";
- return input.startsWith("/") ? input : `/${input}`;
-}
-
-function PluginRouteComponent() {
- const { pluginId, _splat: rest } = Route.useParams();
- const plugins = useClientPlugins();
- const plugin = plugins.find((p) => p.id === pluginId);
- // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router represents not-found from components by throwing notFound()
- if (!plugin) throw notFound();
-
- const target = normalizePath(rest ?? "/");
- const page = plugin.pages?.find((p) => normalizePath(p.path) === target);
- // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router represents not-found from components by throwing notFound()
- if (!page) throw notFound();
-
- const Component = page.component;
- return ;
-}
diff --git a/packages/app/src/routes/policies.tsx b/packages/app/src/routes/policies.tsx
deleted file mode 100644
index a9de9ff6f..000000000
--- a/packages/app/src/routes/policies.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { PoliciesPage } from "@executor-js/react/pages/policies";
-
-export const Route = createFileRoute("/policies")({
- component: () => ,
-});
diff --git a/packages/app/src/routes/resume.$executionId.tsx b/packages/app/src/routes/resume.$executionId.tsx
deleted file mode 100644
index 32a84347b..000000000
--- a/packages/app/src/routes/resume.$executionId.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { useCallback } from "react";
-import { useAtomSet, useAtomValue } from "@effect/atom-react";
-import { Data, Effect, Option, Schema } from "effect";
-import * as Atom from "effect/unstable/reactivity/Atom";
-import { createFileRoute } from "@tanstack/react-router";
-import {
- ResumeApprovalPage,
- ResumeApprovalPageView,
-} from "@executor-js/react/pages/resume-approval";
-import { pausedExecutionAtom } from "@executor-js/react/api/atoms";
-import type { ElicitationAction } from "@executor-js/react/components/elicitation-approval";
-
-const SearchParams = Schema.toStandardSchemaV1(
- Schema.Struct({
- mcp_session_id: Schema.optional(Schema.String),
- }),
-);
-const LocalMcpResumeCompleted = Schema.Struct({
- status: Schema.Literal("completed"),
- text: Schema.String,
- structured: Schema.Unknown,
- isError: Schema.Boolean,
-});
-const LocalMcpResumePaused = Schema.Struct({
- status: Schema.Literal("paused"),
- text: Schema.String,
- structured: Schema.Unknown,
-});
-const LocalMcpResumeResult = Schema.Union([LocalMcpResumeCompleted, LocalMcpResumePaused]);
-const decodeLocalMcpResumeResult = Schema.decodeUnknownOption(LocalMcpResumeResult);
-
-class LocalMcpResumeError extends Data.TaggedError("LocalMcpResumeError")<{
- readonly message: string;
-}> {}
-
-type LocalMcpResumeInput = {
- readonly mcpSessionId: string;
- readonly executionId: string;
- readonly action: ElicitationAction;
- readonly content?: Record;
-};
-
-const resumeLocalMcpExecution = Atom.fn()((input) =>
- Effect.gen(function* () {
- const response = yield* Effect.tryPromise({
- try: () =>
- fetch(
- `/api/mcp-sessions/${encodeURIComponent(input.mcpSessionId)}/executions/${encodeURIComponent(input.executionId)}/resume`,
- {
- method: "POST",
- headers: { "content-type": "application/json" },
- body: JSON.stringify(
- input.action === "accept"
- ? { action: input.action, content: input.content ?? {} }
- : { action: input.action },
- ),
- },
- ),
- catch: () => new LocalMcpResumeError({ message: "Failed to submit approval." }),
- });
-
- if (!response.ok) {
- const body = yield* Effect.tryPromise({
- try: () => response.text(),
- catch: () => "",
- }).pipe(Effect.orElseSucceed(() => ""));
- return yield* new LocalMcpResumeError({
- message: body || `Approval request failed (${response.status}).`,
- });
- }
-
- const body = yield* Effect.tryPromise({
- try: () => response.json(),
- catch: () => new LocalMcpResumeError({ message: "Approval response was not valid JSON." }),
- });
- const result = decodeLocalMcpResumeResult(body);
- if (Option.isNone(result)) {
- return yield* new LocalMcpResumeError({
- message: "Approval response had an unexpected shape.",
- });
- }
- return result.value;
- }),
-);
-
-export const Route = createFileRoute("/resume/$executionId")({
- validateSearch: SearchParams,
- component: RouteComponent,
-});
-
-function RouteComponent() {
- const { executionId } = Route.useParams();
- const { mcp_session_id: mcpSessionId } = Route.useSearch();
- if (mcpSessionId) {
- return ;
- }
- return ;
-}
-
-function LocalMcpResumeApproval(props: { executionId: string; mcpSessionId: string }) {
- const paused = useAtomValue(pausedExecutionAtom(props.executionId));
- const doResume = useAtomSet(resumeLocalMcpExecution, { mode: "promiseExit" });
- const resume = useCallback(
- (executionId: string, action: ElicitationAction, content?: Record) =>
- doResume({ mcpSessionId: props.mcpSessionId, executionId, action, content }),
- [doResume, props.mcpSessionId],
- );
-
- return (
-
- );
-}
diff --git a/packages/app/src/routes/tools.tsx b/packages/app/src/routes/tools.tsx
deleted file mode 100644
index 25929fd2b..000000000
--- a/packages/app/src/routes/tools.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { ToolsPage } from "@executor-js/react/pages/tools";
-
-export const Route = createFileRoute("/tools")({
- component: ToolsPage,
-});
diff --git a/packages/app/vite.ts b/packages/app/vite.ts
index 55d04da45..0baf8ea1a 100644
--- a/packages/app/vite.ts
+++ b/packages/app/vite.ts
@@ -3,6 +3,8 @@ import type { PluginOption } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
+import { physical, rootRoute } from "@tanstack/virtual-file-routes";
+import { consoleRoutes } from "@executor-js/react/console-routes";
import executorVitePlugin from "@executor-js/vite-plugin";
const APP_ROOT = fileURLToPath(new URL("./", import.meta.url));
@@ -58,6 +60,14 @@ export default function appPlugin(options: AppPluginOptions = {}): PluginOption[
autoCodeSplitting: true,
routesDirectory: fileURLToPath(new URL("./src/routes", import.meta.url)),
generatedRouteTree: fileURLToPath(new URL("./src/routeTree.gen.ts", import.meta.url)),
+ // Shared console routes come from @executor-js/react (see its
+ // console-routes.ts); this app owns its root and the local-specific
+ // routes under src/routes/app. /secrets is excluded: the local app's
+ // variant shows credential-provider info.
+ virtualRouteConfig: rootRoute("__root.tsx", [
+ ...consoleRoutes({ dir: "../../../react/src/routes", exclude: ["/secrets"] }),
+ physical("", "app"),
+ ]),
}),
...react(),
];
diff --git a/packages/react/package.json b/packages/react/package.json
index 6deace349..9ccba4b1f 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -23,6 +23,7 @@
},
"scripts": {
"test": "vitest run",
+ "routes:gen": "tsr generate",
"test:watch": "vitest",
"typecheck": "tsgo --noEmit",
"typecheck:slow": "tsc --noEmit",
diff --git a/packages/react/src/console-routes.test.ts b/packages/react/src/console-routes.test.ts
new file mode 100644
index 000000000..0ccb66b60
--- /dev/null
+++ b/packages/react/src/console-routes.test.ts
@@ -0,0 +1,36 @@
+import { describe, expect, it } from "@effect/vitest";
+
+import { CONSOLE_ROUTE_PATHS, consoleRoutes } from "./console-routes";
+import { routeTree } from "./routes/routeTree.gen";
+
+// consoleRoutes() (what apps mount) and routes/routeTree.gen.ts (what `bunx
+// tsr generate` scans from src/routes, and what types this package's links)
+// are two views of the same directory. If they drift — a route file added
+// without a consoleRoutes() entry, or vice versa — apps would silently lack a
+// route that this package's pages link to. Lock them together.
+
+const collectPaths = (route: unknown): ReadonlyArray => {
+ const node = route as {
+ options?: { id?: string };
+ children?: ReadonlyArray;
+ };
+ const children = node.children ?? [];
+ const id = node.options?.id;
+ const own = typeof id === "string" ? [id] : [];
+ return [...own, ...children.flatMap(collectPaths)];
+};
+
+describe("console route contract", () => {
+ it("consoleRoutes() declares exactly the routes in src/routes", () => {
+ const generated = new Set(collectPaths(routeTree));
+ expect([...generated].sort()).toEqual([...CONSOLE_ROUTE_PATHS].sort());
+ });
+
+ it("every path has a virtual route node and exclude removes it", () => {
+ const all = consoleRoutes({ dir: "shared" });
+ expect(all).toHaveLength(CONSOLE_ROUTE_PATHS.length);
+ const withoutSecrets = consoleRoutes({ dir: "shared", exclude: ["/secrets"] });
+ expect(withoutSecrets).toHaveLength(CONSOLE_ROUTE_PATHS.length - 1);
+ expect(JSON.stringify(withoutSecrets)).not.toContain("secrets.tsx");
+ });
+});