import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { useCallback, useEffect, useRef, useState } from "react"; import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import type { Integration } from "@executor-js/sdk/shared"; import { integrationsAtom, integrationsOptimisticAtom, toolsAllAtom, } from "@executor-js/react/api/atoms"; import { Button } from "@executor-js/react/components/button"; import { toast } from "@executor-js/react/components/sonner"; import { copyToClipboard } from "@executor-js/react/lib/clipboard"; import { integrationPresetIconUrl } from "@executor-js/react/components/integration-favicon"; import { IntegrationIconWithAccount } from "@executor-js/react/components/integration-icon-with-account"; import { CommandPalette } from "@executor-js/react/components/command-palette"; import { useClientPlugins, useIntegrationPlugins } from "@executor-js/sdk/client"; import { ServerConnectionMenu } from "./server-connection-menu"; // ── Env ───────────────────────────────────────────────────────────────── type AppMetaEnv = { readonly VITE_APP_VERSION: string; readonly VITE_GITHUB_URL: string; }; const { VITE_APP_VERSION, VITE_GITHUB_URL } = ( import.meta as ImportMeta & { readonly env: AppMetaEnv; } ).env; // ── Version helpers ───────────────────────────────────────────────────── type UpdateChannel = "latest" | "beta"; const EXECUTOR_DIST_TAGS_PATH = "/v1/app/npm/dist-tags"; type ParsedVersion = { readonly major: number; readonly minor: number; readonly patch: number; readonly prerelease: ReadonlyArray | null; }; const semverPattern = /^(?\d+)\.(?\d+)\.(?\d+)(?:-(?[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/; const resolveUpdateChannel = (version: string): UpdateChannel => version.includes("-beta.") ? "beta" : "latest"; const parseVersion = (version: string): ParsedVersion | null => { const match = version.trim().match(semverPattern); if (!match?.groups) return null; return { major: Number(match.groups.major), minor: Number(match.groups.minor), patch: Number(match.groups.patch), prerelease: match.groups.prerelease ? match.groups.prerelease.split(".").map((id) => (/^\d+$/.test(id) ? Number(id) : id)) : null, }; }; const comparePrereleaseIdentifiers = ( left: ReadonlyArray | null, right: ReadonlyArray | null, ): number => { if (left === null && right === null) return 0; if (left === null) return 1; if (right === null) return -1; const max = Math.max(left.length, right.length); for (let i = 0; i < max; i++) { const l = left[i]; const r = right[i]; if (l === undefined) return -1; if (r === undefined) return 1; if (l === r) continue; if (typeof l === "number" && typeof r === "number") return l < r ? -1 : 1; if (typeof l === "number") return -1; if (typeof r === "number") return 1; return l < r ? -1 : 1; } return 0; }; const compareVersions = (left: string, right: string): number | null => { const lv = parseVersion(left); const rv = parseVersion(right); if (!lv || !rv) return null; if (lv.major !== rv.major) return lv.major < rv.major ? -1 : 1; if (lv.minor !== rv.minor) return lv.minor < rv.minor ? -1 : 1; if (lv.patch !== rv.patch) return lv.patch < rv.patch ? -1 : 1; return comparePrereleaseIdentifiers(lv.prerelease, rv.prerelease); }; // ── useLatestVersion ──────────────────────────────────────────────────── function useLatestVersion(currentVersion: string) { const channel = resolveUpdateChannel(currentVersion); const [latestVersion, setLatestVersion] = useState(null); useEffect(() => { let cancelled = false; void Effect.runPromiseExit( Effect.tryPromise({ try: async () => { const res = await fetch(EXECUTOR_DIST_TAGS_PATH); if (!res.ok) return null; return (await res.json()) as Partial>; }, catch: (cause) => cause, }), ).then((exit) => { if (!cancelled && Exit.isSuccess(exit)) { setLatestVersion(exit.value?.[channel] ?? null); } }); return () => { cancelled = true; }; }, [channel]); const updateAvailable = latestVersion !== null && compareVersions(currentVersion, latestVersion) === -1; return { latestVersion, updateAvailable, channel }; } // ── UpdateCard ────────────────────────────────────────────────────────── function UpdateCard(props: { latestVersion: string; channel: UpdateChannel }) { const command = `npm i -g executor@${props.channel}`; const [copied, setCopied] = useState(false); const handleCopy = useCallback(() => { void copyToClipboard(command).then((ok) => { if (!ok) { toast.error("Failed to copy to clipboard"); return; } setCopied(true); setTimeout(() => setCopied(false), 1500); }); }, [command]); return (

