History

pretty ui

Rhys Sullivan committed 7 months ago

Commit cb0197f1 parent 2d91da111 files · +655 −160Browse files

Unified · Split

11 files
  1. apps/web/index.html +3 −0
  2. apps/web/package.json +2 −0
  3. apps/web/src/main.tsx +1 −0
  4. apps/web/src/pages/secrets.tsx +41 −21
  5. apps/web/src/pages/sources.tsx +51 −23
  6. apps/web/src/pages/tools.tsx +59 −21
  7. apps/web/src/shell.tsx +134 −24
  8. apps/web/src/styles.css +128 −0
  9. apps/web/vite.config.ts +2 −0
  10. bun.lock +78 −0
  11. packages/plugins/openapi/src/react/AddOpenApiSource.tsx +156 −71
apps/web/index.html+3−0
@@ -4,6 +4,9 @@ · 3 unmodified lines
44 <meta charset="UTF-8" />
55 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
66 <title>Executor</title>
7+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300..700;1,9..40,300..700&family=Instrument+Serif&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
710 </head>
811 <body>
912 <div id="root"></div>
apps/web/package.json+2−0
@@ -5,6 +5,7 @@ · 4 unmodified lines
55 "type": "module",
66 "dependencies": {
77 "@executor/plugin-openapi": "workspace:*",
8+ "tailwindcss": "^4.1.10",
89 "@executor/react": "workspace:*",
910 "@executor/server": "workspace:*",
1011 "@tanstack/react-router": "^1.168.10",
@@ -15,6 +16,7 @@ · 4 unmodified lines
1516 "@types/react": "^19.1.0",
1617 "@types/react-dom": "^19.1.0",
1718 "@types/node": "catalog:",
19+ "@tailwindcss/vite": "^4.1.10",
1820 "@vitejs/plugin-react": "^4.5.2",
1921 "typescript": "^5.9.3",
2022 "vite": "^6.3.5"
apps/web/src/main.tsx+1−0
@@ -1,5 +1,6 @@
11 import React from "react";
22 import { createRoot } from "react-dom/client";
3+import "./styles.css";
34 import { App } from "./App";
45
56 const root = createRoot(document.getElementById("root")!);
apps/web/src/pages/secrets.tsx+41−21
@@ -3,25 +3,45 @@ import { useAtomValue, Result, secretsAtom } from "@executor/react"; · 2 unmodified lines
33 export function SecretsPage() {
44 const secrets = useAtomValue(secretsAtom());
55
6− return Result.match(secrets, {
7− onInitial: () => <p>Loading secrets…</p>,
8− onFailure: () => <p style={{ color: "red" }}>Failed to load secrets</p>,
9− onSuccess: ({ value }) => (
10− <div>
11− <h2>Secrets ({value.length})</h2>
12− {value.length === 0 ? (
13− <p style={{ color: "#888" }}>No secrets stored yet.</p>
14− ) : (
15− <ul>
16− {value.map((s) => (
17− <li key={s.id}>
18− <strong>{s.name}</strong>
19− {s.purpose && <span> — {s.purpose}</span>}
20− </li>
21− ))}
22− </ul>
23− )}
24− </div>
25− ),
26− });
6+ return (
7+ <div>
8+ <h1 className="text-xl font-semibold text-foreground">Secrets</h1>
9+
10+ {Result.match(secrets, {
11+ onInitial: () => (
12+ <p className="mt-4 text-sm text-muted-foreground">Loading secrets…</p>
13+ ),
14+ onFailure: () => (
15+ <p className="mt-4 text-sm text-destructive">Failed to load secrets</p>
16+ ),
17+ onSuccess: ({ value }) =>
18+ value.length === 0 ? (
19+ <p className="mt-4 text-sm text-muted-foreground">
20+ No secrets stored yet. Secrets are created when you configure authentication for a source.
21+ </p>
22+ ) : (
23+ <div className="mt-4 grid gap-2">
24+ {value.map((s) => (
25+ <div
26+ key={s.id}
27+ className="flex items-center justify-between rounded-lg border border-border bg-card p-3"
28+ >
29+ <div className="min-w-0">
30+ <p className="text-sm font-medium text-card-foreground">{s.name}</p>
31+ {s.purpose && (
32+ <p className="mt-0.5 text-xs text-muted-foreground">{s.purpose}</p>
33+ )}
34+ </div>
35+ {s.provider && (
36+ <span className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground">
37+ {s.provider}
38+ </span>
39+ )}
40+ </div>
41+ ))}
42+ </div>
43+ ),
44+ })}
45+ </div>
46+ );
2747 }
apps/web/src/pages/sources.tsx+51−23
@@ -18,12 +18,10 @@ export function SourcesPage() { · 17 unmodified lines
1818 const tools = useAtomValue(toolsAtom());
1919 const refreshTools = useAtomRefresh(toolsAtom());
2020
21− // Group tools by namespace (second tag after "openapi")
2221 const sources = useMemo(() => {
2322 if (tools._tag !== "Success") return [];
2423 const namespaces = new Map<string, number>();
2524 for (const tool of tools.value) {
26− // Tools are tagged ["openapi", namespace, ...]
2725 const ns = tool.tags.find(
2826 (t) => t !== "openapi" && !sourcePlugins.some((p) => p.key === t),
2927 );
@@ -44,7 +42,7 @@ export function SourcesPage() { · 14 unmodified lines
4442 if (plugin) {
4543 const AddComponent = plugin.add;
4644 return (
47− <Suspense fallback={<p>Loading…</p>}>
45+ <Suspense fallback={<p className="text-sm text-muted-foreground">Loading…</p>}>
4846 <AddComponent
4947 onComplete={() => {
5048 setAdding(null);
@@ -58,35 +56,65 @@ export function SourcesPage() { · 7 unmodified lines
5856
5957 return (
6058 <div>
61− <h2>Sources</h2>
59+ <div className="flex items-center justify-between">
60+ <h1 className="text-xl font-semibold text-foreground">Sources</h1>
61+ <div className="flex gap-2">
62+ {sourcePlugins.map((p) => (
63+ <button
64+ key={p.key}
65+ onClick={() => setAdding(p.key)}
66+ className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
67+ >
68+ <svg viewBox="0 0 16 16" fill="none" className="size-3.5">
69+ <path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
70+ </svg>
71+ Add {p.label}
72+ </button>
73+ ))}
74+ </div>
75+ </div>
6276
6377 {Result.match(tools, {
64− onInitial: () => <p>Loading…</p>,
65− onFailure: () => <p style={{ color: "red" }}>Failed to load sources</p>,
78+ onInitial: () => (
79+ <p className="mt-4 text-sm text-muted-foreground">Loading…</p>
80+ ),
81+ onFailure: () => (
82+ <p className="mt-4 text-sm text-destructive">Failed to load sources</p>
83+ ),
6684 onSuccess: () =>
6785 sources.length === 0 ? (
68− <p style={{ color: "#888" }}>No sources configured yet.</p>
86+ <div className="mt-8 flex flex-col items-center justify-center text-center">
87+ <div className="rounded-full bg-muted p-4">
88+ <svg viewBox="0 0 24 24" fill="none" className="size-8 text-muted-foreground/50">
89+ <path d="M12 6v12M6 12h12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
90+ </svg>
91+ </div>
92+ <p className="mt-3 text-sm font-medium text-foreground">No sources configured</p>
93+ <p className="mt-1 text-xs text-muted-foreground">
94+ Add an API source to start discovering tools.
95+ </p>
96+ </div>
6997 ) : (
70− <ul>
98+ <div className="mt-4 grid gap-2">
7199 {sources.map((s) => (
72− <li key={s.namespace}>
73− <strong>{s.namespace}</strong> — {s.toolCount} tools
74− </li>
100+ <div
101+ key={s.namespace}
102+ className="flex items-center justify-between rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent/50"
103+ >
104+ <div>
105+ <p className="text-sm font-medium text-card-foreground">{s.namespace}</p>
106+ <p className="mt-0.5 text-xs text-muted-foreground">
107+ {s.toolCount} tool{s.toolCount !== 1 ? "s" : ""}
108+ </p>
109+ </div>
110+ <span className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground">
111+ openapi
112+ </span>
113+ </div>
75114 ))}
76− </ul>
115+ </div>
77116 ),
78117 })}
79−
80− <div style={{ marginTop: "1rem" }}>
81− <h3>Add a source</h3>
82− <div style={{ display: "flex", gap: "0.5rem" }}>
83− {sourcePlugins.map((p) => (
84− <button key={p.key} onClick={() => setAdding(p.key)}>
85− {p.label}
86− </button>
87− ))}
88− </div>
89− </div>
90118 </div>
91119 );
92120 }
apps/web/src/pages/tools.tsx+59−21
@@ -3,25 +3,63 @@ import { useAtomValue, Result, toolsAtom } from "@executor/react"; · 2 unmodified lines
33 export function ToolsPage() {
44 const tools = useAtomValue(toolsAtom());
55
6− return Result.match(tools, {
7− onInitial: () => <p>Loading tools…</p>,
8− onFailure: () => <p style={{ color: "red" }}>Failed to load tools</p>,
9− onSuccess: ({ value }) => (
10− <div>
11− <h2>Tools ({value.length})</h2>
12− {value.length === 0 ? (
13− <p style={{ color: "#888" }}>No tools registered yet.</p>
14− ) : (
15− <ul>
16− {value.map((t) => (
17− <li key={t.id}>
18− <strong>{t.name}</strong>
19− {t.description && <span> — {t.description}</span>}
20− </li>
21− ))}
22− </ul>
23− )}
24− </div>
25− ),
26− });
6+ return (
7+ <div>
8+ <h1 className="text-xl font-semibold text-foreground">Tools</h1>
9+
10+ {Result.match(tools, {
11+ onInitial: () => (
12+ <p className="mt-4 text-sm text-muted-foreground">Loading tools…</p>
13+ ),
14+ onFailure: () => (
15+ <p className="mt-4 text-sm text-destructive">Failed to load tools</p>
16+ ),
17+ onSuccess: ({ value }) =>
18+ value.length === 0 ? (
19+ <p className="mt-4 text-sm text-muted-foreground">
20+ No tools registered yet. Add a source to get started.
21+ </p>
22+ ) : (
23+ <div className="mt-4">
24+ <p className="text-sm text-muted-foreground mb-3">
25+ {value.length} tool{value.length !== 1 ? "s" : ""} available
26+ </p>
27+ <div className="grid gap-2">
28+ {value.map((t) => (
29+ <div
30+ key={t.id}
31+ className="rounded-lg border border-border bg-card p-3 transition-colors hover:bg-accent/50"
32+ >
33+ <div className="flex items-start justify-between gap-2">
34+ <div className="min-w-0">
35+ <p className="text-sm font-medium text-card-foreground truncate">
36+ {t.name}
37+ </p>
38+ {t.description && (
39+ <p className="mt-0.5 text-xs text-muted-foreground line-clamp-2">
40+ {t.description}
41+ </p>
42+ )}
43+ </div>
44+ {t.tags.length > 0 && (
45+ <div className="flex shrink-0 gap-1">
46+ {t.tags.slice(0, 2).map((tag) => (
47+ <span
48+ key={tag}
49+ className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground"
50+ >
51+ {tag}
52+ </span>
53+ ))}
54+ </div>
55+ )}
56+ </div>
57+ </div>
58+ ))}
59+ </div>
60+ </div>
61+ ),
62+ })}
63+ </div>
64+ );
2765 }
apps/web/src/shell.tsx+134−24
@@ -1,31 +1,141 @@
1−import { Link } from "@tanstack/react-router";
1+import { Link, useLocation } from "@tanstack/react-router";
2+import { useMemo } from "react";
3+import { useAtomValue, toolsAtom, Result } from "@executor/react";
24 import type { ReactNode } from "react";
35
4−export function Shell({ children }: { children: ReactNode }) {
6+// ---------------------------------------------------------------------------
7+// Nav item
8+// ---------------------------------------------------------------------------
9+
10+function NavItem(props: { to: string; label: string; active: boolean }) {
511 return (
6− <div style={{ display: "flex", minHeight: "100vh", fontFamily: "system-ui" }}>
7− <nav
8− style={{
9− width: 200,
10− padding: "1rem",
11− borderRight: "1px solid #eee",
12− display: "flex",
13− flexDirection: "column",
14− gap: "0.5rem",
15− }}
16− >
17− <h2 style={{ margin: 0, fontSize: "1.2rem" }}>Executor</h2>
18− <Link to="/tools" style={{ textDecoration: "none", color: "#333" }}>
19− Tools
20− </Link>
21− <Link to="/sources" style={{ textDecoration: "none", color: "#333" }}>
22− Sources
23− </Link>
24− <Link to="/secrets" style={{ textDecoration: "none", color: "#333" }}>
25− Secrets
12+ <Link
13+ to={props.to}
14+ className={[
15+ "flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors",
16+ props.active
17+ ? "bg-sidebar-active text-foreground font-medium"
18+ : "text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground",
19+ ].join(" ")}
20+ >
21+ {props.label}
22+ </Link>
23+ );
24+}
25+
26+// ---------------------------------------------------------------------------
27+// Source list in sidebar
28+// ---------------------------------------------------------------------------
29+
30+function SourceList() {
31+ const tools = useAtomValue(toolsAtom());
32+
33+ const sources = useMemo(() => {
34+ if (tools._tag !== "Success") return [];
35+ const namespaces = new Map<string, number>();
36+ for (const tool of tools.value) {
37+ const ns = tool.tags.find((t) => t !== "openapi");
38+ if (ns && ns !== tool.tags[0]) {
39+ namespaces.set(ns, (namespaces.get(ns) ?? 0) + 1);
40+ }
41+ }
42+ return [...namespaces.entries()].map(([ns, count]) => ({ namespace: ns, toolCount: count }));
43+ }, [tools]);
44+
45+ if (tools._tag === "Initial" || tools.waiting) {
46+ return <div className="px-2.5 py-2 text-[11px] text-muted-foreground/40">Loading…</div>;
47+ }
48+
49+ if (sources.length === 0) {
50+ return <div className="px-2.5 py-2 text-[11px] text-muted-foreground/40">No sources yet</div>;
51+ }
52+
53+ return (
54+ <div className="flex flex-col gap-px">
55+ {sources.map((s) => (
56+ <Link
57+ key={s.namespace}
58+ to="/sources"
59+ className="group flex items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground transition-colors"
60+ >
61+ <span className="flex-1 truncate">{s.namespace}</span>
62+ <span className="text-[10px] tabular-nums text-muted-foreground/50">{s.toolCount}</span>
2663 </Link>
27− </nav>
28− <main style={{ flex: 1, padding: "1.5rem" }}>{children}</main>
64+ ))}
65+ </div>
66+ );
67+}
68+
69+// ---------------------------------------------------------------------------
70+// Shell
71+// ---------------------------------------------------------------------------
72+
73+export function Shell({ children }: { children: ReactNode }) {
74+ const location = useLocation();
75+ const pathname = location.pathname;
76+
77+ return (
78+ <div className="flex h-screen overflow-hidden">
79+ {/* Sidebar */}
80+ <aside className="hidden w-52 shrink-0 border-r border-sidebar-border bg-sidebar md:flex md:flex-col lg:w-56">
81+ {/* Brand */}
82+ <div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4">
83+ <Link to="/" className="flex items-center gap-1.5">
84+ <span className="font-display text-base tracking-tight text-foreground">
85+ executor
86+ </span>
87+ <span className="rounded bg-primary/15 px-1.5 py-0.5 text-[10px] font-medium text-primary">
88+ v4
89+ </span>
90+ </Link>
91+ </div>
92+
93+ {/* Nav */}
94+ <nav className="flex flex-1 flex-col overflow-y-auto p-2">
95+ <NavItem to="/" label="Tools" active={pathname === "/" || pathname === "/tools"} />
96+ <NavItem to="/secrets" label="Secrets" active={pathname === "/secrets"} />
97+
98+ {/* Sources section */}
99+ <div className="mt-5 mb-1 px-2.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground/50">
100+ <div className="flex items-center justify-between gap-2">
101+ <span>Sources</span>
102+ <Link
103+ to="/sources"
104+ className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-[10px] font-medium normal-case tracking-normal text-primary transition-colors hover:bg-sidebar-active hover:text-foreground"
105+ >
106+ <svg viewBox="0 0 16 16" fill="none" className="size-3">
107+ <path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
108+ </svg>
109+ Add
110+ </Link>
111+ </div>
112+ </div>
113+
114+ <SourceList />
115+ </nav>
116+
117+ {/* Footer */}
118+ <div className="shrink-0 border-t border-sidebar-border px-4 py-2.5">
119+ <div className="flex items-center justify-between text-[10px] leading-none">
120+ <span className="text-muted-foreground/70 tabular-nums">v4.0.0-dev</span>
121+ <a
122+ href="https://github.com"
123+ target="_blank"
124+ rel="noreferrer"
125+ className="text-muted-foreground/70 transition-colors hover:text-foreground"
126+ >
127+ GitHub
128+ </a>
129+ </div>
130+ </div>
131+ </aside>
132+
133+ {/* Main content */}
134+ <main className="flex min-h-0 flex-1 flex-col min-w-0 overflow-auto">
135+ <div className="flex-1 p-6 max-w-5xl">
136+ {children}
137+ </div>
138+ </main>
29139 </div>
30140 );
31141 }
apps/web/src/styles.css+128−0
@@ -0,0 +1,128 @@
1+@import "tailwindcss";
2+
3+@source "../../../packages/clients/react/src/**/*.{ts,tsx}";
4+@source "../../../packages/plugins/openapi/src/react/**/*.{ts,tsx}";
5+
6+@theme inline {
7+ --font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;
8+ --font-mono: "JetBrains Mono", ui-monospace, monospace;
9+ --font-display: "Instrument Serif", Georgia, serif;
10+
11+ --color-background: var(--background);
12+ --color-foreground: var(--foreground);
13+ --color-card: var(--card);
14+ --color-card-foreground: var(--card-foreground);
15+ --color-primary: var(--primary);
16+ --color-primary-foreground: var(--primary-foreground);
17+ --color-secondary: var(--secondary);
18+ --color-secondary-foreground: var(--secondary-foreground);
19+ --color-muted: var(--muted);
20+ --color-muted-foreground: var(--muted-foreground);
21+ --color-accent: var(--accent);
22+ --color-accent-foreground: var(--accent-foreground);
23+ --color-destructive: var(--destructive);
24+ --color-border: var(--border);
25+ --color-input: var(--input);
26+ --color-ring: var(--ring);
27+
28+ --color-sidebar: var(--sidebar);
29+ --color-sidebar-foreground: var(--sidebar-foreground);
30+ --color-sidebar-border: var(--sidebar-border);
31+ --color-sidebar-active: var(--sidebar-active);
32+
33+ --radius-sm: calc(var(--radius) * 0.6);
34+ --radius-md: calc(var(--radius) * 0.8);
35+ --radius-lg: var(--radius);
36+ --radius-xl: calc(var(--radius) * 1.4);
37+}
38+
39+/* ——— Light theme ——— */
40+:root {
41+ color-scheme: light dark;
42+ --radius: 0.625rem;
43+
44+ --background: oklch(0.985 0.002 260);
45+ --foreground: oklch(0.145 0.005 260);
46+ --card: oklch(0.99 0.001 260);
47+ --card-foreground: oklch(0.145 0.005 260);
48+ --primary: oklch(0.55 0.17 175);
49+ --primary-foreground: oklch(0.98 0.005 175);
50+ --secondary: oklch(0.94 0.005 260);
51+ --secondary-foreground: oklch(0.32 0.008 260);
52+ --muted: oklch(0.955 0.004 260);
53+ --muted-foreground: oklch(0.50 0.01 250);
54+ --accent: oklch(0.94 0.006 260);
55+ --accent-foreground: oklch(0.145 0.005 260);
56+ --destructive: oklch(0.55 0.22 25);
57+ --border: oklch(0.88 0.006 260);
58+ --input: oklch(0.88 0.006 260);
59+ --ring: oklch(0.55 0.17 175);
60+
61+ --sidebar: oklch(0.97 0.003 260);
62+ --sidebar-foreground: oklch(0.32 0.008 260);
63+ --sidebar-border: oklch(0.90 0.005 260);
64+ --sidebar-active: oklch(0.935 0.006 260);
65+
66+ --scrollbar-thumb: oklch(0.78 0.005 260);
67+ --scrollbar-thumb-hover: oklch(0.68 0.005 260);
68+}
69+
70+/* ——— Dark theme ——— */
71+@media (prefers-color-scheme: dark) {
72+ :root {
73+ --background: oklch(0.115 0.005 260);
74+ --foreground: oklch(0.90 0.008 250);
75+ --card: oklch(0.145 0.005 260);
76+ --card-foreground: oklch(0.90 0.008 250);
77+ --primary: oklch(0.72 0.15 175);
78+ --primary-foreground: oklch(0.12 0.01 175);
79+ --secondary: oklch(0.195 0.007 260);
80+ --secondary-foreground: oklch(0.78 0.008 250);
81+ --muted: oklch(0.175 0.005 260);
82+ --muted-foreground: oklch(0.56 0.01 250);
83+ --accent: oklch(0.195 0.008 260);
84+ --accent-foreground: oklch(0.90 0.008 250);
85+ --destructive: oklch(0.62 0.22 25);
86+ --border: oklch(0.235 0.007 260);
87+ --input: oklch(0.195 0.007 260);
88+ --ring: oklch(0.72 0.15 175);
89+
90+ --sidebar: oklch(0.105 0.005 260);
91+ --sidebar-foreground: oklch(0.72 0.008 250);
92+ --sidebar-border: oklch(0.20 0.006 260);
93+ --sidebar-active: oklch(0.18 0.008 260);
94+
95+ --scrollbar-thumb: oklch(0.30 0.005 260);
96+ --scrollbar-thumb-hover: oklch(0.38 0.005 260);
97+ }
98+}
99+
100+@layer base {
101+ * {
102+ @apply border-border;
103+ }
104+
105+ html, body, #root {
106+ min-height: 100%;
107+ height: 100%;
108+ }
109+
110+ body {
111+ @apply bg-background font-sans text-foreground antialiased;
112+ }
113+
114+ ::-webkit-scrollbar {
115+ width: 6px;
116+ height: 6px;
117+ }
118+ ::-webkit-scrollbar-track {
119+ background: transparent;
120+ }
121+ ::-webkit-scrollbar-thumb {
122+ background: var(--scrollbar-thumb);
123+ border-radius: 3px;
124+ }
125+ ::-webkit-scrollbar-thumb:hover {
126+ background: var(--scrollbar-thumb-hover);
127+ }
128+}
apps/web/vite.config.ts+2−0
@@ -1,5 +1,6 @@
11 import { defineConfig } from "vite";
22 import react from "@vitejs/plugin-react";
3+import tailwindcss from "@tailwindcss/vite";
34 import { createApiHandler } from "@executor/server";
45
56 const api = createApiHandler();
@@ -7,6 +8,7 @@ const api = createApiHandler(); · 1 unmodified line
78 export default defineConfig({
89 plugins: [
910 react(),
11+ tailwindcss(),
1012 {
1113 name: "executor-api",
1214 configureServer(server) {
bun.lock+78−0
@@ -37,8 +37,10 @@ · 36 unmodified lines
3737 "@tanstack/react-router": "^1.168.10",
3838 "react": "^19.1.0",
3939 "react-dom": "^19.1.0",
40+ "tailwindcss": "^4.1.10",
4041 },
4142 "devDependencies": {
43+ "@tailwindcss/vite": "^4.1.10",
4244 "@types/node": "catalog:",
4345 "@types/react": "^19.1.0",
4446 "@types/react-dom": "^19.1.0",
@@ -521,6 +523,36 @@ · 476 unmodified lines
521523
522524 "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
523525
526+ "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
527+
528+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
529+
530+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
531+
532+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
533+
534+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
535+
536+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
537+
538+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
539+
540+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
541+
542+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
543+
544+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
545+
546+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
547+
548+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
549+
550+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
551+
552+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
553+
554+ "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
555+
524556 "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="],
525557
526558 "@tanstack/react-router": ["@tanstack/react-router@1.168.10", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.9", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-/RmDlOwDkCug609KdPB3U+U1zmrtadJpvsmRg2zEn8TRCKRNri7dYZIjQZbNg8PgUiRL4T6njrZBV1ChzblNaA=="],
@@ -623,6 +655,8 @@ · 96 unmodified lines
623655
624656 "electron-to-chromium": ["electron-to-chromium@1.5.330", "", {}, "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA=="],
625657
658+ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
659+
626660 "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
627661
628662 "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
@@ -647,12 +681,16 @@ · 18 unmodified lines
647681
648682 "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
649683
684+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
685+
650686 "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
651687
652688 "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
653689
654690 "isbot": ["isbot@5.1.37", "", {}, "sha512-5bcicX81xf6NlTEV8rWdg7Pk01LFizDetuYGHx6d/f6y3lR2/oo8IfxjzJqn1UdDEyCcwT9e7NRloj8DwCYujQ=="],
655691
692+ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
693+
656694 "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
657695
658696 "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -665,6 +703,30 @@ · 6 unmodified lines
665703
666704 "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
667705
706+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
707+
708+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
709+
710+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
711+
712+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
713+
714+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
715+
716+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
717+
718+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
719+
720+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
721+
722+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
723+
724+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
725+
726+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
727+
728+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
729+
668730 "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
669731
670732 "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
@@ -737,6 +799,10 @@ · 66 unmodified lines
737799
738800 "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
739801
802+ "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
803+
804+ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
805+
740806 "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
741807
742808 "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
@@ -781,6 +847,18 @@ · 38 unmodified lines
781847
782848 "@executor/sdk/typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
783849
850+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
851+
852+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
853+
854+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
855+
856+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
857+
858+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
859+
860+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
861+
784862 "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
785863
786864 "vite-node/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
packages/plugins/openapi/src/react/AddOpenApiSource.tsx+156−71
@@ -7,14 +7,14 @@ import { · 6 unmodified lines
77 Outlet,
88 RouterProvider,
99 } from "@tanstack/react-router";
10−import { useAtomSet, Result } from "@effect-atom/atom-react";
10+import { useAtomSet } from "@effect-atom/atom-react";
1111 import { Option } from "effect";
1212
1313 import { previewOpenApiSpec, addOpenApiSpec } from "./atoms";
1414 import type { SpecPreview } from "../sdk/preview";
1515
1616 // ---------------------------------------------------------------------------
17−// Shared state via context
17+// Context
1818 // ---------------------------------------------------------------------------
1919
2020 type Navigate = (to: string) => void;
@@ -24,9 +24,12 @@ const useNav = () => useContext(NavContext); · 3 unmodified lines
2424 interface AddState {
2525 specUrl: string;
2626 setSpecUrl: (v: string) => void;
27− preview: Result.Result<SpecPreview, unknown> | null;
28− doPreview: (spec: string) => void;
29− doAdd: (spec: string, baseUrl?: string, headers?: Record<string, unknown>) => void;
27+ preview: SpecPreview | null;
28+ setPreview: (p: SpecPreview | null) => void;
29+ loading: boolean;
30+ setLoading: (v: boolean) => void;
31+ error: string | null;
32+ setError: (v: string | null) => void;
3033 onComplete: () => void;
3134 onCancel: () => void;
3235 }
@@ -39,30 +42,57 @@ const useAddState = () => useContext(AddStateContext); · 6 unmodified lines
3942 // ---------------------------------------------------------------------------
4043
4144 function UrlStep() {
42− const { specUrl, setSpecUrl, onCancel, doPreview } = useAddState();
45+ const { specUrl, setSpecUrl, onCancel, setPreview, setLoading, setError, loading } = useAddState();
4346 const nav = useNav();
47+ const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promise" });
4448
45− const handleNext = () => {
46− doPreview(specUrl);
47− nav("/auth");
49+ const handleNext = async () => {
50+ setLoading(true);
51+ setError(null);
52+ try {
53+ const result = await doPreview({
54+ path: { scopeId: "default" as never },
55+ payload: { spec: specUrl },
56+ });
57+ setPreview(result);
58+ nav("/auth");
59+ } catch (e) {
60+ setError(e instanceof Error ? e.message : "Failed to parse spec");
61+ } finally {
62+ setLoading(false);
63+ }
4864 };
4965
5066 return (
51− <div>
52− <label>
53− Spec URL or paste JSON/YAML:
67+ <div className="space-y-4">
68+ <div>
69+ <label className="block text-sm font-medium text-foreground mb-1.5">
70+ OpenAPI Spec
71+ </label>
5472 <textarea
5573 value={specUrl}
5674 onChange={(e) => setSpecUrl((e.target as HTMLTextAreaElement).value)}
57− placeholder={'https://api.example.com/openapi.json\n\nor paste spec content here...'}
75+ placeholder={"https://api.example.com/openapi.json\n\nor paste spec content here..."}
5876 rows={6}
59− style={{ width: "100%", fontFamily: "monospace", fontSize: "0.85rem" }}
77+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring resize-y"
6078 />
61− </label>
62− <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}>
63− <button onClick={onCancel}>Cancel</button>
64− <button disabled={!specUrl.trim()} onClick={handleNext}>
65− Next
79+ <p className="mt-1 text-xs text-muted-foreground">
80+ Paste a URL or the full OpenAPI JSON/YAML content.
81+ </p>
82+ </div>
83+ <div className="flex gap-2">
84+ <button
85+ onClick={onCancel}
86+ className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent"
87+ >
88+ Cancel
89+ </button>
90+ <button
91+ disabled={!specUrl.trim() || loading}
92+ onClick={handleNext}
93+ className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
94+ >
95+ {loading ? "Analyzing…" : "Next"}
6696 </button>
6797 </div>
6898 </div>
@@ -70,64 +100,128 @@ function UrlStep() { · 1 unmodified line
70100 }
71101
72102 function AuthStep() {
73− const { preview } = useAddState();
103+ const { preview, error: stateError } = useAddState();
74104 const nav = useNav();
75105
76106 return (
77− <div>
78− {preview && Result.match(preview, {
79− onInitial: () => <p>Analyzing spec…</p>,
80− onSuccess: ({ value }) => (
81− <div>
82− <h4>{Option.getOrElse(value.title, () => "API")} {Option.getOrElse(value.version, () => "")}</h4>
83− <p>{value.operationCount} operations found</p>
84− {value.headerPresets.length > 0 && (
85− <div>
86− <p>Authentication options:</p>
87− <ul>
88− {value.headerPresets.map((p, i) => (
89− <li key={i}>{p.label}</li>
90− ))}
91− </ul>
92− </div>
93− )}
107+ <div className="space-y-4">
108+ {preview && (
109+ <div className="rounded-lg border border-border bg-card p-4">
110+ <div className="flex items-start justify-between">
111+ <div>
112+ <p className="text-sm font-medium text-card-foreground">
113+ {Option.getOrElse(preview.title, () => "API")}
114+ </p>
115+ <p className="text-xs text-muted-foreground">
116+ {Option.getOrElse(preview.version, () => "")} · {preview.operationCount} operations
117+ </p>
118+ </div>
94119 </div>
95− ),
96− onFailure: () => <p style={{ color: "red" }}>Failed to parse spec</p>,
97− })}
98− <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}>
99− <button onClick={() => nav("/")}>Back</button>
100− <button onClick={() => nav("/confirm")}>Next</button>
120+
121+ {preview.tags.length > 0 && (
122+ <div className="mt-3 flex flex-wrap gap-1">
123+ {preview.tags.slice(0, 10).map((tag) => (
124+ <span
125+ key={tag}
126+ className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground"
127+ >
128+ {tag}
129+ </span>
130+ ))}
131+ {preview.tags.length > 10 && (
132+ <span className="text-[10px] text-muted-foreground">
133+ +{preview.tags.length - 10} more
134+ </span>
135+ )}
136+ </div>
137+ )}
138+
139+ {preview.headerPresets.length > 0 && (
140+ <div className="mt-3 border-t border-border pt-3">
141+ <p className="text-xs font-medium text-muted-foreground mb-1.5">
142+ Authentication
143+ </p>
144+ {preview.headerPresets.map((p, i) => (
145+ <div key={i} className="flex items-center gap-2 py-1">
146+ <span className="size-1.5 rounded-full bg-primary/50" />
147+ <span className="text-xs text-foreground">{p.label}</span>
148+ </div>
149+ ))}
150+ </div>
151+ )}
152+ </div>
153+ )}
154+
155+ {stateError && (
156+ <p className="text-sm text-destructive">{stateError}</p>
157+ )}
158+
159+ <div className="flex gap-2">
160+ <button
161+ onClick={() => nav("/")}
162+ className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent"
163+ >
164+ Back
165+ </button>
166+ <button
167+ onClick={() => nav("/confirm")}
168+ className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
169+ >
170+ Next
171+ </button>
101172 </div>
102173 </div>
103174 );
104175 }
105176
106177 function ConfirmStep() {
107− const { specUrl, doAdd, onComplete } = useAddState();
108− const [adding, setAdding] = useState(false);
109− const [error, setError] = useState<string | null>(null);
178+ const { specUrl, preview, onComplete, setError, error } = useAddState();
110179 const nav = useNav();
180+ const [adding, setAdding] = useState(false);
181+ const doAdd = useAtomSet(addOpenApiSpec, { mode: "promise" });
111182
112183 const handleAdd = async () => {
113184 setAdding(true);
114185 setError(null);
115186 try {
116− doAdd(specUrl);
187+ await doAdd({
188+ path: { scopeId: "default" as never },
189+ payload: { spec: specUrl },
190+ });
117191 onComplete();
118192 } catch (e) {
119− setError(e instanceof Error ? e.message : String(e));
193+ setError(e instanceof Error ? e.message : "Failed to add source");
120194 setAdding(false);
121195 }
122196 };
123197
124198 return (
125− <div>
126− <p>Ready to add spec.</p>
127− {error && <p style={{ color: "red" }}>{error}</p>}
128− <div style={{ marginTop: "0.5rem", display: "flex", gap: "0.5rem" }}>
129− <button onClick={() => nav("/auth")} disabled={adding}>Back</button>
130− <button onClick={handleAdd} disabled={adding}>
199+ <div className="space-y-4">
200+ <div className="rounded-lg border border-border bg-card p-4">
201+ <p className="text-sm font-medium text-card-foreground">Ready to add source</p>
202+ <p className="mt-1 text-xs text-muted-foreground">
203+ {preview
204+ ? `${Option.getOrElse(preview.title, () => "API")} — ${preview.operationCount} operations will be registered as tools.`
205+ : `Source from: ${specUrl.slice(0, 60)}${specUrl.length > 60 ? "…" : ""}`
206+ }
207+ </p>
208+ </div>
209+
210+ {error && <p className="text-sm text-destructive">{error}</p>}
211+
212+ <div className="flex gap-2">
213+ <button
214+ onClick={() => nav("/auth")}
215+ disabled={adding}
216+ className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent disabled:opacity-50"
217+ >
218+ Back
219+ </button>
220+ <button
221+ onClick={handleAdd}
222+ disabled={adding}
223+ className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
224+ >
131225 {adding ? "Adding…" : "Add Source"}
132226 </button>
133227 </div>
@@ -144,16 +238,15 @@ export default function AddOpenApiSource(props: { · 10 unmodified lines
144238 onCancel: () => void;
145239 }) {
146240 const [specUrl, setSpecUrl] = useState("");
147− const [preview] = useState<Result.Result<SpecPreview, unknown> | null>(null);
148−
149− const doPreviewMutation = useAtomSet(previewOpenApiSpec, { mode: "promise" });
150− const doAddMutation = useAtomSet(addOpenApiSpec, { mode: "promise" });
241+ const [preview, setPreview] = useState<SpecPreview | null>(null);
242+ const [loading, setLoading] = useState(false);
243+ const [error, setError] = useState<string | null>(null);
151244
152245 const router = useMemo(() => {
153246 const rootRoute = createRootRoute({
154247 component: () => (
155248 <div>
156− <h3>Add OpenAPI Source</h3>
249+ <h1 className="text-xl font-semibold text-foreground mb-4">Add OpenAPI Source</h1>
157250 <Outlet />
158251 </div>
159252 ),
@@ -176,18 +269,10 @@ export default function AddOpenApiSource(props: { · 16 unmodified lines
176269 };
177270
178271 const state: AddState = {
179− specUrl,
180− setSpecUrl,
181− preview,
182− doPreview: (spec) => {
183− void doPreviewMutation({ path: { scopeId: "default" as never }, payload: { spec } });
184− },
185− doAdd: (spec, baseUrl, headers) => {
186− void doAddMutation({
187− path: { scopeId: "default" as never },
188− payload: { spec, baseUrl, headers },
189− });
190− },
272+ specUrl, setSpecUrl,
273+ preview, setPreview,
274+ loading, setLoading,
275+ error, setError,
191276 onComplete: props.onComplete,
192277 onCancel: props.onCancel,
193278 };

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t