diff --git a/frontend/apps/inspector/src/index.css b/frontend/apps/inspector/src/index.css index 52aa134b3d..fec19d7d56 100644 --- a/frontend/apps/inspector/src/index.css +++ b/frontend/apps/inspector/src/index.css @@ -1,4 +1,4 @@ -@import "../../../packages/components/public/theme.css"; +@import "../../../src/index.css"; @tailwind base; @tailwind components; @@ -42,37 +42,3 @@ } } -:root { - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-color-text: theme("colors.white"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-constant: theme("colors.violet.300"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-string: theme("colors.violet.300"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-comment: theme("colors.zinc.500"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-keyword: theme("colors.sky.300"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-parameter: theme("colors.pink.300"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-function: theme("colors.violet.300"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-string-expression: theme("colors.violet.300"); - /* biome-ignore lint/correctness/noUnknownFunction: tailwind functions */ - --shiki-token-punctuation: theme("colors.zinc.200"); - - --spacing: 0.25rem; - - ::-webkit-scrollbar { - @apply w-2.5 h-2.5; - } - - ::-webkit-scrollbar-track { - @apply bg-transparent; - } - - ::-webkit-scrollbar-thumb { - @apply rounded-full bg-border border-[1px] border-transparent border-solid bg-clip-padding; - } -} diff --git a/frontend/src/components/actors/actors-actor-details.tsx b/frontend/src/components/actors/actors-actor-details.tsx index 0373767d6c..58e0b0ba44 100644 --- a/frontend/src/components/actors/actors-actor-details.tsx +++ b/frontend/src/components/actors/actors-actor-details.tsx @@ -1,4 +1,5 @@ import { faQuestionSquare, Icon } from "@rivet-gg/icons"; +import { useQuery } from "@tanstack/react-query"; import { memo, type ReactNode, Suspense } from "react"; import { cn, @@ -9,11 +10,12 @@ import { TabsTrigger, } from "@/components"; import { ActorConfigTab } from "./actor-config-tab"; -import { ActorDatabaseTab } from "./actor-db-tab"; -import { ActorQueueTab } from "./actor-queue-tab"; import { ActorConnectionsTab } from "./actor-connections-tab"; +import { ActorDatabaseTab } from "./actor-db-tab"; import { ActorDetailsSettingsProvider } from "./actor-details-settings"; +import { useActorInspector } from "./actor-inspector-context"; import { ActorLogsTab } from "./actor-logs-tab"; +import { ActorQueueTab } from "./actor-queue-tab"; import { ActorStateTab } from "./actor-state-tab"; import { QueriedActorStatus } from "./actor-status"; import { ActorStopButton } from "./actor-stop-button"; @@ -85,6 +87,276 @@ export const ActorsActorEmptyDetails = () => { ); }; +const TAB_PRIORITY = [ + "workflow", + "database", + "state", + "queue", + "connections", + "metadata", +] as const; + +type TabId = (typeof TAB_PRIORITY)[number]; + +function useActorTabVisibility(actorId: ActorId) { + const inspector = useActorInspector(); + + const { data: stateData } = useQuery( + inspector.actorStateQueryOptions(actorId), + ); + const { data: isDatabaseEnabled } = useQuery( + inspector.actorDatabaseEnabledQueryOptions(actorId), + ); + const { data: isWorkflowEnabled } = useQuery( + inspector.actorIsWorkflowEnabledQueryOptions(actorId), + ); + + const isStateEnabled = stateData?.isEnabled ?? false; + const isQueueEnabled = inspector.features.queue.supported; + + const hiddenTabs = new Set(); + if (!isWorkflowEnabled) hiddenTabs.add("workflow"); + if (!isDatabaseEnabled) hiddenTabs.add("database"); + if (!isStateEnabled) hiddenTabs.add("state"); + if (!isQueueEnabled) hiddenTabs.add("queue"); + + const firstAvailableTab = + TAB_PRIORITY.find((tab) => !hiddenTabs.has(tab)) ?? "connections"; + + return { hiddenTabs, firstAvailableTab }; +} + +function ActorTabTriggers({ + actorId, + tab, + onTabChange, + className, + children, +}: { + actorId: ActorId; + tab?: string; + onTabChange?: (tab: string) => void; + className?: string; + children?: ReactNode; +}) { + const { hiddenTabs, firstAvailableTab } = useActorTabVisibility(actorId); + const guardContent = useInspectorGuard(); + + const normalizedTab = tab === "events" ? "traces" : tab; + const isTabHidden = normalizedTab && hiddenTabs.has(normalizedTab as TabId); + const value = + !normalizedTab || isTabHidden ? firstAvailableTab : normalizedTab; + + return ( + + {children} + + ); +} + +function ActorTabsShell({ + actorId, + value, + onValueChange, + className, + guardContent, + hiddenTabs, + children, +}: { + actorId: ActorId; + value: string; + onValueChange?: (tab: string) => void; + className?: string; + guardContent: ReactNode; + hiddenTabs: Set; + children?: ReactNode; +}) { + return ( + +
+
+ + {!hiddenTabs.has("workflow") && ( + + Workflow + + )} + {!hiddenTabs.has("database") && ( + + Database + + )} + {!hiddenTabs.has("state") && ( + + State + + )} + {!hiddenTabs.has("queue") && ( + + Queue + + )} + + Connections + + + Metadata + + + + + + +
+
+ + }> + {guardContent || } + + + + + + + {guardContent || } + + + {guardContent || } + + + {guardContent || } + + + {guardContent || } + + + {guardContent || } + + {children} +
+ ); +} + +function ActorTabsNotRunning({ + actorId, + tab, + onTabChange, + className, + children, +}: { + actorId: ActorId; + tab?: string; + onTabChange?: (tab: string) => void; + className?: string; + children?: ReactNode; +}) { + const guardContent = useInspectorGuard(); + const normalizedTab = tab === "events" ? "traces" : tab; + const value = normalizedTab || "connections"; + + return ( + + {children} + + ); +} + +function ActorTabsWithId({ + actorId, + tab, + onTabChange, + className, + children, +}: { + actorId: ActorId; + tab?: string; + onTabChange?: (tab: string) => void; + className?: string; + children?: ReactNode; +}) { + const guardContent = useInspectorGuard(); + + // Inspector context is only available when the actor is running (guardContent is null). + // We must not call useActorInspector hooks when the inspector is unavailable. + if (guardContent) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +} + export function ActorTabs({ tab, onTabChange, @@ -100,16 +372,22 @@ export function ActorTabs({ className?: string; children?: ReactNode; }) { - const normalizedTab = tab === "events" ? "traces" : tab; - const value = disabled ? undefined : normalizedTab || "state"; - - const guardContent = useInspectorGuard(); + if (actorId) { + return ( + + {children} + + ); + } return (
@@ -117,40 +395,38 @@ export function ActorTabs({ - State + Workflow - - Connections + Database - - Queue + State - Workflow + Queue - Database + Connections - {actorId ? ( - - - - - ) : null}
- {actorId ? ( - <> - - }> - {guardContent || } - - - - - - - {guardContent || ( - - )} - - - {guardContent || } - - - {guardContent || } - - - {guardContent || } - - - {guardContent || } - - - ) : null} {children}
); diff --git a/frontend/src/components/actors/workflow/workflow-visualizer.tsx b/frontend/src/components/actors/workflow/workflow-visualizer.tsx index 0511df9825..34f118d0a0 100644 --- a/frontend/src/components/actors/workflow/workflow-visualizer.tsx +++ b/frontend/src/components/actors/workflow/workflow-visualizer.tsx @@ -1,29 +1,29 @@ "use client"; -import { useMemo, useState, useCallback } from "react"; import { Background, BackgroundVariant, Controls, MiniMap, - ReactFlow, - ReactFlowProvider, type Node, type NodeMouseHandler, + ReactFlow, + ReactFlowProvider, } from "@xyflow/react"; +import { useCallback, useMemo, useState } from "react"; import "@xyflow/react/dist/style.css"; import { faXmark, Icon } from "@rivet-gg/icons"; import { cn, DiscreteCopyButton } from "@/components"; import { ActorObjectInspector } from "../console/actor-inspector"; +import { workflowHistoryToXYFlow } from "./workflow-to-xyflow"; +import type { WorkflowHistory } from "./workflow-types"; import { + formatDuration, TYPE_COLORS, TypeIcon, - formatDuration, - workflowNodeTypes, type WorkflowNodeData, + workflowNodeTypes, } from "./xyflow-nodes"; -import { workflowHistoryToXYFlow } from "./workflow-to-xyflow"; -import type { WorkflowHistory } from "./workflow-types"; type MetaExtendedEntryType = | "step" @@ -79,7 +79,6 @@ export function WorkflowVisualizer({ nodeTypes={workflowNodeTypes} fitView panOnScroll - selectionOnDrag panOnDrag={[1, 2]} edgesFocusable={false} onNodeClick={onNodeClick} @@ -118,7 +117,9 @@ export function WorkflowVisualizer({ }} > @@ -181,7 +182,9 @@ export function WorkflowVisualizer({ Key diff --git a/frontend/src/components/theme.css b/frontend/src/components/theme.css index f1b729a53e..133c1b49a9 100644 --- a/frontend/src/components/theme.css +++ b/frontend/src/components/theme.css @@ -68,4 +68,44 @@ .shiki .highlighted { @apply bg-secondary/50; -} \ No newline at end of file +} + +/* Dark theme overrides for xyflow Controls. */ +.react-flow__controls { + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: 6px; + box-shadow: none; +} + +.react-flow__controls-button { + background: transparent; + border-bottom: 1px solid hsl(var(--border)); + fill: hsl(var(--foreground)); +} + +.react-flow__controls-button:hover { + background: hsl(var(--accent)); +} + +.react-flow__controls-button:last-child { + border-bottom: none; +} + +/* Dark theme overrides for xyflow MiniMap. */ +.react-flow__minimap { + background: hsl(var(--card)) !important; + border: 1px solid hsl(var(--border)); + border-radius: 6px; +} + +/* Remove Ladle story container padding and make it fill the full viewport. */ +.ladle-main { + padding: 0 !important; + height: 100vh; +} + +/* Dark theme for xyflow edges. */ +.react-flow__edge-path { + stroke: hsl(var(--muted-foreground)); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index 4385dbf813..00dabe8032 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,4 @@ -@import "@/components/theme.css"; +@import "../packages/components/public/theme.css"; @tailwind base; @tailwind components;