History

merge @executor/ui + @executor/react into packages/react (#88)

- Merge UI components, hooks, lib, styles from packages/ui - Merge API client, atoms, provider from packages/clients/react - Move shared pages and components from apps/local/src/web - Direct subpath exports: @executor/react/{api,plugins,pages,components,hooks,lib}/* - No barrel files, no backwards compat shims - Each app owns its own shell/router, imports pages from package - Delete packages/ui and packages/clients/react

Rhys Sullivan committed 7 months ago

Commit 4153df42 parents c3f0b23 7cb48ec133 files · +877 −498Browse files

Unified · Split

133 files
  1. .changeset/config.json +1 −3
  2. apps/cloud/package.json +0 −1
  3. apps/cloud/src/api.ts +25 −4
  4. apps/cloud/src/auth/api.ts +25 −0
  5. apps/cloud/src/auth/context.ts +8 −0
  6. apps/cloud/src/auth/handlers.ts +34 −0
  7. apps/cloud/src/auth/workos.ts +34 −31
  8. apps/cloud/src/web/auth.tsx +32 −26
  9. apps/cloud/src/web/client.tsx +22 −0
  10. apps/cloud/src/web/main.tsx +1 −1
  11. apps/cloud/src/web/router.tsx +7 −7
  12. apps/cloud/src/web/shell.tsx +483 −0
  13. apps/cloud/tsconfig.json +0 −3
  14. apps/cloud/vite.config.ts +0 −6
  15. apps/local/package.json +0 −1
  16. apps/local/src/web/lib/use-scope.ts +0 −1
  17. apps/local/src/web/main.tsx +1 −1
  18. apps/local/src/web/router.tsx +6 −6
  19. apps/local/src/web/shell.tsx +4 −10
  20. bun.lock +10 −40
  21. package.json +1 −1
  22. packages/clients/react/CHANGELOG.md +0 −4
  23. packages/clients/react/package.json +0 −27
  24. packages/clients/react/src/index.ts +0 −54
  25. packages/clients/react/src/use-scope.ts +0 −2
  26. packages/plugins/google-discovery/package.json +1 −6
  27. packages/plugins/google-discovery/src/react/AddGoogleDiscoverySource.tsx +10 −14
  28. packages/plugins/google-discovery/src/react/EditGoogleDiscoverySource.tsx +2 −2
  29. packages/plugins/google-discovery/src/react/GoogleDiscoverySourceSummary.tsx +1 −1
  30. packages/plugins/google-discovery/src/react/client.ts +1 −1
  31. packages/plugins/google-discovery/src/react/source-plugin.ts +1 −1
  32. packages/plugins/graphql/package.json +1 −6
  33. packages/plugins/graphql/src/react/AddGraphqlSource.tsx +7 −11
  34. packages/plugins/graphql/src/react/client.ts +1 −1
  35. packages/plugins/graphql/src/react/source-plugin.ts +1 −1
  36. packages/plugins/mcp/package.json +1 −6
  37. packages/plugins/mcp/src/react/AddMcpSource.tsx +6 −6
  38. packages/plugins/mcp/src/react/EditMcpSource.tsx +2 −2
  39. packages/plugins/mcp/src/react/McpSourceSummary.tsx +1 −1
  40. packages/plugins/mcp/src/react/client.ts +1 −1
  41. packages/plugins/mcp/src/react/source-plugin.ts +1 −1
  42. packages/plugins/onepassword/package.json +1 −5
  43. packages/plugins/onepassword/src/react/OnePasswordSettings.tsx +6 −6
  44. packages/plugins/onepassword/src/react/client.ts +1 −1
  45. packages/plugins/onepassword/src/react/secret-provider-plugin.ts +1 −1
  46. packages/plugins/openapi/package.json +1 −6
  47. packages/plugins/openapi/src/react/AddOpenApiSource.tsx +10 −15
  48. packages/plugins/openapi/src/react/client.ts +1 −1
  49. packages/plugins/openapi/src/react/source-plugin.ts +1 −1
  50. packages/react/package.json +11 −1
  51. packages/react/src/api/atoms.tsx +0 −0
  52. packages/react/src/api/base-url.tsx +0 −0
  53. packages/react/src/api/client.tsx +0 −0
  54. packages/react/src/api/provider.tsx +0 −0
  55. packages/react/src/api/scope-context.tsx +0 −0
  56. packages/react/src/components/accordion.tsx +1 −1
  57. packages/react/src/components/alert-dialog.tsx +2 −2
  58. packages/react/src/components/alert.tsx +1 −1
  59. packages/react/src/components/aspect-ratio.tsx +0 −0
  60. packages/react/src/components/avatar.tsx +1 −1
  61. packages/react/src/components/badge.tsx +1 −1
  62. packages/react/src/components/breadcrumb.tsx +1 −1
  63. packages/react/src/components/button-group.tsx +2 −2
  64. packages/react/src/components/button.tsx +1 −1
  65. packages/react/src/components/calendar.tsx +2 −2
  66. packages/react/src/components/card.tsx +1 −1
  67. packages/react/src/components/carousel.tsx +2 −2
  68. packages/react/src/components/chart.tsx +1 −1
  69. packages/react/src/components/checkbox.tsx +1 −1
  70. packages/react/src/components/code-block.tsx +0 −0
  71. packages/react/src/components/collapsible.tsx +0 −0
  72. packages/react/src/components/combobox.tsx +3 −3
  73. packages/react/src/components/command.tsx +2 −2
  74. packages/react/src/components/context-menu.tsx +1 −1
  75. packages/react/src/components/dialog.tsx +2 −2
  76. packages/react/src/components/direction.tsx +0 −0
  77. packages/react/src/components/drawer.tsx +1 −1
  78. packages/react/src/components/dropdown-menu.tsx +1 −1
  79. packages/react/src/components/empty.tsx +1 −1
  80. packages/react/src/components/field.tsx +3 −3
  81. packages/react/src/components/form.tsx +2 −2
  82. packages/react/src/components/hover-card.tsx +1 −1
  83. packages/react/src/components/input-group.tsx +4 −4
  84. packages/react/src/components/input-otp.tsx +1 −1
  85. packages/react/src/components/input.tsx +1 −1
  86. packages/react/src/components/item.tsx +2 −2
  87. packages/react/src/components/kbd.tsx +1 −1
  88. packages/react/src/components/label.tsx +1 −1
  89. packages/react/src/components/markdown.tsx +0 −0
  90. packages/react/src/components/mcp-install-card.tsx +0 −0
  91. packages/react/src/components/menubar.tsx +1 −1
  92. packages/react/src/components/native-select.tsx +1 −1
  93. packages/react/src/components/navigation-menu.tsx +1 −1
  94. packages/react/src/components/pagination.tsx +2 −2
  95. packages/react/src/components/popover.tsx +1 −1
  96. packages/react/src/components/progress.tsx +1 −1
  97. packages/react/src/components/radio-group.tsx +1 −1
  98. packages/react/src/components/resizable.tsx +1 −1
  99. packages/react/src/components/scroll-area.tsx +1 −1
  100. packages/react/src/components/select.tsx +1 −1
  101. packages/react/src/components/separator.tsx +1 −1
  102. packages/react/src/components/sheet.tsx +1 −1
  103. packages/react/src/components/sidebar.tsx +8 −8
  104. packages/react/src/components/skeleton.tsx +1 −1
  105. packages/react/src/components/slider.tsx +1 −1
  106. packages/react/src/components/sonner.tsx +0 −0
  107. packages/react/src/components/spinner.tsx +1 −1
  108. packages/react/src/components/switch.tsx +1 −1
  109. packages/react/src/components/table.tsx +1 −1
  110. packages/react/src/components/tabs.tsx +1 −1
  111. packages/react/src/components/textarea.tsx +1 −1
  112. packages/react/src/components/toggle-group.tsx +2 −2
  113. packages/react/src/components/toggle.tsx +1 −1
  114. packages/react/src/components/tool-detail.tsx +7 −5
  115. packages/react/src/components/tool-tree.tsx +0 −0
  116. packages/react/src/components/tooltip.tsx +1 −1
  117. packages/react/src/hooks/use-mobile.ts +0 −0
  118. packages/react/src/hooks/use-scope.ts +1 −0
  119. packages/react/src/lib/shiki.ts +0 −0
  120. packages/react/src/lib/utils.ts +0 −0
  121. packages/react/src/pages/secrets.tsx +11 −18
  122. packages/react/src/pages/source-detail.tsx +3 −12
  123. packages/react/src/pages/sources-add.tsx +4 −3
  124. packages/react/src/pages/sources.tsx +4 −3
  125. packages/react/src/pages/tools.tsx +3 −2
  126. packages/react/src/plugins/secret-picker.tsx +3 −3
  127. packages/react/src/plugins/secret-provider-plugin.tsx +0 −0
  128. packages/react/src/plugins/source-plugin.tsx +0 −0
  129. packages/react/src/styles/globals.css +0 −0
  130. packages/react/tsconfig.json +0 −0
  131. packages/ui/CHANGELOG.md +0 −4
  132. packages/ui/components.json +0 −20
  133. packages/ui/tsconfig.json +0 −25
.changeset/config.json+1−3
@@ -26,8 +26,6 @@ · 25 unmodified lines
2626 "@executor/plugin-keychain",
2727 "@executor/plugin-mcp",
2828 "@executor/plugin-onepassword",
29− "@executor/plugin-openapi",
30− "@executor/ui",
31− "@executor/marketing"
29+ "@executor/plugin-openapi"
3230 ]
3331 }
apps/cloud/package.json+0−1
@@ -15,7 +15,6 @@ · 14 unmodified lines
1515 "@executor/react": "workspace:*",
1616 "@executor/sdk": "workspace:*",
1717 "@executor/storage-postgres": "workspace:*",
18− "@executor/ui": "workspace:*",
1918 "@tanstack/react-router": "^1.168.10",
2019 "@workos-inc/node": "^7.0.0",
2120 "drizzle-orm": "catalog:",
apps/cloud/src/api.ts+25−4
@@ -1,5 +1,5 @@
11 // ---------------------------------------------------------------------------
2−// Cloud API — core handlers from @executor/api + cloud-specific plugins
2+// Cloud API — core handlers from @executor/api + cloud-specific plugins + auth
33 // ---------------------------------------------------------------------------
44
55 import {
@@ -20,16 +20,20 @@ import { GraphqlGroup, GraphqlExtensionService, GraphqlHandlers } from "@executo · 14 unmodified lines
2020
2121 import { createTeamExecutor } from "./services/executor";
2222 import { authenticateRequest } from "./auth/workos";
23+import { CloudAuthApi } from "./auth/api";
24+import { CloudAuthHandlers } from "./auth/handlers";
25+import { AuthContext, UserStoreService } from "./auth/context";
2326 import type { DrizzleDb } from "./services/db";
2427
2528 // ---------------------------------------------------------------------------
26−// Cloud API — core + cloud plugins (no onepassword)
29+// Cloud API — core + cloud plugins + cloud auth (no onepassword)
2730 // ---------------------------------------------------------------------------
2831
2932 const CloudApi = addGroup(OpenApiGroup)
3033 .add(McpGroup)
3134 .add(GoogleDiscoveryGroup)
32− .add(GraphqlGroup);
35+ .add(GraphqlGroup)
36+ .add(CloudAuthApi);
3337
3438 const CloudApiBase = HttpApiBuilder.api(CloudApi).pipe(
3539 Layer.provide(CoreHandlers),
@@ -38,6 +42,7 @@ const CloudApiBase = HttpApiBuilder.api(CloudApi).pipe( · 2 unmodified lines
3842 McpHandlers,
3943 GoogleDiscoveryHandlers,
4044 GraphqlHandlers,
45+ CloudAuthHandlers,
4146 )),
4247 );
4348
@@ -96,13 +101,29 @@ export const createCloudApiHandler = (db: DrizzleDb, encryptionKey: string) => { · 52 unmodified lines
96101 Layer.provideMerge(pluginExtensions),
97102 Layer.provideMerge(Layer.succeed(ExecutorService, executor)),
98103 Layer.provideMerge(Layer.succeed(ExecutionEngineService, engine)),
104+ Layer.provideMerge(Layer.succeed(AuthContext, {
105+ userId: auth.userId,
106+ email: auth.email,
107+ teamId,
108+ name: `${auth.firstName ?? ""} ${auth.lastName ?? ""}`.trim() || null,
109+ avatarUrl: auth.avatarUrl,
110+ })),
111+ Layer.provideMerge(Layer.succeed(UserStoreService, userStore)),
99112 Layer.provideMerge(HttpServer.layerContext),
100113 ),
101114 { middleware: HttpMiddleware.logger },
102115 );
103116
104117 try {
105− return await handler.handler(request);
118+ const response = await handler.handler(request);
119+
120+ if (auth.refreshedCookie) {
121+ const newResponse = new Response(response.body, response);
122+ newResponse.headers.append("Set-Cookie", auth.refreshedCookie);
123+ return newResponse;
124+ }
125+
126+ return response;
106127 } finally {
107128 await Effect.runPromise(executor.close()).catch(() => undefined);
108129 handler.dispose();
apps/cloud/src/auth/api.ts+25−0
@@ -0,0 +1,25 @@
1+import { HttpApiEndpoint, HttpApiGroup } from "@effect/platform";
2+import { Schema } from "effect";
3+
4+const AuthUser = Schema.Struct({
5+ id: Schema.String,
6+ email: Schema.String,
7+ name: Schema.NullOr(Schema.String),
8+ avatarUrl: Schema.NullOr(Schema.String),
9+});
10+
11+const AuthTeam = Schema.Struct({
12+ id: Schema.String,
13+ name: Schema.String,
14+});
15+
16+const AuthMeResponse = Schema.Struct({
17+ user: AuthUser,
18+ team: Schema.NullOr(AuthTeam),
19+});
20+
21+export class CloudAuthApi extends HttpApiGroup.make("cloudAuth")
22+ .add(
23+ HttpApiEndpoint.get("me")`/auth/me`
24+ .addSuccess(AuthMeResponse),
25+ ) {}
apps/cloud/src/auth/context.ts+8−0
@@ -1,4 +1,5 @@
11 import { Context } from "effect";
2+import type { makeUserStore } from "@executor/storage-postgres";
23
34 export class AuthContext extends Context.Tag("@executor/cloud/AuthContext")<
45 AuthContext,
@@ -6,5 +7,12 @@ export class AuthContext extends Context.Tag("@executor/cloud/AuthContext")< · 1 unmodified line
67 readonly userId: string;
78 readonly teamId: string;
89 readonly email: string;
10+ readonly name: string | null;
11+ readonly avatarUrl: string | null;
912 }
1013 >() {}
14+
15+export class UserStoreService extends Context.Tag("@executor/cloud/UserStoreService")<
16+ UserStoreService,
17+ ReturnType<typeof makeUserStore>
18+>() {}
apps/cloud/src/auth/handlers.ts+34−0
@@ -0,0 +1,34 @@
1+import { HttpApiBuilder } from "@effect/platform";
2+import { Effect } from "effect";
3+
4+import { addGroup } from "@executor/api";
5+import { CloudAuthApi } from "./api";
6+import { AuthContext, UserStoreService } from "./context";
7+
8+const ApiWithCloudAuth = addGroup(CloudAuthApi);
9+
10+export const CloudAuthHandlers = HttpApiBuilder.group(
11+ ApiWithCloudAuth,
12+ "cloudAuth",
13+ (handlers) =>
14+ handlers.handle("me", () =>
15+ Effect.gen(function* () {
16+ const auth = yield* AuthContext;
17+ const userStore = yield* UserStoreService;
18+
19+ const team = yield* Effect.tryPromise(() =>
20+ userStore.getTeam(auth.teamId),
21+ ).pipe(Effect.orDie);
22+
23+ return {
24+ user: {
25+ id: auth.userId,
26+ email: auth.email,
27+ name: auth.name,
28+ avatarUrl: auth.avatarUrl,
29+ },
30+ team: team ? { id: team.id, name: team.name } : null,
31+ };
32+ }),
33+ ),
34+);
apps/cloud/src/auth/workos.ts+34−31
@@ -48,6 +48,10 @@ export const authenticateWithCode = async (code: string) => { · 47 unmodified lines
4848 * Authenticate a request using the sealed session cookie.
4949 * Returns user info or null if not authenticated.
5050 */
51+/**
52+ * Authenticate a request using the sealed session cookie.
53+ * Automatically refreshes expired tokens and returns a new cookie if needed.
54+ */
5155 export const authenticateRequest = async (request: Request) => {
5256 const cookieHeader = request.headers.get("cookie");
5357 const sessionData = parseCookie(cookieHeader, COOKIE_NAME);
@@ -60,39 +64,38 @@ export const authenticateRequest = async (request: Request) => { · 6 unmodified lines
6064 });
6165
6266 const result = await session.authenticate();
63− if (!result.authenticated) return null;
64−
65− return {
66− userId: result.user.id,
67− email: result.user.email,
68− firstName: result.user.firstName,
69− lastName: result.user.lastName,
70− avatarUrl: result.user.profilePictureUrl,
71− sessionId: result.sessionId,
72− };
73−};
74−
75−/**
76− * Refresh the sealed session cookie. Returns new cookie value or null.
77− */
78−export const refreshSession = async (request: Request) => {
79− const cookieHeader = request.headers.get("cookie");
80− const sessionData = parseCookie(cookieHeader, COOKIE_NAME);
81− if (!sessionData) return null;
8267
83− const wos = getWorkOS();
84− const session = wos.userManagement.loadSealedSession({
85− sessionData,
86− cookiePassword: getCookiePassword(),
87− });
88−
89− const result = await session.refresh();
90− if (!result.authenticated || !result.sealedSession) return null;
68+ if (result.authenticated) {
69+ return {
70+ userId: result.user.id,
71+ email: result.user.email,
72+ firstName: result.user.firstName,
73+ lastName: result.user.lastName,
74+ avatarUrl: result.user.profilePictureUrl,
75+ sessionId: result.sessionId,
76+ refreshedCookie: undefined as string | undefined,
77+ };
78+ }
9179
92− return {
93− sealedSession: result.sealedSession,
94− cookie: makeSessionCookie(result.sealedSession),
95− };
80+ // Token expired — try refreshing
81+ if (result.reason === "no_session_cookie_provided") return null;
82+
83+ try {
84+ const refreshed = await session.refresh();
85+ if (!refreshed.authenticated || !refreshed.sealedSession) return null;
86+
87+ return {
88+ userId: refreshed.user.id,
89+ email: refreshed.user.email,
90+ firstName: refreshed.user.firstName,
91+ lastName: refreshed.user.lastName,
92+ avatarUrl: refreshed.user.profilePictureUrl,
93+ sessionId: refreshed.sessionId,
94+ refreshedCookie: makeSessionCookie(refreshed.sealedSession),
95+ };
96+ } catch {
97+ return null;
98+ }
9699 };
97100
98101 /**
apps/cloud/src/web/auth.tsx+32−26
@@ -1,4 +1,11 @@
1−import React, { createContext, useContext, useEffect, useState } from "react";
1+import React, { createContext, useContext } from "react";
2+import { useAtomValue, Result } from "@effect-atom/atom-react";
3+
4+import { CloudApiClient } from "./client";
5+
6+// ---------------------------------------------------------------------------
7+// Types (from CloudAuthApi response schema)
8+// ---------------------------------------------------------------------------
29
310 type AuthUser = {
411 id: string;
@@ -12,40 +19,39 @@ type AuthTeam = { · 7 unmodified lines
1219 name: string;
1320 };
1421
22+// ---------------------------------------------------------------------------
23+// Auth atom — typed query against CloudAuthApi
24+// ---------------------------------------------------------------------------
25+
26+export const authAtom = CloudApiClient.query("cloudAuth", "me", {
27+ timeToLive: "5 minutes",
28+});
29+
30+// ---------------------------------------------------------------------------
31+// Provider + hook
32+// ---------------------------------------------------------------------------
33+
1534 type AuthState =
1635 | { status: "loading" }
1736 | { status: "unauthenticated" }
18− | { status: "authenticated"; user: AuthUser; team: AuthTeam };
37+ | { status: "authenticated"; user: AuthUser; team: AuthTeam | null };
1938
2039 const AuthContext = createContext<AuthState>({ status: "loading" });
2140
2241 export const useAuth = () => useContext(AuthContext);
2342
2443 export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
25− const [state, setState] = useState<AuthState>({ status: "loading" });
26−
27− useEffect(() => {
28− fetch("/auth/me")
29− .then((res) => {
30− if (!res.ok) {
31− setState({ status: "unauthenticated" });
32− return;
33− }
34− return res.json();
35− })
36− .then((data) => {
37− if (data?.user) {
38− setState({
39− status: "authenticated",
40− user: data.user,
41− team: data.team,
42− });
43− }
44− })
45− .catch(() => {
46− setState({ status: "unauthenticated" });
47− });
48− }, []);
44+ const result = useAtomValue(authAtom);
45+
46+ const state: AuthState = Result.match(result, {
47+ onInitial: () => ({ status: "loading" as const }),
48+ onSuccess: ({ value }) => ({
49+ status: "authenticated" as const,
50+ user: value.user,
51+ team: value.team,
52+ }),
53+ onFailure: () => ({ status: "unauthenticated" as const }),
54+ });
4955
5056 return <AuthContext.Provider value={state}>{children}</AuthContext.Provider>;
5157 };
apps/cloud/src/web/client.tsx+22−0
@@ -0,0 +1,22 @@
1+import { AtomHttpApi } from "@effect-atom/atom-react";
2+import { FetchHttpClient } from "@effect/platform";
3+import { addGroup } from "@executor/api";
4+import { getBaseUrl } from "@executor/react/api/base-url";
5+import { CloudAuthApi } from "../auth/api";
6+
7+// ---------------------------------------------------------------------------
8+// Cloud API client — core API + cloud auth
9+// ---------------------------------------------------------------------------
10+
11+const CloudApi = addGroup(CloudAuthApi);
12+
13+class CloudApiClient extends AtomHttpApi.Tag<CloudApiClient>()(
14+ "CloudApiClient",
15+ {
16+ api: CloudApi,
17+ httpClient: FetchHttpClient.layer,
18+ baseUrl: getBaseUrl(),
19+ },
20+) {}
21+
22+export { CloudApiClient };
apps/cloud/src/web/main.tsx+1−1
@@ -1,6 +1,6 @@
11 import React from "react";
22 import { createRoot } from "react-dom/client";
3−import "@executor/ui/globals.css";
3+import "@executor/react/globals.css";
44 import { App } from "./App";
55
66 const root = createRoot(document.getElementById("root")!);
apps/cloud/src/web/router.tsx+7−7
@@ -3,13 +3,13 @@ import { · 2 unmodified lines
33 createRootRoute,
44 createRoute,
55 } from "@tanstack/react-router";
6−import { ExecutorProvider } from "@executor/react";
7−import { ToolsPage } from "@web/pages/tools";
8−import { SourcesPage } from "@web/pages/sources";
9−import { SourcesAddPage } from "@web/pages/sources-add";
10−import { SourceDetailPage } from "@web/pages/source-detail";
11−import { SecretsPage } from "@web/pages/secrets";
12−import { Shell } from "@web/shell";
6+import { ExecutorProvider } from "@executor/react/api/provider";
7+import { ToolsPage } from "@executor/react/pages/tools";
8+import { SourcesPage } from "@executor/react/pages/sources";
9+import { SourcesAddPage } from "@executor/react/pages/sources-add";
10+import { SourceDetailPage } from "@executor/react/pages/source-detail";
11+import { SecretsPage } from "@executor/react/pages/secrets";
12+import { Shell } from "./shell";
1313
1414 // ---------------------------------------------------------------------------
1515 // Root layout — Shell renders <Outlet /> directly
apps/cloud/src/web/shell.tsx+483−0
@@ -0,0 +1,483 @@
1+import { Link, Outlet, useLocation } from "@tanstack/react-router";
2+import { useCallback, useEffect, useRef, useState } from "react";
3+import { useAtomRefresh, useAtomValue, Result } from "@effect-atom/atom-react";
4+import { sourcesAtom, toolsAtom } from "@executor/react/api/atoms";
5+import { useScope, useScopeInfo } from "@executor/react/api/scope-context";
6+import { Button } from "@executor/react/components/button";
7+import { useAuth } from "./auth";
8+
9+// ── Env ─────────────────────────────────────────────────────────────────
10+
11+type AppMetaEnv = {
12+ readonly VITE_APP_VERSION: string;
13+ readonly VITE_GITHUB_URL: string;
14+};
15+
16+const { VITE_APP_VERSION, VITE_GITHUB_URL } = (import.meta as ImportMeta & {
17+ readonly env: AppMetaEnv;
18+}).env;
19+
20+// ── Version helpers ─────────────────────────────────────────────────────
21+
22+type UpdateChannel = "latest" | "beta";
23+
24+const EXECUTOR_DIST_TAGS_PATH = "/v1/app/npm/dist-tags";
25+
26+type ParsedVersion = {
27+ readonly major: number;
28+ readonly minor: number;
29+ readonly patch: number;
30+ readonly prerelease: ReadonlyArray<string | number> | null;
31+};
32+
33+const semverPattern =
34+ /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<prerelease>[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
35+
36+const resolveUpdateChannel = (version: string): UpdateChannel =>
37+ version.includes("-beta.") ? "beta" : "latest";
38+
39+const parseVersion = (version: string): ParsedVersion | null => {
40+ const match = version.trim().match(semverPattern);
41+ if (!match?.groups) return null;
42+ return {
43+ major: Number(match.groups.major),
44+ minor: Number(match.groups.minor),
45+ patch: Number(match.groups.patch),
46+ prerelease: match.groups.prerelease
47+ ? match.groups.prerelease.split(".").map((id) =>
48+ /^\d+$/.test(id) ? Number(id) : id,
49+ )
50+ : null,
51+ };
52+};
53+
54+const comparePrereleaseIdentifiers = (
55+ left: ReadonlyArray<string | number> | null,
56+ right: ReadonlyArray<string | number> | null,
57+): number => {
58+ if (left === null && right === null) return 0;
59+ if (left === null) return 1;
60+ if (right === null) return -1;
61+ const max = Math.max(left.length, right.length);
62+ for (let i = 0; i < max; i++) {
63+ const l = left[i];
64+ const r = right[i];
65+ if (l === undefined) return -1;
66+ if (r === undefined) return 1;
67+ if (l === r) continue;
68+ if (typeof l === "number" && typeof r === "number") return l < r ? -1 : 1;
69+ if (typeof l === "number") return -1;
70+ if (typeof r === "number") return 1;
71+ return l < r ? -1 : 1;
72+ }
73+ return 0;
74+};
75+
76+const compareVersions = (left: string, right: string): number | null => {
77+ const lv = parseVersion(left);
78+ const rv = parseVersion(right);
79+ if (!lv || !rv) return null;
80+ if (lv.major !== rv.major) return lv.major < rv.major ? -1 : 1;
81+ if (lv.minor !== rv.minor) return lv.minor < rv.minor ? -1 : 1;
82+ if (lv.patch !== rv.patch) return lv.patch < rv.patch ? -1 : 1;
83+ return comparePrereleaseIdentifiers(lv.prerelease, rv.prerelease);
84+};
85+
86+// ── useLatestVersion ────────────────────────────────────────────────────
87+
88+function useLatestVersion(currentVersion: string) {
89+ const channel = resolveUpdateChannel(currentVersion);
90+ const [latestVersion, setLatestVersion] = useState<string | null>(null);
91+
92+ useEffect(() => {
93+ let cancelled = false;
94+ fetch(EXECUTOR_DIST_TAGS_PATH)
95+ .then(async (res) => {
96+ if (!res.ok) throw new Error(`Failed to load dist tags: ${res.status}`);
97+ return res.json() as Promise<Partial<Record<UpdateChannel, string>>>;
98+ })
99+ .then((data) => {
100+ if (!cancelled) setLatestVersion(data[channel] ?? null);
101+ })
102+ .catch(() => {});
103+ return () => { cancelled = true; };
104+ }, [channel]);
105+
106+ const updateAvailable =
107+ latestVersion !== null && compareVersions(currentVersion, latestVersion) === -1;
108+
109+ return { latestVersion, updateAvailable, channel };
110+}
111+
112+// ── UpdateCard ──────────────────────────────────────────────────────────
113+
114+function UpdateCard(props: { latestVersion: string; channel: UpdateChannel }) {
115+ const command = `npm i -g executor@${props.channel}`;
116+ const [copied, setCopied] = useState(false);
117+
118+ const handleCopy = useCallback(() => {
119+ void navigator.clipboard.writeText(command).then(() => {
120+ setCopied(true);
121+ setTimeout(() => setCopied(false), 1500);
122+ });
123+ }, [command]);
124+
125+ return (
126+ <div className="mx-2 mb-2 rounded-xl border border-primary/25 bg-primary/[0.06] p-3">
127+ <div className="flex items-center gap-2">
128+ <div className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/15">
129+ <svg viewBox="0 0 16 16" fill="none" className="size-3 text-primary">
130+ <path d="M8 3v7M5 7l3 3 3-3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
131+ <path d="M3 12h10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
132+ </svg>
133+ </div>
134+ <div className="min-w-0">
135+ <p className="text-[11px] font-semibold text-foreground">Update available</p>
136+ <p className="text-[10px] text-muted-foreground">
137+ v{props.latestVersion}
138+ </p>
139+ </div>
140+ </div>
141+ <Button
142+ type="button"
143+ variant="outline"
144+ onClick={handleCopy}
145+ className="mt-2.5 flex w-full items-center justify-between gap-2 rounded-lg border-border/60 bg-background/50 px-2.5 py-1.5 text-left hover:bg-background/80"
146+ >
147+ <code className="truncate font-mono text-[10px] text-sidebar-foreground">
148+ {command}
149+ </code>
150+ <span className="shrink-0 text-muted-foreground transition-colors group-hover:text-foreground">
151+ {copied ? (
152+ <svg viewBox="0 0 16 16" fill="none" className="size-3 text-primary">
153+ <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
154+ </svg>
155+ ) : (
156+ <svg viewBox="0 0 16 16" fill="none" className="size-3">
157+ <rect x="5" y="5" width="8" height="8" rx="1.5" stroke="currentColor" strokeWidth="1.2" />
158+ <path d="M3 11V3.5A.5.5 0 013.5 3H11" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
159+ </svg>
160+ )}
161+ </span>
162+ </Button>
163+ </div>
164+ );
165+}
166+
167+// ── NavItem ──────────────────────────────────────────────────────────────
168+
169+function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) {
170+ return (
171+ <Link
172+ to={props.to}
173+ onClick={props.onNavigate}
174+ className={[
175+ "flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors",
176+ props.active
177+ ? "bg-sidebar-active text-foreground font-medium"
178+ : "text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground",
179+ ].join(" ")}
180+ >
181+ {props.label}
182+ </Link>
183+ );
184+}
185+
186+// ── SourceList ───────────────────────────────────────────────────────────
187+
188+function SourceList(props: { pathname: string; onNavigate?: () => void }) {
189+ const scopeId = useScope();
190+ const sources = useAtomValue(sourcesAtom(scopeId));
191+
192+ return Result.match(sources, {
193+ onInitial: () => (
194+ <div className="px-2.5 py-2 text-[11px] text-muted-foreground/40">Loading…</div>
195+ ),
196+ onFailure: () => (
197+ <div className="px-2.5 py-2 text-[11px] text-muted-foreground/40">No sources yet</div>
198+ ),
199+ onSuccess: ({ value }) =>
200+ value.length === 0 ? (
201+ <div className="px-2.5 py-2 text-[11px] leading-relaxed text-muted-foreground/40">
202+ No sources yet
203+ </div>
204+ ) : (
205+ <div className="flex flex-col gap-px">
206+ {value.map((s) => {
207+ const detailPath = `/sources/${s.id}`;
208+ const active =
209+ props.pathname === detailPath ||
210+ props.pathname.startsWith(`${detailPath}/`);
211+ return (
212+ <Link
213+ key={s.id}
214+ to="/sources/$namespace"
215+ params={{ namespace: s.id }}
216+ onClick={props.onNavigate}
217+ className={[
218+ "group flex items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] transition-colors",
219+ active
220+ ? "bg-sidebar-active text-foreground font-medium"
221+ : "text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground",
222+ ].join(" ")}
223+ >
224+ <span className="flex-1 truncate">{s.name}</span>
225+ <span className="rounded bg-secondary/50 px-1 py-px text-[9px] font-medium text-muted-foreground/50">
226+ {s.kind}
227+ </span>
228+ </Link>
229+ );
230+ })}
231+ </div>
232+ ),
233+ });
234+}
235+
236+// ── ScopeLabel ───────────────────────────────────────────────────────────
237+
238+function ScopeLabel() {
239+ const { name } = useScopeInfo();
240+ // Show just the last folder name, with full path as tooltip
241+ const parts = name.replace(/\/+$/, "").split("/");
242+ const folder = parts[parts.length - 1] || name;
243+
244+ return (
245+ <div className="mb-1.5 flex items-center gap-1.5 rounded-md px-2.5 py-1.5" title={name}>
246+ <svg viewBox="0 0 16 16" fill="none" className="size-3.5 shrink-0 text-muted-foreground/50">
247+ <path
248+ d="M2 4.5C2 3.67 2.67 3 3.5 3h3.09a1 1 0 0 1 .7.29l1.42 1.42a1 1 0 0 0 .7.29H12.5c.83 0 1.5.67 1.5 1.5v5.5c0 .83-.67 1.5-1.5 1.5h-9A1.5 1.5 0 0 1 2 12V4.5z"
249+ stroke="currentColor"
250+ strokeWidth="1.2"
251+ />
252+ </svg>
253+ <span className="truncate text-[12px] font-medium text-foreground/80">
254+ {folder}
255+ </span>
256+ </div>
257+ );
258+}
259+
260+// ── UserFooter ──────────────────────────────────────────────────────────
261+
262+function UserFooter() {
263+ const auth = useAuth();
264+ if (auth.status !== "authenticated") return null;
265+
266+ const initials = auth.user.name
267+ ? auth.user.name.split(" ").map((n) => n[0]).join("").slice(0, 2).toUpperCase()
268+ : auth.user.email[0]!.toUpperCase();
269+
270+ return (
271+ <div className="shrink-0 border-t border-sidebar-border px-3 py-2.5">
272+ <div className="flex items-center gap-2.5">
273+ {auth.user.avatarUrl ? (
274+ <img
275+ src={auth.user.avatarUrl}
276+ alt=""
277+ className="size-7 shrink-0 rounded-full"
278+ />
279+ ) : (
280+ <div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary">
281+ {initials}
282+ </div>
283+ )}
284+ <div className="min-w-0 flex-1">
285+ <p className="truncate text-[12px] font-medium text-foreground">
286+ {auth.user.name ?? auth.user.email}
287+ </p>
288+ {auth.team && (
289+ <p className="truncate text-[10px] text-muted-foreground">
290+ {auth.team.name}
291+ </p>
292+ )}
293+ </div>
294+ <a
295+ href="/auth/logout"
296+ className="shrink-0 rounded-md p-1 text-muted-foreground/50 transition-colors hover:bg-sidebar-active hover:text-foreground"
297+ title="Sign out"
298+ >
299+ <svg viewBox="0 0 16 16" fill="none" className="size-3.5">
300+ <path d="M6 2H3.5A1.5 1.5 0 002 3.5v9A1.5 1.5 0 003.5 14H6M10.5 11.5L14 8l-3.5-3.5M14 8H6" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
301+ </svg>
302+ </a>
303+ </div>
304+ </div>
305+ );
306+}
307+
308+// ── SidebarContent ───────────────────────────────────────────────────────
309+
310+function SidebarContent(props: {
311+ pathname: string;
312+ onNavigate?: () => void;
313+ showBrand?: boolean;
314+ updateAvailable: boolean;
315+ latestVersion: string | null;
316+ channel: UpdateChannel;
317+}) {
318+ const isHome = props.pathname === "/";
319+ const isSecrets = props.pathname === "/secrets";
320+
321+ return (
322+ <>
323+ {props.showBrand !== false && (
324+ <div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4">
325+ <Link to="/" className="flex items-center gap-1.5">
326+ <span className="font-display text-base tracking-tight text-foreground">
327+ executor
328+ </span>
329+ </Link>
330+ </div>
331+ )}
332+
333+ <nav className="flex flex-1 flex-col overflow-y-auto p-2">
334+ <ScopeLabel />
335+ <NavItem
336+ to="/"
337+ label="Sources"
338+ active={isHome}
339+ onNavigate={props.onNavigate}
340+ />
341+ <NavItem
342+ to="/secrets"
343+ label="Secrets"
344+ active={isSecrets}
345+ onNavigate={props.onNavigate}
346+ />
347+
348+ {/* Sources list */}
349+ <div className="mt-5 mb-1 px-2.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground/50">
350+ <span>Configured</span>
351+ </div>
352+
353+ <SourceList pathname={props.pathname} onNavigate={props.onNavigate} />
354+ </nav>
355+
356+ {/* User */}
357+ <UserFooter />
358+ </>
359+ );
360+}
361+
362+// ── Shell ─────────────────────────────────────────────────────────────────
363+
364+export function Shell() {
365+ const location = useLocation();
366+ const pathname = location.pathname;
367+ const scopeId = useScope();
368+ const refreshSources = useAtomRefresh(sourcesAtom(scopeId));
369+ const refreshTools = useAtomRefresh(toolsAtom(scopeId));
370+ const { latestVersion, updateAvailable, channel } = useLatestVersion(VITE_APP_VERSION);
371+ const lastPathname = useRef(pathname);
372+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
373+ if (lastPathname.current !== pathname) {
374+ lastPathname.current = pathname;
375+ if (mobileSidebarOpen) setMobileSidebarOpen(false);
376+ }
377+
378+ // Lock scroll when mobile sidebar open
379+ useEffect(() => {
380+ if (!mobileSidebarOpen) return;
381+ const prev = document.body.style.overflow;
382+ document.body.style.overflow = "hidden";
383+ return () => {
384+ document.body.style.overflow = prev;
385+ };
386+ }, [mobileSidebarOpen]);
387+
388+ useEffect(() => {
389+ if (!import.meta.hot) {
390+ return;
391+ }
392+
393+ const refreshBackendData = () => {
394+ refreshSources();
395+ refreshTools();
396+ };
397+
398+ import.meta.hot.on("executor:backend-updated", refreshBackendData);
399+
400+ return () => {
401+ import.meta.hot?.off("executor:backend-updated", refreshBackendData);
402+ };
403+ }, [refreshSources, refreshTools]);
404+
405+ return (
406+ <div className="flex h-screen overflow-hidden">
407+ {/* Desktop sidebar */}
408+ <aside className="hidden w-52 shrink-0 border-r border-sidebar-border bg-sidebar md:flex md:flex-col lg:w-56">
409+ <SidebarContent
410+ pathname={pathname}
411+ updateAvailable={updateAvailable}
412+ latestVersion={latestVersion}
413+ channel={channel}
414+ />
415+ </aside>
416+
417+ {/* Mobile sidebar overlay */}
418+ {mobileSidebarOpen && (
419+ <div className="fixed inset-0 z-50 flex md:hidden">
420+ <button
421+ type="button"
422+ aria-label="Close navigation"
423+ className="absolute inset-0 bg-black/45 backdrop-blur-[1px]"
424+ onClick={() => setMobileSidebarOpen(false)}
425+ />
426+ <div className="relative flex h-full w-[84vw] max-w-xs flex-col border-r border-sidebar-border bg-sidebar shadow-2xl">
427+ <div className="flex h-12 shrink-0 items-center justify-between border-b border-sidebar-border px-4">
428+ <Link to="/" className="flex items-center gap-1.5">
429+ <span className="font-display text-base tracking-tight text-foreground">
430+ executor
431+ </span>
432+ </Link>
433+ <button
434+ type="button"
435+ aria-label="Close navigation"
436+ onClick={() => setMobileSidebarOpen(false)}
437+ className="size-8 flex items-center justify-center rounded-md text-sidebar-foreground hover:bg-sidebar-active hover:text-foreground"
438+ >
439+ <svg viewBox="0 0 16 16" className="size-3.5">
440+ <path d="M3 3l10 10M13 3L3 13" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
441+ </svg>
442+ </button>
443+ </div>
444+ <SidebarContent
445+ pathname={pathname}
446+ onNavigate={() => setMobileSidebarOpen(false)}
447+ showBrand={false}
448+ updateAvailable={updateAvailable}
449+ latestVersion={latestVersion}
450+ channel={channel}
451+ />
452+ </div>
453+ </div>
454+ )}
455+
456+ {/* Main content */}
457+ <main className="flex min-h-0 flex-1 flex-col min-w-0 overflow-hidden">
458+ {/* Mobile top bar */}
459+ <div className="flex h-12 shrink-0 items-center justify-between border-b border-border bg-background px-4 md:hidden">
460+ <button
461+ type="button"
462+ aria-label="Open navigation"
463+ onClick={() => setMobileSidebarOpen(true)}
464+ className="size-8 flex items-center justify-center rounded-md border border-border bg-card hover:bg-accent/50"
465+ >
466+ <svg viewBox="0 0 16 16" className="size-4">
467+ <path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
468+ </svg>
469+ </button>
470+ <Link to="/" className="flex items-center gap-1.5">
471+ <span className="font-display text-base tracking-tight text-foreground">
472+ executor
473+ </span>
474+ </Link>
475+ <div className="w-8 shrink-0" />
476+ </div>
477+
478+ {/* Outlet — pages control their own padding/scroll */}
479+ <Outlet />
480+ </main>
481+ </div>
482+ );
483+}
apps/cloud/tsconfig.json+0−3
@@ -12,9 +12,6 @@ · 11 unmodified lines
1212 "declarationMap": true,
1313 "sourceMap": true,
1414 "jsx": "react-jsx",
15− "paths": {
16− "@web/*": ["../local/src/web/*"]
17− },
1815 "plugins": [
1916 {
2017 "name": "@effect/language-service",
apps/cloud/vite.config.ts+0−6
@@ -1,4 +1,3 @@
1−import { resolve } from "node:path";
21 import { defineConfig } from "vite";
32 import react from "@vitejs/plugin-react";
43 import tailwindcss from "@tailwindcss/vite";
@@ -11,11 +10,6 @@ export default defineConfig({ · 6 unmodified lines
1110 "import.meta.env.VITE_APP_VERSION": JSON.stringify("0.0.0"),
1211 "import.meta.env.VITE_GITHUB_URL": JSON.stringify("https://github.com/RhysSullivan/executor"),
1312 },
14− resolve: {
15− alias: {
16− "@web": resolve(__dirname, "../local/src/web"),
17− },
18− },
1913 server: {
2014 proxy: {
2115 "/v1": `http://localhost:${API_PORT}`,
apps/local/package.json+0−1
@@ -25,7 +25,6 @@ · 24 unmodified lines
2525 "@executor/react": "workspace:*",
2626 "@executor/sdk": "workspace:*",
2727 "@executor/storage-file": "workspace:*",
28− "@executor/ui": "workspace:*",
2928 "@modelcontextprotocol/sdk": "^1.12.1",
3029 "@shikijs/langs": "^4.0.2",
3130 "@shikijs/themes": "^4.0.2",
apps/local/src/web/lib/use-scope.ts+0−1
@@ -1 +0,0 @@
1−export { useScope } from "@executor/react";
apps/local/src/web/main.tsx+1−1
@@ -1,6 +1,6 @@
11 import React from "react";
22 import { createRoot } from "react-dom/client";
3−import "@executor/ui/globals.css";
3+import "@executor/react/globals.css";
44 import { App } from "./App";
55
66 const root = createRoot(document.getElementById("root")!);
apps/local/src/web/router.tsx+6−6
@@ -3,12 +3,12 @@ import { · 2 unmodified lines
33 createRootRoute,
44 createRoute,
55 } from "@tanstack/react-router";
6−import { ExecutorProvider } from "@executor/react";
7−import { ToolsPage } from "./pages/tools";
8−import { SourcesPage } from "./pages/sources";
9−import { SourcesAddPage } from "./pages/sources-add";
10−import { SourceDetailPage } from "./pages/source-detail";
11−import { SecretsPage } from "./pages/secrets";
6+import { ExecutorProvider } from "@executor/react/api/provider";
7+import { ToolsPage } from "@executor/react/pages/tools";
8+import { SourcesPage } from "@executor/react/pages/sources";
9+import { SourcesAddPage } from "@executor/react/pages/sources-add";
10+import { SourceDetailPage } from "@executor/react/pages/source-detail";
11+import { SecretsPage } from "@executor/react/pages/secrets";
1212 import { Shell } from "./shell";
1313
1414 // ---------------------------------------------------------------------------
apps/local/src/web/shell.tsx+4−10
@@ -1,15 +1,9 @@
11 import { Link, Outlet, useLocation } from "@tanstack/react-router";
22 import { useCallback, useEffect, useRef, useState } from "react";
3−import {
4− useAtomRefresh,
5− useAtomValue,
6− Result,
7− sourcesAtom,
8− toolsAtom,
9− useScope,
10− useScopeInfo,
11−} from "@executor/react";
12−import { Button } from "@executor/ui/components/button";
3+import { useAtomRefresh, useAtomValue, Result } from "@effect-atom/atom-react";
4+import { sourcesAtom, toolsAtom } from "@executor/react/api/atoms";
5+import { useScope, useScopeInfo } from "@executor/react/api/scope-context";
6+import { Button } from "@executor/react/components/button";
137
148 // ── Env ─────────────────────────────────────────────────────────────────
159
bun.lock+10−40
@@ -53,7 +53,6 @@ · 52 unmodified lines
5353 "@executor/react": "workspace:*",
5454 "@executor/sdk": "workspace:*",
5555 "@executor/storage-postgres": "workspace:*",
56− "@executor/ui": "workspace:*",
5756 "@tanstack/react-router": "^1.168.10",
5857 "@workos-inc/node": "^7.0.0",
5958 "drizzle-orm": "catalog:",
@@ -106,7 +105,6 @@ · 46 unmodified lines
106105 "@executor/react": "workspace:*",
107106 "@executor/sdk": "workspace:*",
108107 "@executor/storage-file": "workspace:*",
109− "@executor/ui": "workspace:*",
110108 "@modelcontextprotocol/sdk": "^1.12.1",
111109 "@shikijs/langs": "^4.0.2",
112110 "@shikijs/themes": "^4.0.2",
@@ -140,25 +138,6 @@ · 27 unmodified lines
140138 "tailwindcss": "^4.2.2",
141139 },
142140 },
143− "packages/clients/react": {
144− "name": "@executor/react",
145− "version": "1.4.0",
146− "dependencies": {
147− "@effect-atom/atom": "^0.5.3",
148− "@effect-atom/atom-react": "^0.5.0",
149− "@effect/platform": "catalog:",
150− "@executor/api": "workspace:*",
151− "@executor/sdk": "workspace:*",
152− "@executor/ui": "workspace:*",
153− "effect": "catalog:",
154− "react": "^19.1.0",
155− },
156− "devDependencies": {
157− "@types/node": "catalog:",
158− "@types/react": "^19.1.0",
159− "typescript": "^5.9.3",
160− },
161− },
162141 "packages/core/api": {
163142 "name": "@executor/api",
164143 "version": "1.4.0",
@@ -357,7 +336,6 @@ · 192 unmodified lines
357336 "@effect-atom/atom-react": "^0.5.0",
358337 "@effect/vitest": "catalog:",
359338 "@executor/react": "workspace:*",
360− "@executor/ui": "workspace:*",
361339 "@types/node": "catalog:",
362340 "@types/react": "^19.1.0",
363341 "bun-types": "catalog:",
@@ -367,14 +345,12 @@ · 3 unmodified lines
367345 "peerDependencies": {
368346 "@effect-atom/atom-react": "^0.5.0",
369347 "@executor/react": "workspace:*",
370− "@executor/ui": "workspace:*",
371348 "@tanstack/react-router": "^1.168.10",
372349 "react": "^19.1.0",
373350 },
374351 "optionalPeers": [
375352 "@effect-atom/atom-react",
376353 "@executor/react",
377− "@executor/ui",
378354 "@tanstack/react-router",
379355 "react",
380356 ],
@@ -393,7 +369,6 @@ · 12 unmodified lines
393369 "@effect/vitest": "catalog:",
394370 "@executor/api": "workspace:*",
395371 "@executor/react": "workspace:*",
396− "@executor/ui": "workspace:*",
397372 "@types/node": "catalog:",
398373 "@types/react": "^19.1.0",
399374 "bun-types": "catalog:",
@@ -404,7 +379,6 @@ · 4 unmodified lines
404379 "@effect-atom/atom-react": "^0.5.0",
405380 "@executor/api": "workspace:*",
406381 "@executor/react": "workspace:*",
407− "@executor/ui": "workspace:*",
408382 "@tanstack/react-router": "^1.168.10",
409383 "react": "^19.1.0",
410384 },
@@ -412,7 +386,6 @@ · 1 unmodified line
412386 "@effect-atom/atom-react",
413387 "@executor/api",
414388 "@executor/react",
415− "@executor/ui",
416389 "@tanstack/react-router",
417390 "react",
418391 ],
@@ -447,7 +420,6 @@ · 28 unmodified lines
447420 "@effect/vitest": "catalog:",
448421 "@executor/api": "workspace:*",
449422 "@executor/react": "workspace:*",
450− "@executor/ui": "workspace:*",
451423 "@types/node": "catalog:",
452424 "@types/react": "^19.1.0",
453425 "bun-types": "catalog:",
@@ -459,7 +431,6 @@ · 5 unmodified lines
459431 "@effect-atom/atom-react": "^0.5.0",
460432 "@executor/api": "workspace:*",
461433 "@executor/react": "workspace:*",
462− "@executor/ui": "workspace:*",
463434 "@tanstack/react-router": "^1.168.10",
464435 "react": "^19.1.0",
465436 },
@@ -467,7 +438,6 @@ · 1 unmodified line
467438 "@effect-atom/atom-react",
468439 "@executor/api",
469440 "@executor/react",
470− "@executor/ui",
471441 "@tanstack/react-router",
472442 "react",
473443 ],
@@ -495,13 +465,11 @@ · 21 unmodified lines
495465 "peerDependencies": {
496466 "@effect-atom/atom-react": "^0.5.0",
497467 "@executor/react": "workspace:*",
498− "@executor/ui": "workspace:*",
499468 "react": ">=18",
500469 },
501470 "optionalPeers": [
502471 "@effect-atom/atom-react",
503472 "@executor/react",
504− "@executor/ui",
505473 "react",
506474 ],
507475 },
@@ -522,7 +490,6 @@ · 14 unmodified lines
522490 "@effect/vitest": "catalog:",
523491 "@executor/api": "workspace:*",
524492 "@executor/react": "workspace:*",
525− "@executor/ui": "workspace:*",
526493 "@types/node": "catalog:",
527494 "@types/react": "^19.1.0",
528495 "bun-types": "catalog:",
@@ -533,7 +500,6 @@ · 4 unmodified lines
533500 "@effect-atom/atom-react": "^0.5.0",
534501 "@executor/api": "workspace:*",
535502 "@executor/react": "workspace:*",
536− "@executor/ui": "workspace:*",
537503 "@tanstack/react-router": "^1.168.10",
538504 "react": "^19.1.0",
539505 },
@@ -541,16 +507,20 @@ · 1 unmodified line
541507 "@effect-atom/atom-react",
542508 "@executor/api",
543509 "@executor/react",
544− "@executor/ui",
545510 "@tanstack/react-router",
546511 "react",
547512 ],
548513 },
549− "packages/ui": {
550− "name": "@executor/ui",
514+ "packages/react": {
515+ "name": "@executor/react",
551516 "version": "1.4.0",
552517 "dependencies": {
553518 "@base-ui/react": "^1.3.0",
519+ "@effect-atom/atom": "^0.5.3",
520+ "@effect-atom/atom-react": "^0.5.0",
521+ "@effect/platform": "catalog:",
522+ "@executor/api": "workspace:*",
523+ "@executor/sdk": "workspace:*",
554524 "@hookform/resolvers": "^5.2.2",
555525 "@shikijs/langs": "^4.0.2",
556526 "@shikijs/themes": "^4.0.2",
@@ -558,6 +528,7 @@ · 1 unmodified line
558528 "clsx": "^2.1.1",
559529 "cmdk": "^1.1.1",
560530 "date-fns": "^4.1.0",
531+ "effect": "catalog:",
561532 "embla-carousel-react": "^8.6.0",
562533 "input-otp": "^1.4.2",
563534 "lucide-react": "^1.7.0",
@@ -576,6 +547,7 @@ · 12 unmodified lines
576547 "zod": "^4.3.6",
577548 },
578549 "devDependencies": {
550+ "@types/node": "catalog:",
579551 "@types/react": "^19.1.0",
580552 "hast-util-to-jsx-runtime": "^2.3.6",
581553 "tailwindcss": "^4.1.10",
@@ -885,7 +857,7 @@ · 303 unmodified lines
885857
886858 "@executor/plugin-openapi": ["@executor/plugin-openapi@workspace:packages/plugins/openapi"],
887859
888− "@executor/react": ["@executor/react@workspace:packages/clients/react"],
860+ "@executor/react": ["@executor/react@workspace:packages/react"],
889861
890862 "@executor/runtime-deno-subprocess": ["@executor/runtime-deno-subprocess@workspace:packages/kernel/runtime-deno-subprocess"],
891863
@@ -897,8 +869,6 @@ · 5 unmodified lines
897869
898870 "@executor/storage-postgres": ["@executor/storage-postgres@workspace:packages/core/storage-postgres"],
899871
900− "@executor/ui": ["@executor/ui@workspace:packages/ui"],
901−
902872 "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
903873
904874 "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
package.json+1−1
@@ -24,7 +24,7 @@ · 23 unmodified lines
2424 "packageManager": "bun@1.3.11",
2525 "workspaces": [
2626 "packages/*/*",
27− "packages/ui",
27+ "packages/react",
2828 "apps/*"
2929 ],
3030 "catalog": {
packages/clients/react/CHANGELOG.md+0−4
@@ -1,4 +0,0 @@
1−# @executor/react changelog
2−
3−This file exists for Changesets release workflow compatibility.
4−Canonical user-facing release notes are published on GitHub Releases.
packages/clients/react/package.json+0−27
@@ -1,27 +0,0 @@
1−{
2− "name": "@executor/react",
3− "version": "1.4.0",
4− "private": true,
5− "type": "module",
6− "exports": {
7− ".": "./src/index.ts"
8− },
9− "dependencies": {
10− "@effect/platform": "catalog:",
11− "@effect-atom/atom": "^0.5.3",
12− "@effect-atom/atom-react": "^0.5.0",
13− "@executor/api": "workspace:*",
14− "@executor/ui": "workspace:*",
15− "@executor/sdk": "workspace:*",
16− "effect": "catalog:",
17− "react": "^19.1.0"
18− },
19− "devDependencies": {
20− "@types/react": "^19.1.0",
21− "@types/node": "catalog:",
22− "typescript": "^5.9.3"
23− },
24− "scripts": {
25− "typecheck": "tsc --noEmit"
26− }
27−}
packages/clients/react/src/index.ts+0−54
@@ -1,54 +0,0 @@
1−// Re-export effect-atom essentials so consumers don't need direct deps
2−export {
3− Atom,
4− AtomHttpApi,
5− Result,
6− RegistryContext,
7− RegistryProvider,
8− useAtom,
9− useAtomMount,
10− useAtomRefresh,
11− useAtomSet,
12− useAtomSuspense,
13− useAtomValue,
14−} from "@effect-atom/atom-react";
15−
16−// Base URL management
17−export { getBaseUrl, setBaseUrl } from "./base-url";
18−
19−// Typed API client
20−export { ExecutorApiClient } from "./client";
21−
22−// Query & mutation atoms
23−export {
24− toolsAtom,
25− sourceToolsAtom,
26− toolSchemaAtom,
27− sourcesAtom,
28− sourceAtom,
29− secretsAtom,
30− secretStatusAtom,
31− setSecret,
32− resolveSecret,
33− removeSecret,
34− removeSource,
35− refreshSource,
36− detectSource,
37−} from "./atoms";
38−
39−// Scope hook
40−export { useScope, useScopeInfo } from "./use-scope";
41−export type { ScopeInfo } from "./use-scope";
42−
43−// Provider
44−export { ExecutorProvider } from "./provider";
45−export { SecretPicker } from "./secret-picker";
46−export type { SecretPickerSecret } from "./secret-picker";
47−
48−// Re-export commonly needed SDK types
49−export { ScopeId, ToolId } from "@executor/sdk";
50−export type { ScopeId as ScopeIdType, ToolId as ToolIdType } from "@executor/sdk";
51−
52−// Plugin contracts
53−export type { SourcePlugin, SourcePreset } from "./source-plugin";
54−export type { SecretProviderPlugin } from "./secret-provider-plugin";
packages/clients/react/src/use-scope.ts+0−2
@@ -1,2 +0,0 @@
1−export { useScope, useScopeInfo } from "./scope-context";
2−export type { ScopeInfo } from "./scope-context";
packages/plugins/google-discovery/package.json+1−6
@@ -24,8 +24,7 @@ · 23 unmodified lines
2424 "react": "^19.1.0",
2525 "@effect-atom/atom-react": "^0.5.0",
2626 "@tanstack/react-router": "^1.168.10",
27− "@executor/react": "workspace:*",
28− "@executor/ui": "workspace:*"
27+ "@executor/react": "workspace:*"
2928 },
3029 "peerDependenciesMeta": {
3130 "react": {
@@ -39,16 +38,12 @@ · 7 unmodified lines
3938 },
4039 "@executor/react": {
4140 "optional": true
42− },
43− "@executor/ui": {
44− "optional": true
4541 }
4642 },
4743 "devDependencies": {
4844 "@effect-atom/atom-react": "^0.5.0",
4945 "@effect/vitest": "catalog:",
5046 "@executor/react": "workspace:*",
51− "@executor/ui": "workspace:*",
5247 "@types/node": "catalog:",
5348 "@types/react": "^19.1.0",
5449 "bun-types": "catalog:",
packages/plugins/google-discovery/src/react/AddGoogleDiscoverySource.tsx+10−14
@@ -1,25 +1,21 @@
11 import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22 import { useAtomSet, useAtomValue, useAtomRefresh, Result } from "@effect-atom/atom-react";
33
4−import {
5− useScope,
6− SecretPicker,
7− secretsAtom,
8− setSecret,
9− type SecretPickerSecret,
10−} from "@executor/react";
4+import { secretsAtom, setSecret } from "@executor/react/api/atoms";
5+import { useScope } from "@executor/react/api/scope-context";
6+import { SecretPicker, type SecretPickerSecret } from "@executor/react/plugins/secret-picker";
117 import { SecretId } from "@executor/sdk";
12−import { Badge } from "@executor/ui/components/badge";
13−import { Button } from "@executor/ui/components/button";
8+import { Badge } from "@executor/react/components/badge";
9+import { Button } from "@executor/react/components/button";
1410 import {
1511 Collapsible,
1612 CollapsibleContent,
1713 CollapsibleTrigger,
18−} from "@executor/ui/components/collapsible";
19−import { Input } from "@executor/ui/components/input";
20−import { Label } from "@executor/ui/components/label";
21−import { RadioGroup, RadioGroupItem } from "@executor/ui/components/radio-group";
22−import { Spinner } from "@executor/ui/components/spinner";
14+} from "@executor/react/components/collapsible";
15+import { Input } from "@executor/react/components/input";
16+import { Label } from "@executor/react/components/label";
17+import { RadioGroup, RadioGroupItem } from "@executor/react/components/radio-group";
18+import { Spinner } from "@executor/react/components/spinner";
2319 import {
2420 addGoogleDiscoverySource,
2521 probeGoogleDiscovery,
packages/plugins/google-discovery/src/react/EditGoogleDiscoverySource.tsx+2−2
@@ -1,5 +1,5 @@
1−import { Badge } from "@executor/ui/components/badge";
2−import { Button } from "@executor/ui/components/button";
1+import { Badge } from "@executor/react/components/badge";
2+import { Button } from "@executor/react/components/button";
33
44 export default function EditGoogleDiscoverySource({
55 sourceId,
packages/plugins/google-discovery/src/react/GoogleDiscoverySourceSummary.tsx+1−1
@@ -1,4 +1,4 @@
1−import { Badge } from "@executor/ui/components/badge";
1+import { Badge } from "@executor/react/components/badge";
22
33 export default function GoogleDiscoverySourceSummary({
44 sourceId,
packages/plugins/google-discovery/src/react/client.ts+1−1
@@ -1,7 +1,7 @@
11 import { AtomHttpApi } from "@effect-atom/atom-react";
22 import { FetchHttpClient } from "@effect/platform";
33 import { addGroup } from "@executor/api";
4−import { getBaseUrl } from "@executor/react";
4+import { getBaseUrl } from "@executor/react/api/base-url";
55 import { GoogleDiscoveryGroup } from "../api/group";
66
77 const GoogleDiscoveryApi = addGroup(GoogleDiscoveryGroup);
packages/plugins/google-discovery/src/react/source-plugin.ts+1−1
@@ -1,5 +1,5 @@
11 import { lazy } from "react";
2−import type { SourcePlugin } from "@executor/react";
2+import type { SourcePlugin } from "@executor/react/plugins/source-plugin";
33 import { googleDiscoveryPresets } from "../sdk/presets";
44
55 export const googleDiscoverySourcePlugin: SourcePlugin = {
packages/plugins/graphql/package.json+1−6
@@ -25,8 +25,7 @@ · 24 unmodified lines
2525 "@effect-atom/atom-react": "^0.5.0",
2626 "@tanstack/react-router": "^1.168.10",
2727 "@executor/api": "workspace:*",
28− "@executor/react": "workspace:*",
29− "@executor/ui": "workspace:*"
28+ "@executor/react": "workspace:*"
3029 },
3130 "peerDependenciesMeta": {
3231 "react": {
@@ -43,9 +42,6 @@ · 10 unmodified lines
4342 },
4443 "@executor/react": {
4544 "optional": true
46− },
47− "@executor/ui": {
48− "optional": true
4945 }
5046 },
5147 "devDependencies": {
@@ -53,7 +49,6 @@ · 1 unmodified line
5349 "@effect-atom/atom-react": "^0.5.0",
5450 "@executor/api": "workspace:*",
5551 "@executor/react": "workspace:*",
56− "@executor/ui": "workspace:*",
5752 "@types/react": "^19.1.0",
5853 "@effect/vitest": "catalog:",
5954 "@types/node": "catalog:",
packages/plugins/graphql/src/react/AddGraphqlSource.tsx+7−11
@@ -1,18 +1,14 @@
11 import { useState } from "react";
22 import { useAtomSet, useAtomValue, useAtomRefresh, Result } from "@effect-atom/atom-react";
33
4−import {
5− secretsAtom,
6− setSecret,
7− useScope,
8− SecretPicker,
9− type SecretPickerSecret,
10−} from "@executor/react";
4+import { secretsAtom, setSecret } from "@executor/react/api/atoms";
5+import { useScope } from "@executor/react/api/scope-context";
6+import { SecretPicker, type SecretPickerSecret } from "@executor/react/plugins/secret-picker";
117 import { SecretId } from "@executor/sdk";
12−import { Button } from "@executor/ui/components/button";
13−import { Input } from "@executor/ui/components/input";
14−import { Label } from "@executor/ui/components/label";
15−import { Spinner } from "@executor/ui/components/spinner";
8+import { Button } from "@executor/react/components/button";
9+import { Input } from "@executor/react/components/input";
10+import { Label } from "@executor/react/components/label";
11+import { Spinner } from "@executor/react/components/spinner";
1612 import { addGraphqlSource } from "./atoms";
1713 import type { HeaderValue } from "../sdk/types";
1814
packages/plugins/graphql/src/react/client.ts+1−1
@@ -1,7 +1,7 @@
11 import { AtomHttpApi } from "@effect-atom/atom-react";
22 import { FetchHttpClient } from "@effect/platform";
33 import { addGroup } from "@executor/api";
4−import { getBaseUrl } from "@executor/react";
4+import { getBaseUrl } from "@executor/react/api/base-url";
55 import { GraphqlGroup } from "../api/group";
66
77 // ---------------------------------------------------------------------------
packages/plugins/graphql/src/react/source-plugin.ts+1−1
@@ -1,5 +1,5 @@
11 import { lazy } from "react";
2−import type { SourcePlugin } from "@executor/react";
2+import type { SourcePlugin } from "@executor/react/plugins/source-plugin";
33 import { graphqlPresets } from "../sdk/presets";
44
55 export const graphqlSourcePlugin: SourcePlugin = {
packages/plugins/mcp/package.json+1−6
@@ -26,8 +26,7 @@ · 25 unmodified lines
2626 "@effect-atom/atom-react": "^0.5.0",
2727 "@tanstack/react-router": "^1.168.10",
2828 "@executor/api": "workspace:*",
29− "@executor/react": "workspace:*",
30− "@executor/ui": "workspace:*"
29+ "@executor/react": "workspace:*"
3130 },
3231 "peerDependenciesMeta": {
3332 "react": {
@@ -44,9 +43,6 @@ · 10 unmodified lines
4443 },
4544 "@executor/react": {
4645 "optional": true
47− },
48− "@executor/ui": {
49− "optional": true
5046 }
5147 },
5248 "devDependencies": {
@@ -54,7 +50,6 @@ · 1 unmodified line
5450 "@effect-atom/atom-react": "^0.5.0",
5551 "@executor/api": "workspace:*",
5652 "@executor/react": "workspace:*",
57− "@executor/ui": "workspace:*",
5853 "@types/react": "^19.1.0",
5954 "@effect/vitest": "catalog:",
6055 "@types/node": "catalog:",
packages/plugins/mcp/src/react/AddMcpSource.tsx+6−6
@@ -1,12 +1,12 @@
11 import { useReducer, useCallback, useEffect, useRef, useState } from "react";
22 import { useAtomSet } from "@effect-atom/atom-react";
33
4−import { useScope } from "@executor/react";
5−import { Button } from "@executor/ui/components/button";
6−import { Input } from "@executor/ui/components/input";
7−import { Label } from "@executor/ui/components/label";
8−import { Badge } from "@executor/ui/components/badge";
9−import { Spinner } from "@executor/ui/components/spinner";
4+import { useScope } from "@executor/react/api/scope-context";
5+import { Button } from "@executor/react/components/button";
6+import { Input } from "@executor/react/components/input";
7+import { Label } from "@executor/react/components/label";
8+import { Badge } from "@executor/react/components/badge";
9+import { Spinner } from "@executor/react/components/spinner";
1010 import { probeMcpEndpoint, addMcpSource, startMcpOAuth } from "./atoms";
1111 import { mcpPresets, type McpPreset } from "../sdk/presets";
1212
packages/plugins/mcp/src/react/EditMcpSource.tsx+2−2
@@ -1,5 +1,5 @@
1−import { Button } from "@executor/ui/components/button";
2−import { Badge } from "@executor/ui/components/badge";
1+import { Button } from "@executor/react/components/button";
2+import { Badge } from "@executor/react/components/badge";
33
44 // ---------------------------------------------------------------------------
55 // Edit MCP Source — config view for an existing MCP source
packages/plugins/mcp/src/react/McpSourceSummary.tsx+1−1
@@ -1,4 +1,4 @@
1−import { Badge } from "@executor/ui/components/badge";
1+import { Badge } from "@executor/react/components/badge";
22
33 // ---------------------------------------------------------------------------
44 // MCP Source Summary — shown in the source list
packages/plugins/mcp/src/react/client.ts+1−1
@@ -1,7 +1,7 @@
11 import { AtomHttpApi } from "@effect-atom/atom-react";
22 import { FetchHttpClient } from "@effect/platform";
33 import { addGroup } from "@executor/api";
4−import { getBaseUrl } from "@executor/react";
4+import { getBaseUrl } from "@executor/react/api/base-url";
55 import { McpGroup } from "../api/group";
66
77 // ---------------------------------------------------------------------------
packages/plugins/mcp/src/react/source-plugin.ts+1−1
@@ -1,5 +1,5 @@
11 import { lazy } from "react";
2−import type { SourcePlugin } from "@executor/react";
2+import type { SourcePlugin } from "@executor/react/plugins/source-plugin";
33 import { mcpPresets } from "../sdk/presets";
44
55 export const mcpSourcePlugin: SourcePlugin = {
packages/plugins/onepassword/package.json+1−5
@@ -33,8 +33,7 @@ · 32 unmodified lines
3333 "peerDependencies": {
3434 "react": ">=18",
3535 "@effect-atom/atom-react": "^0.5.0",
36− "@executor/react": "workspace:*",
37− "@executor/ui": "workspace:*"
36+ "@executor/react": "workspace:*"
3837 },
3938 "peerDependenciesMeta": {
4039 "react": {
@@ -45,9 +44,6 @@ · 4 unmodified lines
4544 },
4645 "@executor/react": {
4746 "optional": true
48− },
49− "@executor/ui": {
50− "optional": true
5147 }
5248 }
5349 }
packages/plugins/onepassword/src/react/OnePasswordSettings.tsx+6−6
@@ -1,17 +1,17 @@
11 import { useState } from "react";
22 import { useAtomSet, useAtomValue, useAtomRefresh } from "@effect-atom/atom-react";
33 import { Result } from "@effect-atom/atom-react";
4−import { useScope } from "@executor/react";
5−import { Button } from "@executor/ui/components/button";
6−import { Input } from "@executor/ui/components/input";
7−import { Label } from "@executor/ui/components/label";
4+import { useScope } from "@executor/react/api/scope-context";
5+import { Button } from "@executor/react/components/button";
6+import { Input } from "@executor/react/components/input";
7+import { Label } from "@executor/react/components/label";
88 import {
99 Select,
1010 SelectContent,
1111 SelectItem,
1212 SelectTrigger,
1313 SelectValue,
14−} from "@executor/ui/components/select";
14+} from "@executor/react/components/select";
1515 import {
1616 Dialog,
1717 DialogContent,
@@ -20,7 +20,7 @@ import { · 2 unmodified lines
2020 DialogDescription,
2121 DialogFooter,
2222 DialogClose,
23−} from "@executor/ui/components/dialog";
23+} from "@executor/react/components/dialog";
2424
2525 import {
2626 onepasswordConfigAtom,
packages/plugins/onepassword/src/react/client.ts+1−1
@@ -1,7 +1,7 @@
11 import { AtomHttpApi } from "@effect-atom/atom-react";
22 import { FetchHttpClient } from "@effect/platform";
33 import { addGroup } from "@executor/api";
4−import { getBaseUrl } from "@executor/react";
4+import { getBaseUrl } from "@executor/react/api/base-url";
55 import { OnePasswordGroup } from "../api/group";
66
77 // ---------------------------------------------------------------------------
packages/plugins/onepassword/src/react/secret-provider-plugin.ts+1−1
@@ -1,5 +1,5 @@
11 import { lazy } from "react";
2−import type { SecretProviderPlugin } from "@executor/react";
2+import type { SecretProviderPlugin } from "@executor/react/plugins/secret-provider-plugin";
33
44 export const onePasswordSecretProviderPlugin: SecretProviderPlugin = {
55 key: "onepassword",
packages/plugins/openapi/package.json+1−6
@@ -28,8 +28,7 @@ · 27 unmodified lines
2828 "@effect-atom/atom-react": "^0.5.0",
2929 "@tanstack/react-router": "^1.168.10",
3030 "@executor/api": "workspace:*",
31− "@executor/react": "workspace:*",
32− "@executor/ui": "workspace:*"
31+ "@executor/react": "workspace:*"
3332 },
3433 "peerDependenciesMeta": {
3534 "react": {
@@ -46,9 +45,6 @@ · 10 unmodified lines
4645 },
4746 "@executor/react": {
4847 "optional": true
49− },
50− "@executor/ui": {
51− "optional": true
5248 }
5349 },
5450 "devDependencies": {
@@ -56,7 +52,6 @@ · 1 unmodified line
5652 "@effect-atom/atom-react": "^0.5.0",
5753 "@executor/api": "workspace:*",
5854 "@executor/react": "workspace:*",
59− "@executor/ui": "workspace:*",
6055 "@types/react": "^19.1.0",
6156 "@effect/vitest": "catalog:",
6257 "@types/node": "catalog:",
packages/plugins/openapi/src/react/AddOpenApiSource.tsx+10−15
@@ -2,22 +2,17 @@ import { useState, useEffect, useRef } from "react"; · 1 unmodified line
22 import { useAtomSet, useAtomValue, useAtomRefresh, Result } from "@effect-atom/atom-react";
33 import { Option } from "effect";
44
5−import {
6− secretsAtom,
7− setSecret,
8− resolveSecret,
9− useScope,
10− SecretPicker,
11− type SecretPickerSecret,
12−} from "@executor/react";
5+import { secretsAtom, setSecret, resolveSecret } from "@executor/react/api/atoms";
6+import { useScope } from "@executor/react/api/scope-context";
7+import { SecretPicker, type SecretPickerSecret } from "@executor/react/plugins/secret-picker";
138 import { SecretId } from "@executor/sdk";
14−import { Button } from "@executor/ui/components/button";
15−import { Input } from "@executor/ui/components/input";
16−import { Label } from "@executor/ui/components/label";
17−import { Textarea } from "@executor/ui/components/textarea";
18−import { Badge } from "@executor/ui/components/badge";
19−import { RadioGroup, RadioGroupItem } from "@executor/ui/components/radio-group";
20−import { Spinner } from "@executor/ui/components/spinner";
9+import { Button } from "@executor/react/components/button";
10+import { Input } from "@executor/react/components/input";
11+import { Label } from "@executor/react/components/label";
12+import { Textarea } from "@executor/react/components/textarea";
13+import { Badge } from "@executor/react/components/badge";
14+import { RadioGroup, RadioGroupItem } from "@executor/react/components/radio-group";
15+import { Spinner } from "@executor/react/components/spinner";
2116 import { previewOpenApiSpec, addOpenApiSpec } from "./atoms";
2217 import type { SpecPreview, HeaderPreset } from "../sdk/preview";
2318 import type { HeaderValue } from "../sdk/types";
packages/plugins/openapi/src/react/client.ts+1−1
@@ -1,7 +1,7 @@
11 import { AtomHttpApi } from "@effect-atom/atom-react";
22 import { FetchHttpClient } from "@effect/platform";
33 import { addGroup } from "@executor/api";
4−import { getBaseUrl } from "@executor/react";
4+import { getBaseUrl } from "@executor/react/api/base-url";
55 import { OpenApiGroup } from "../api/group";
66
77 // ---------------------------------------------------------------------------
packages/plugins/openapi/src/react/source-plugin.ts+1−1
@@ -1,5 +1,5 @@
11 import { lazy } from "react";
2−import type { SourcePlugin } from "@executor/react";
2+import type { SourcePlugin } from "@executor/react/plugins/source-plugin";
33 import { openApiPresets } from "../sdk/presets";
44
55 export const openApiSourcePlugin: SourcePlugin = {
packages/react/package.jsonrenamed from packages/ui/package.json+11−1
@@ -1,9 +1,12 @@
11 {
2− "name": "@executor/ui",
2+ "name": "@executor/react",
33 "version": "1.4.0",
44 "private": true,
55 "type": "module",
66 "exports": {
7+ "./api/*": "./src/api/*.tsx",
8+ "./plugins/*": "./src/plugins/*.tsx",
9+ "./pages/*": "./src/pages/*.tsx",
710 "./components/*": "./src/components/*.tsx",
811 "./hooks/*": "./src/hooks/*.ts",
912 "./lib/*": "./src/lib/*.ts",
@@ -11,6 +14,11 @@ · 1 unmodified line
1114 },
1215 "dependencies": {
1316 "@base-ui/react": "^1.3.0",
17+ "@effect/platform": "catalog:",
18+ "@effect-atom/atom": "^0.5.3",
19+ "@effect-atom/atom-react": "^0.5.0",
20+ "@executor/api": "workspace:*",
21+ "@executor/sdk": "workspace:*",
1422 "@hookform/resolvers": "^5.2.2",
1523 "@shikijs/langs": "^4.0.2",
1624 "@shikijs/themes": "^4.0.2",
@@ -18,6 +26,7 @@ · 1 unmodified line
1826 "clsx": "^2.1.1",
1927 "cmdk": "^1.1.1",
2028 "date-fns": "^4.1.0",
29+ "effect": "catalog:",
2130 "embla-carousel-react": "^8.6.0",
2231 "input-otp": "^1.4.2",
2332 "lucide-react": "^1.7.0",
@@ -37,6 +46,7 @@ · 13 unmodified lines
3746 },
3847 "devDependencies": {
3948 "@types/react": "^19.1.0",
49+ "@types/node": "catalog:",
4050 "hast-util-to-jsx-runtime": "^2.3.6",
4151 "tailwindcss": "^4.1.10",
4252 "typescript": "^5.9.3"
packages/react/src/api/atoms.tsxrenamed from packages/clients/react/src/atoms.ts+0−0

File renamed without changes.

packages/react/src/api/base-url.tsxrenamed from packages/clients/react/src/base-url.ts+0−0

File renamed without changes.

packages/react/src/api/client.tsxrenamed from packages/clients/react/src/client.ts+0−0

File renamed without changes.

packages/react/src/api/provider.tsxrenamed from packages/clients/react/src/provider.tsx+0−0

File renamed without changes.

packages/react/src/api/scope-context.tsxrenamed from packages/clients/react/src/scope-context.tsx+0−0

File renamed without changes.

packages/react/src/components/accordion.tsxrenamed from packages/ui/src/components/accordion.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import { ChevronDownIcon } from "lucide-react"
33 import { Accordion as AccordionPrimitive } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 function Accordion({
88 ...props
packages/react/src/components/alert-dialog.tsxrenamed from packages/ui/src/components/alert-dialog.tsx+2−2
@@ -3,8 +3,8 @@ · 2 unmodified lines
33 import * as React from "react"
44 import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
55
6−import { cn } from "@executor/ui/lib/utils"
7−import { Button } from "@executor/ui/components/button"
6+import { cn } from "../lib/utils"
7+import { Button } from "./button"
88
99 function AlertDialog({
1010 ...props
packages/react/src/components/alert.tsxrenamed from packages/ui/src/components/alert.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { cva, type VariantProps } from "class-variance-authority"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 const alertVariants = cva(
77 "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
packages/react/src/components/aspect-ratio.tsxrenamed from packages/ui/src/components/aspect-ratio.tsx+0−0

File renamed without changes.

packages/react/src/components/avatar.tsxrenamed from packages/ui/src/components/avatar.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Avatar as AvatarPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Avatar({
77 className,
packages/react/src/components/badge.tsxrenamed from packages/ui/src/components/badge.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import { cva, type VariantProps } from "class-variance-authority"
33 import { Slot } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 const badgeVariants = cva(
88 "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
packages/react/src/components/breadcrumb.tsxrenamed from packages/ui/src/components/breadcrumb.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import { ChevronRight, MoreHorizontal } from "lucide-react"
33 import { Slot } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
88 return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
packages/react/src/components/button-group.tsxrenamed from packages/ui/src/components/button-group.tsx+2−2
@@ -1,8 +1,8 @@
11 import { cva, type VariantProps } from "class-variance-authority"
22 import { Slot } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
5−import { Separator } from "@executor/ui/components/separator"
4+import { cn } from "../lib/utils"
5+import { Separator } from "./separator"
66
77 const buttonGroupVariants = cva(
88 "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
packages/react/src/components/button.tsxrenamed from packages/ui/src/components/button.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import { cva, type VariantProps } from "class-variance-authority"
33 import { Slot } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 const buttonVariants = cva(
88 "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
packages/react/src/components/calendar.tsxrenamed from packages/ui/src/components/calendar.tsx+2−2
@@ -10,8 +10,8 @@ import { · 9 unmodified lines
1010 type DayButton,
1111 } from "react-day-picker"
1212
13−import { cn } from "@executor/ui/lib/utils"
14−import { Button, buttonVariants } from "@executor/ui/components/button"
13+import { cn } from "../lib/utils"
14+import { Button, buttonVariants } from "./button"
1515
1616 function Calendar({
1717 className,
packages/react/src/components/card.tsxrenamed from packages/ui/src/components/card.tsx+1−1
@@ -1,6 +1,6 @@
11 import * as React from "react"
22
3−import { cn } from "@executor/ui/lib/utils"
3+import { cn } from "../lib/utils"
44
55 function Card({ className, ...props }: React.ComponentProps<"div">) {
66 return (
packages/react/src/components/carousel.tsxrenamed from packages/ui/src/components/carousel.tsx+2−2
@@ -6,8 +6,8 @@ import useEmblaCarousel, { · 5 unmodified lines
66 } from "embla-carousel-react"
77 import { ArrowLeft, ArrowRight } from "lucide-react"
88
9−import { cn } from "@executor/ui/lib/utils"
10−import { Button } from "@executor/ui/components/button"
9+import { cn } from "../lib/utils"
10+import { Button } from "./button"
1111
1212 type CarouselApi = UseEmblaCarouselType[1]
1313 type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
packages/react/src/components/chart.tsxrenamed from packages/ui/src/components/chart.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import * as RechartsPrimitive from "recharts"
33 import type { TooltipValueType } from "recharts"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 // Format: { THEME_NAME: CSS_SELECTOR }
88 const THEMES = { light: "", dark: ".dark" } as const
packages/react/src/components/checkbox.tsxrenamed from packages/ui/src/components/checkbox.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { CheckIcon } from "lucide-react"
55 import { Checkbox as CheckboxPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function Checkbox({
1010 className,
packages/react/src/components/code-block.tsxrenamed from packages/ui/src/components/code-block.tsx+0−0

File renamed without changes.

packages/react/src/components/collapsible.tsxrenamed from packages/ui/src/components/collapsible.tsx+0−0

File renamed without changes.

packages/react/src/components/combobox.tsxrenamed from packages/ui/src/components/combobox.tsx+3−3
@@ -4,14 +4,14 @@ import * as React from "react" · 3 unmodified lines
44 import { Combobox as ComboboxPrimitive } from "@base-ui/react"
55 import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"
66
7−import { cn } from "@executor/ui/lib/utils"
8−import { Button } from "@executor/ui/components/button"
7+import { cn } from "../lib/utils"
8+import { Button } from "./button"
99 import {
1010 InputGroup,
1111 InputGroupAddon,
1212 InputGroupButton,
1313 InputGroupInput,
14−} from "@executor/ui/components/input-group"
14+} from "./input-group"
1515
1616 const Combobox = ComboboxPrimitive.Root
1717
packages/react/src/components/command.tsxrenamed from packages/ui/src/components/command.tsx+2−2
@@ -4,14 +4,14 @@ import * as React from "react" · 3 unmodified lines
44 import { Command as CommandPrimitive } from "cmdk"
55 import { SearchIcon } from "lucide-react"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88 import {
99 Dialog,
1010 DialogContent,
1111 DialogDescription,
1212 DialogHeader,
1313 DialogTitle,
14−} from "@executor/ui/components/dialog"
14+} from "./dialog"
1515
1616 function Command({
1717 className,
packages/react/src/components/context-menu.tsxrenamed from packages/ui/src/components/context-menu.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
55 import { ContextMenu as ContextMenuPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function ContextMenu({
1010 ...props
packages/react/src/components/dialog.tsxrenamed from packages/ui/src/components/dialog.tsx+2−2
@@ -2,8 +2,8 @@ import * as React from "react" · 1 unmodified line
22 import { XIcon } from "lucide-react"
33 import { Dialog as DialogPrimitive } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
6−import { Button } from "@executor/ui/components/button"
5+import { cn } from "../lib/utils"
6+import { Button } from "./button"
77
88 function Dialog({
99 ...props
packages/react/src/components/direction.tsxrenamed from packages/ui/src/components/direction.tsx+0−0

File renamed without changes.

packages/react/src/components/drawer.tsxrenamed from packages/ui/src/components/drawer.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Drawer as DrawerPrimitive } from "vaul"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Drawer({
77 ...props
packages/react/src/components/dropdown-menu.tsxrenamed from packages/ui/src/components/dropdown-menu.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
55 import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function DropdownMenu({
1010 ...props
packages/react/src/components/empty.tsxrenamed from packages/ui/src/components/empty.tsx+1−1
@@ -1,6 +1,6 @@
11 import { cva, type VariantProps } from "class-variance-authority"
22
3−import { cn } from "@executor/ui/lib/utils"
3+import { cn } from "../lib/utils"
44
55 function Empty({ className, ...props }: React.ComponentProps<"div">) {
66 return (
packages/react/src/components/field.tsxrenamed from packages/ui/src/components/field.tsx+3−3
@@ -3,9 +3,9 @@ · 2 unmodified lines
33 import { useMemo } from "react"
44 import { cva, type VariantProps } from "class-variance-authority"
55
6−import { cn } from "@executor/ui/lib/utils"
7−import { Label } from "@executor/ui/components/label"
8−import { Separator } from "@executor/ui/components/separator"
6+import { cn } from "../lib/utils"
7+import { Label } from "./label"
8+import { Separator } from "./separator"
99
1010 function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
1111 return (
packages/react/src/components/form.tsxrenamed from packages/ui/src/components/form.tsx+2−2
@@ -11,8 +11,8 @@ import { · 10 unmodified lines
1111 type FieldValues,
1212 } from "react-hook-form"
1313
14−import { cn } from "@executor/ui/lib/utils"
15−import { Label } from "@executor/ui/components/label"
14+import { cn } from "../lib/utils"
15+import { Label } from "./label"
1616
1717 const Form = FormProvider
1818
packages/react/src/components/hover-card.tsxrenamed from packages/ui/src/components/hover-card.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { HoverCard as HoverCardPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function HoverCard({
77 ...props
packages/react/src/components/input-group.tsxrenamed from packages/ui/src/components/input-group.tsx+4−4
@@ -1,10 +1,10 @@
11 import * as React from "react"
22 import { cva, type VariantProps } from "class-variance-authority"
33
4−import { cn } from "@executor/ui/lib/utils"
5−import { Button } from "@executor/ui/components/button"
6−import { Input } from "@executor/ui/components/input"
7−import { Textarea } from "@executor/ui/components/textarea"
4+import { cn } from "../lib/utils"
5+import { Button } from "./button"
6+import { Input } from "./input"
7+import { Textarea } from "./textarea"
88
99 function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
1010 return (
packages/react/src/components/input-otp.tsxrenamed from packages/ui/src/components/input-otp.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { OTPInput, OTPInputContext } from "input-otp"
55 import { MinusIcon } from "lucide-react"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function InputOTP({
1010 className,
packages/react/src/components/input.tsxrenamed from packages/ui/src/components/input.tsx+1−1
@@ -1,6 +1,6 @@
11 import * as React from "react"
22
3−import { cn } from "@executor/ui/lib/utils"
3+import { cn } from "../lib/utils"
44
55 function Input({ className, type, ...props }: React.ComponentProps<"input">) {
66 return (
packages/react/src/components/item.tsxrenamed from packages/ui/src/components/item.tsx+2−2
@@ -2,8 +2,8 @@ import * as React from "react" · 1 unmodified line
22 import { cva, type VariantProps } from "class-variance-authority"
33 import { Slot } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
6−import { Separator } from "@executor/ui/components/separator"
5+import { cn } from "../lib/utils"
6+import { Separator } from "./separator"
77
88 function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
99 return (
packages/react/src/components/kbd.tsxrenamed from packages/ui/src/components/kbd.tsx+1−1
@@ -1,4 +1,4 @@
1−import { cn } from "@executor/ui/lib/utils"
1+import { cn } from "../lib/utils"
22
33 function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
44 return (
packages/react/src/components/label.tsxrenamed from packages/ui/src/components/label.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Label as LabelPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Label({
77 className,
packages/react/src/components/markdown.tsxrenamed from packages/ui/src/components/markdown.tsx+0−0

File renamed without changes.

packages/react/src/components/mcp-install-card.tsxrenamed from apps/local/src/web/components/mcp-install-card.tsx+0−0

File renamed without changes.

packages/react/src/components/menubar.tsxrenamed from packages/ui/src/components/menubar.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
55 import { Menubar as MenubarPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function Menubar({
1010 className,
packages/react/src/components/native-select.tsxrenamed from packages/ui/src/components/native-select.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { ChevronDownIcon } from "lucide-react"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function NativeSelect({
77 className,
packages/react/src/components/navigation-menu.tsxrenamed from packages/ui/src/components/navigation-menu.tsx+1−1
@@ -3,7 +3,7 @@ import { cva } from "class-variance-authority" · 2 unmodified lines
33 import { ChevronDownIcon } from "lucide-react"
44 import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui"
55
6−import { cn } from "@executor/ui/lib/utils"
6+import { cn } from "../lib/utils"
77
88 function NavigationMenu({
99 className,
packages/react/src/components/pagination.tsxrenamed from packages/ui/src/components/pagination.tsx+2−2
@@ -5,8 +5,8 @@ import { · 4 unmodified lines
55 MoreHorizontalIcon,
66 } from "lucide-react"
77
8−import { cn } from "@executor/ui/lib/utils"
9−import { buttonVariants, type Button } from "@executor/ui/components/button"
8+import { cn } from "../lib/utils"
9+import { buttonVariants, type Button } from "./button"
1010
1111 function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
1212 return (
packages/react/src/components/popover.tsxrenamed from packages/ui/src/components/popover.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Popover as PopoverPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Popover({
77 ...props
packages/react/src/components/progress.tsxrenamed from packages/ui/src/components/progress.tsx+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
33 import * as React from "react"
44 import { Progress as ProgressPrimitive } from "radix-ui"
55
6−import { cn } from "@executor/ui/lib/utils"
6+import { cn } from "../lib/utils"
77
88 function Progress({
99 className,
packages/react/src/components/radio-group.tsxrenamed from packages/ui/src/components/radio-group.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import { CircleIcon } from "lucide-react"
33 import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 function RadioGroup({
88 className,
packages/react/src/components/resizable.tsxrenamed from packages/ui/src/components/resizable.tsx+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
33 import { GripVerticalIcon } from "lucide-react"
44 import * as ResizablePrimitive from "react-resizable-panels"
55
6−import { cn } from "@executor/ui/lib/utils"
6+import { cn } from "../lib/utils"
77
88 function ResizablePanelGroup({
99 className,
packages/react/src/components/scroll-area.tsxrenamed from packages/ui/src/components/scroll-area.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function ScrollArea({
77 className,
packages/react/src/components/select.tsxrenamed from packages/ui/src/components/select.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
55 import { Select as SelectPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function Select({
1010 ...props
packages/react/src/components/separator.tsxrenamed from packages/ui/src/components/separator.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Separator as SeparatorPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Separator({
77 className,
packages/react/src/components/sheet.tsxrenamed from packages/ui/src/components/sheet.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { XIcon } from "lucide-react"
55 import { Dialog as SheetPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
1010 return <SheetPrimitive.Root data-slot="sheet" {...props} />
packages/react/src/components/sidebar.tsxrenamed from packages/ui/src/components/sidebar.tsx+8−8
@@ -3,25 +3,25 @@ import { cva, type VariantProps } from "class-variance-authority" · 2 unmodified lines
33 import { PanelLeftIcon } from "lucide-react"
44 import { Slot } from "radix-ui"
55
6−import { useIsMobile } from "@executor/ui/hooks/use-mobile"
7−import { cn } from "@executor/ui/lib/utils"
8−import { Button } from "@executor/ui/components/button"
9−import { Input } from "@executor/ui/components/input"
10−import { Separator } from "@executor/ui/components/separator"
6+import { useIsMobile } from "../hooks/use-mobile"
7+import { cn } from "../lib/utils"
8+import { Button } from "./button"
9+import { Input } from "./input"
10+import { Separator } from "./separator"
1111 import {
1212 Sheet,
1313 SheetContent,
1414 SheetDescription,
1515 SheetHeader,
1616 SheetTitle,
17−} from "@executor/ui/components/sheet"
18−import { Skeleton } from "@executor/ui/components/skeleton"
17+} from "./sheet"
18+import { Skeleton } from "./skeleton"
1919 import {
2020 Tooltip,
2121 TooltipContent,
2222 TooltipProvider,
2323 TooltipTrigger,
24−} from "@executor/ui/components/tooltip"
24+} from "./tooltip"
2525
2626 const SIDEBAR_COOKIE_NAME = "sidebar_state"
2727 const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
packages/react/src/components/skeleton.tsxrenamed from packages/ui/src/components/skeleton.tsx+1−1
@@ -1,4 +1,4 @@
1−import { cn } from "@executor/ui/lib/utils"
1+import { cn } from "../lib/utils"
22
33 function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
44 return (
packages/react/src/components/slider.tsxrenamed from packages/ui/src/components/slider.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Slider as SliderPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Slider({
77 className,
packages/react/src/components/sonner.tsxrenamed from packages/ui/src/components/sonner.tsx+0−0

File renamed without changes.

packages/react/src/components/spinner.tsxrenamed from packages/ui/src/components/spinner.tsx+1−1
@@ -1,6 +1,6 @@
11 import { Loader2Icon } from "lucide-react"
22
3−import { cn } from "@executor/ui/lib/utils"
3+import { cn } from "../lib/utils"
44
55 function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
66 return (
packages/react/src/components/switch.tsxrenamed from packages/ui/src/components/switch.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Switch as SwitchPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function Switch({
77 className,
packages/react/src/components/table.tsxrenamed from packages/ui/src/components/table.tsx+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
22
33 import * as React from "react"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 function Table({ className, ...props }: React.ComponentProps<"table">) {
88 return (
packages/react/src/components/tabs.tsxrenamed from packages/ui/src/components/tabs.tsx+1−1
@@ -2,7 +2,7 @@ import * as React from "react" · 1 unmodified line
22 import { cva, type VariantProps } from "class-variance-authority"
33 import { Tabs as TabsPrimitive } from "radix-ui"
44
5−import { cn } from "@executor/ui/lib/utils"
5+import { cn } from "../lib/utils"
66
77 function Tabs({
88 className,
packages/react/src/components/textarea.tsxrenamed from packages/ui/src/components/textarea.tsx+1−1
@@ -1,6 +1,6 @@
11 import * as React from "react"
22
3−import { cn } from "@executor/ui/lib/utils"
3+import { cn } from "../lib/utils"
44
55 function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
66 return (
packages/react/src/components/toggle-group.tsxrenamed from packages/ui/src/components/toggle-group.tsx+2−2
@@ -4,8 +4,8 @@ import * as React from "react" · 3 unmodified lines
44 import { type VariantProps } from "class-variance-authority"
55 import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
8−import { toggleVariants } from "@executor/ui/components/toggle"
7+import { cn } from "../lib/utils"
8+import { toggleVariants } from "./toggle"
99
1010 const ToggleGroupContext = React.createContext<
1111 VariantProps<typeof toggleVariants> & {
packages/react/src/components/toggle.tsxrenamed from packages/ui/src/components/toggle.tsx+1−1
@@ -4,7 +4,7 @@ import * as React from "react" · 3 unmodified lines
44 import { cva, type VariantProps } from "class-variance-authority"
55 import { Toggle as TogglePrimitive } from "radix-ui"
66
7−import { cn } from "@executor/ui/lib/utils"
7+import { cn } from "../lib/utils"
88
99 const toggleVariants = cva(
1010 "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
packages/react/src/components/tool-detail.tsxrenamed from apps/local/src/web/components/tool-detail.tsx+7−5
@@ -1,9 +1,11 @@
11 import { useMemo, useState } from "react";
2−import { useAtomValue, toolSchemaAtom, Result, ScopeId, ToolId } from "@executor/react";
3−import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@executor/ui/components/accordion";
4−import { Badge } from "@executor/ui/components/badge";
5−import { CodeBlock } from "@executor/ui/components/code-block";
6−import { Markdown } from "@executor/ui/components/markdown";
2+import { useAtomValue, Result } from "@effect-atom/atom-react";
3+import { toolSchemaAtom } from "../api/atoms";
4+import { ScopeId, ToolId } from "@executor/sdk";
5+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./accordion";
6+import { Badge } from "./badge";
7+import { CodeBlock } from "./code-block";
8+import { Markdown } from "./markdown";
79
810 // ---------------------------------------------------------------------------
911 // Copy button
packages/react/src/components/tool-tree.tsxrenamed from apps/local/src/web/components/tool-tree.tsx+0−0

File renamed without changes.

packages/react/src/components/tooltip.tsxrenamed from packages/ui/src/components/tooltip.tsx+1−1
@@ -1,7 +1,7 @@
11 import * as React from "react"
22 import { Tooltip as TooltipPrimitive } from "radix-ui"
33
4−import { cn } from "@executor/ui/lib/utils"
4+import { cn } from "../lib/utils"
55
66 function TooltipProvider({
77 delayDuration = 0,
packages/react/src/hooks/use-mobile.tsrenamed from packages/ui/src/hooks/use-mobile.ts+0−0

File renamed without changes.

packages/react/src/hooks/use-scope.ts+1−0
@@ -0,0 +1 @@
1+export { useScope } from "../api/scope-context";
packages/react/src/lib/shiki.tsrenamed from packages/ui/src/lib/shiki.ts+0−0

File renamed without changes.

packages/react/src/lib/utils.tsrenamed from packages/ui/src/lib/utils.ts+0−0

File renamed without changes.

packages/react/src/pages/secrets.tsxrenamed from apps/local/src/web/pages/secrets.tsx+11−18
@@ -1,16 +1,9 @@
11 import { useState, Suspense } from "react";
2−import {
3− useAtomValue,
4− useAtomSet,
5− useAtomRefresh,
6− Result,
7− secretsAtom,
8− setSecret,
9− removeSecret,
10−} from "@executor/react";
11−import type { SecretProviderPlugin } from "@executor/react";
2+import { useAtomValue, useAtomSet, useAtomRefresh, Result } from "@effect-atom/atom-react";
3+import { secretsAtom, setSecret, removeSecret } from "../api/atoms";
4+import type { SecretProviderPlugin } from "../plugins/secret-provider-plugin";
125 import { SecretId } from "@executor/sdk";
13−import { useScope } from "../lib/use-scope";
6+import { useScope } from "../hooks/use-scope";
147 import { onePasswordSecretProviderPlugin } from "@executor/plugin-onepassword/react";
158 import {
169 Dialog,
@@ -20,24 +13,24 @@ import { · 3 unmodified lines
2013 DialogDescription,
2114 DialogFooter,
2215 DialogClose,
23−} from "@executor/ui/components/dialog";
24−import { Button } from "@executor/ui/components/button";
25−import { Input } from "@executor/ui/components/input";
26−import { Label } from "@executor/ui/components/label";
27−import { Badge } from "@executor/ui/components/badge";
16+} from "../components/dialog";
17+import { Button } from "../components/button";
18+import { Input } from "../components/input";
19+import { Label } from "../components/label";
20+import { Badge } from "../components/badge";
2821 import {
2922 DropdownMenu,
3023 DropdownMenuContent,
3124 DropdownMenuItem,
3225 DropdownMenuTrigger,
33−} from "@executor/ui/components/dropdown-menu";
26+} from "../components/dropdown-menu";
3427 import {
3528 Select,
3629 SelectContent,
3730 SelectItem,
3831 SelectTrigger,
3932 SelectValue,
40−} from "@executor/ui/components/select";
33+} from "../components/select";
4134
4235 // ---------------------------------------------------------------------------
4336 // Add secret dialog
packages/react/src/pages/source-detail.tsxrenamed from apps/local/src/web/pages/source-detail.tsx+3−12
@@ -1,20 +1,11 @@
11 import { useEffect, useMemo, useState } from "react";
22 import { useNavigate } from "@tanstack/react-router";
3−import {
4− useAtomValue,
5− useAtomSet,
6− useAtomRefresh,
7− sourceToolsAtom,
8− sourcesAtom,
9− sourceAtom,
10− removeSource,
11− refreshSource,
12− Result,
13−} from "@executor/react";
3+import { useAtomValue, useAtomSet, useAtomRefresh, Result } from "@effect-atom/atom-react";
4+import { sourceToolsAtom, sourcesAtom, sourceAtom, removeSource, refreshSource } from "../api/atoms";
145 import { ToolTree } from "../components/tool-tree";
156 import { ToolDetail, ToolDetailEmpty } from "../components/tool-detail";
167 import type { ToolSummary } from "../components/tool-tree";
17−import { useScope } from "../lib/use-scope";
8+import { useScope } from "../hooks/use-scope";
189
1910 export function SourceDetailPage(props: { namespace: string }) {
2011 const { namespace } = props;
packages/react/src/pages/sources-add.tsxrenamed from apps/local/src/web/pages/sources-add.tsx+4−3
@@ -1,8 +1,9 @@
11 import { Suspense } from "react";
22 import { Link, useNavigate } from "@tanstack/react-router";
3−import type { SourcePlugin } from "@executor/react";
4−import { useAtomRefresh, sourcesAtom } from "@executor/react";
5−import { useScope } from "../lib/use-scope";
3+import type { SourcePlugin } from "../plugins/source-plugin";
4+import { useAtomRefresh } from "@effect-atom/atom-react";
5+import { sourcesAtom } from "../api/atoms";
6+import { useScope } from "../hooks/use-scope";
67 import { openApiSourcePlugin } from "@executor/plugin-openapi/react";
78 import { mcpSourcePlugin } from "@executor/plugin-mcp/react";
89 import { googleDiscoverySourcePlugin } from "@executor/plugin-google-discovery/react";
packages/react/src/pages/sources.tsxrenamed from apps/local/src/web/pages/sources.tsx+4−3
@@ -1,8 +1,9 @@
11 import { useState, useCallback, useMemo } from "react";
22 import { Link, useNavigate } from "@tanstack/react-router";
3−import { Result, useAtomValue, useAtomSet, sourcesAtom, detectSource } from "@executor/react";
4−import { useScope } from "../lib/use-scope";
5−import type { SourcePlugin, SourcePreset } from "@executor/react";
3+import { Result, useAtomValue, useAtomSet } from "@effect-atom/atom-react";
4+import { sourcesAtom, detectSource } from "../api/atoms";
5+import { useScope } from "../hooks/use-scope";
6+import type { SourcePlugin, SourcePreset } from "../plugins/source-plugin";
67 import { openApiSourcePlugin } from "@executor/plugin-openapi/react";
78 import { mcpSourcePlugin } from "@executor/plugin-mcp/react";
89 import { googleDiscoverySourcePlugin } from "@executor/plugin-google-discovery/react";
packages/react/src/pages/tools.tsxrenamed from apps/local/src/web/pages/tools.tsx+3−2
@@ -1,5 +1,6 @@
1−import { useAtomValue, Result, toolsAtom } from "@executor/react";
2−import { useScope } from "../lib/use-scope";
1+import { useAtomValue, Result } from "@effect-atom/atom-react";
2+import { toolsAtom } from "../api/atoms";
3+import { useScope } from "../hooks/use-scope";
34
45 export function ToolsPage() {
56 const scopeId = useScope();
packages/react/src/plugins/secret-picker.tsxrenamed from packages/clients/react/src/secret-picker.tsx+3−3
@@ -1,18 +1,18 @@
11 import { useState, type ChangeEvent, type FocusEvent } from "react";
22
3−import { Input } from "@executor/ui/components/input";
3+import { Input } from "../components/input";
44 import {
55 Command,
66 CommandEmpty,
77 CommandGroup,
88 CommandItem,
99 CommandList,
10−} from "@executor/ui/components/command";
10+} from "../components/command";
1111 import {
1212 Popover,
1313 PopoverAnchor,
1414 PopoverContent,
15−} from "@executor/ui/components/popover";
15+} from "../components/popover";
1616
1717 export interface SecretPickerSecret {
1818 readonly id: string;
packages/react/src/plugins/secret-provider-plugin.tsxrenamed from packages/clients/react/src/secret-provider-plugin.ts+0−0

File renamed without changes.

packages/react/src/plugins/source-plugin.tsxrenamed from packages/clients/react/src/source-plugin.ts+0−0

File renamed without changes.

packages/react/src/styles/globals.cssrenamed from packages/ui/src/styles/globals.css+0−0

File renamed without changes.

packages/react/tsconfig.jsonrenamed from packages/clients/react/tsconfig.json+0−0

File renamed without changes.

packages/ui/CHANGELOG.md+0−4
@@ -1,4 +0,0 @@
1−# @executor/ui changelog
2−
3−This file exists for Changesets release workflow compatibility.
4−Canonical user-facing release notes are published on GitHub Releases.
packages/ui/components.json+0−20
@@ -1,20 +0,0 @@
1−{
2− "$schema": "https://ui.shadcn.com/schema.json",
3− "style": "new-york",
4− "rsc": false,
5− "tsx": true,
6− "tailwind": {
7− "config": "",
8− "css": "src/styles/globals.css",
9− "baseColor": "neutral",
10− "cssVariables": true
11− },
12− "iconLibrary": "lucide",
13− "aliases": {
14− "components": "@executor/ui/components",
15− "utils": "@executor/ui/lib/utils",
16− "hooks": "@executor/ui/hooks",
17− "lib": "@executor/ui/lib",
18− "ui": "@executor/ui/components"
19− }
20−}
packages/ui/tsconfig.json+0−25
@@ -1,25 +0,0 @@
1−{
2− "compilerOptions": {
3− "target": "ESNext",
4− "module": "ESNext",
5− "moduleResolution": "bundler",
6− "strict": true,
7− "esModuleInterop": true,
8− "skipLibCheck": true,
9− "outDir": "dist",
10− "rootDir": "src",
11− "declaration": true,
12− "declarationMap": true,
13− "sourceMap": true,
14− "jsx": "react-jsx",
15− "paths": {
16− "@executor/ui/*": ["./src/*"]
17− },
18− "plugins": [
19− {
20− "name": "@effect/language-service"
21− }
22− ]
23− },
24− "include": ["src"]
25−}