Update available

v{props.latestVersion}

); } // ── NavItem ────────────────────────────────────────────────────────────── function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) { return ( {props.label} ); } // ── PluginNav ──────────────────────────────────────────────────────────── // // Renders one nav link per plugin page that opted in via // `pages[].nav.label`. The catch-all `/plugins/$pluginId/$` route is the // mount point; the splat is the page's relative path with the leading // slash stripped. function PluginNav(props: { pathname: string; onNavigate?: () => void }) { const plugins = useClientPlugins(); const entries = plugins.flatMap((plugin) => (plugin.pages ?? []) .filter((page) => page.nav) .map((page) => { const splat = page.path.replace(/^\//, ""); const href = `/plugins/${plugin.id}${splat ? `/${splat}` : "/"}`; return { key: `${plugin.id}:${page.path}`, pluginId: plugin.id, splat, href, label: page.nav!.label, }; }), ); if (entries.length === 0) return null; return ( <> {entries.map((entry) => ( {entry.label} ))} ); } // ── IntegrationList ─────────────────────────────────────────────────────────── function IntegrationList(props: { pathname: string; onNavigate?: () => void }) { const integrations = useAtomValue(integrationsOptimisticAtom); const integrationPlugins = useIntegrationPlugins(); return AsyncResult.match(integrations, { onInitial: () =>
Loading…
, onFailure: () => (
No integrations yet
), onSuccess: ({ value }: { readonly value: readonly Integration[] }) => value.length === 0 ? (
No integrations yet
) : (
{value.map((integration: Integration) => { const slug = String(integration.slug); const name = integration.name || slug; const detailPath = `/integrations/${slug}`; const active = props.pathname === detailPath || props.pathname.startsWith(`${detailPath}/`); return ( {name} ); })}
), }); } // ── SidebarContent ─────────────────────────────────────────────────────── function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean; updateAvailable: boolean; latestVersion: string | null; channel: UpdateChannel; }) { const isHome = props.pathname === "/"; const isSecrets = props.pathname === "/secrets"; const isPolicies = props.pathname === "/policies"; const isToolkits = props.pathname === "/toolkits" || props.pathname.startsWith("/toolkits/"); return ( <> {props.showBrand !== false && (
executor Beta
)} {props.updateAvailable && props.latestVersion && ( )} {/* Footer */} ); } // ── Shell ───────────────────────────────────────────────────────────────── export function Shell() { const location = useLocation(); const pathname = location.pathname; const refreshSources = useAtomRefresh(integrationsAtom); const refreshTools = useAtomRefresh(toolsAllAtom); const { latestVersion, updateAvailable, channel } = useLatestVersion(VITE_APP_VERSION); const lastPathname = useRef(pathname); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); if (lastPathname.current !== pathname) { lastPathname.current = pathname; if (mobileSidebarOpen) setMobileSidebarOpen(false); } // Lock scroll when mobile sidebar open useEffect(() => { if (!mobileSidebarOpen) return; const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, [mobileSidebarOpen]); useEffect(() => { if (!import.meta.hot) { return; } const refreshBackendData = () => { refreshSources(); refreshTools(); }; import.meta.hot.on("executor:backend-updated", refreshBackendData); return () => { import.meta.hot?.off("executor:backend-updated", refreshBackendData); }; }, [refreshSources, refreshTools]); return (
{/* Desktop sidebar */} {/* Mobile sidebar overlay */} {mobileSidebarOpen && (
{/* oxlint-disable-next-line react/forbid-elements */}
setMobileSidebarOpen(false)} showBrand={false} updateAvailable={updateAvailable} latestVersion={latestVersion} channel={channel} />
)} {/* Main content */}
{/* Desktop (macOS frameless) draggable title-bar strip. Gives the main area the same native window drag + double-click-to-zoom as the sidebar header; hidden everywhere else via CSS. */}
{/* Mobile top bar */}
executor
); }