@motiadev/workbench 0.5.11-beta.120-433270 → 0.5.11-beta.120-110250

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.html +21 -1
  3. package/dist/index.js +1 -0
  4. package/dist/middleware.js +15 -2
  5. package/dist/src/App.js +6 -27
  6. package/dist/src/components/flow/hooks/use-get-flow-state.js +1 -0
  7. package/dist/src/components/flow/node-organizer.js +4 -38
  8. package/dist/src/components/header/header.js +1 -1
  9. package/dist/src/components/logs/logs-page.js +1 -1
  10. package/dist/src/components/observability/trace-item/trace-item.js +1 -1
  11. package/dist/src/components/observability/traces-groups.js +1 -1
  12. package/dist/src/components/sidebar/sidebar.js +1 -1
  13. package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +12 -0
  14. package/dist/src/components/tutorial/engine/tutorial-engine.js +36 -0
  15. package/dist/src/components/tutorial/engine/tutorial-types.d.ts +22 -0
  16. package/dist/src/components/tutorial/engine/tutorial-types.js +1 -0
  17. package/dist/src/components/tutorial/engine/workbench-xpath.d.ts +39 -0
  18. package/dist/src/components/tutorial/engine/workbench-xpath.js +39 -0
  19. package/dist/src/components/tutorial/hooks/tutorial-utils.d.ts +1 -0
  20. package/dist/src/components/tutorial/hooks/tutorial-utils.js +17 -0
  21. package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +14 -0
  22. package/dist/src/components/tutorial/hooks/use-tutorial-engine.js +162 -0
  23. package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +5 -0
  24. package/dist/src/components/tutorial/hooks/use-tutorial.js +10 -0
  25. package/dist/src/components/tutorial/tutorial-button.js +20 -0
  26. package/dist/src/components/tutorial/tutorial-step.d.ts +14 -0
  27. package/dist/src/components/tutorial/tutorial-step.js +18 -0
  28. package/dist/src/components/tutorial/tutorial.d.ts +2 -0
  29. package/dist/src/components/tutorial/tutorial.js +8 -0
  30. package/dist/src/components/ui/theme-toggle.js +0 -8
  31. package/dist/src/publicComponents/base-node/base-node.js +3 -1
  32. package/dist/src/publicComponents/base-node/code-display.d.ts +9 -0
  33. package/dist/src/publicComponents/base-node/code-display.js +64 -0
  34. package/dist/src/publicComponents/base-node/feature-card.d.ts +9 -0
  35. package/dist/src/publicComponents/base-node/feature-card.js +5 -0
  36. package/dist/src/publicComponents/base-node/node-sidebar.d.ts +2 -0
  37. package/dist/src/publicComponents/base-node/node-sidebar.js +4 -5
  38. package/dist/src/stores/use-flow-store.d.ts +3 -5
  39. package/dist/src/stores/use-global-store.d.ts +3 -5
  40. package/dist/src/stores/use-tabs-store.d.ts +3 -5
  41. package/dist/src/stores/use-theme-store.d.ts +5 -6
  42. package/dist/src/types/file.d.ts +7 -0
  43. package/dist/src/types/file.js +1 -0
  44. package/dist/tsconfig.app.tsbuildinfo +1 -1
  45. package/dist/tsconfig.node.tsbuildinfo +1 -1
  46. package/package.json +13 -12
  47. package/dist/src/components/ui/tutorial-button.js +0 -69
  48. /package/dist/src/components/{ui → tutorial}/tutorial-button.d.ts +0 -0
package/dist/index.d.ts CHANGED
@@ -6,3 +6,5 @@ export { BaseHandle } from './src/publicComponents/base-node/base-handle';
6
6
  export { Position } from '@xyflow/react';
7
7
  export type { EventNodeData, ApiNodeData } from './src/types/flow';
8
8
  export type { ApiNodeProps, BaseNodeProps, CronNodeProps, EventNodeProps, NoopNodeProps, } from './src/publicComponents/node-props';
9
+ export type { TutorialStep } from './src/components/tutorial/engine/tutorial-types';
10
+ export { workbenchXPath } from './src/components/tutorial/engine/workbench-xpath';
package/dist/index.html CHANGED
@@ -10,6 +10,10 @@
10
10
  href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
11
11
  rel="stylesheet"
12
12
  />
13
+ <link
14
+ href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
15
+ rel="stylesheet"
16
+ />
13
17
  <script src="https://cdn.amplitude.com/libs/analytics-browser-2.11.1-min.js.gz"></script>
14
18
  <script>
15
19
  window.amplitude.init('ab2408031a38aa5cb85587a27ecfc69c', {
@@ -25,10 +29,26 @@
25
29
 
26
30
  <!-- Start of Reo Javascript -->
27
31
  <script type="text/javascript">
28
- !function(){var e,t,n;e="d8f0ce9cae8ae64",t=function(){Reo.init({clientID:"d8f0ce9cae8ae64"})},(n=document.createElement("script")).src="https://static.reo.dev/"+e+"/reo.js",n.defer=!0,n.onload=t,document.head.appendChild(n)}();
32
+ !(function () {
33
+ var e, t, n
34
+ ;(e = 'd8f0ce9cae8ae64'),
35
+ (t = function () {
36
+ Reo.init({ clientID: 'd8f0ce9cae8ae64' })
37
+ }),
38
+ ((n = document.createElement('script')).src = 'https://static.reo.dev/' + e + '/reo.js'),
39
+ (n.defer = !0),
40
+ (n.onload = t),
41
+ document.head.appendChild(n)
42
+ })()
29
43
  </script>
30
44
  <!-- End of Reo Javascript -->
31
45
 
46
+ <script>
47
+ const importFile = async (path) => {
48
+ return await import(/* @vite-ignore */ `/@fs/${processCwd}/${path}`)
49
+ }
50
+ </script>
51
+
32
52
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
33
53
  <title>Motia Workbench</title>
34
54
  </head>
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export { NoopNode } from './src/publicComponents/noop-node';
4
4
  export { BaseNode } from './src/publicComponents/base-node/base-node';
5
5
  export { BaseHandle } from './src/publicComponents/base-node/base-handle';
6
6
  export { Position } from '@xyflow/react';
7
+ export { workbenchXPath } from './src/components/tutorial/engine/workbench-xpath';
@@ -8,6 +8,15 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const vite_1 = require("vite");
10
10
  const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
11
+ const processCwdPlugin = () => {
12
+ return {
13
+ name: 'html-transform',
14
+ transformIndexHtml: (html) => {
15
+ const cwd = process.cwd();
16
+ return html.replace('</head>', `<script>const processCwd = "${cwd}";</script></head>`);
17
+ },
18
+ };
19
+ };
11
20
  const applyMiddleware = async (app) => {
12
21
  const vite = await (0, vite_1.createServer)({
13
22
  appType: 'spa',
@@ -17,13 +26,17 @@ const applyMiddleware = async (app) => {
17
26
  allowedHosts: true,
18
27
  host: true,
19
28
  fs: {
20
- allow: [__dirname, path_1.default.join(process.cwd(), './steps')],
29
+ allow: [
30
+ __dirname, // workbench root
31
+ path_1.default.join(process.cwd(), './steps'), // steps directory
32
+ path_1.default.join(process.cwd(), './tutorial.tsx'), // tutorial file
33
+ ],
21
34
  },
22
35
  },
23
36
  resolve: {
24
37
  alias: { '@': path_1.default.resolve(__dirname, './src') },
25
38
  },
26
- plugins: [(0, plugin_react_1.default)()],
39
+ plugins: [(0, plugin_react_1.default)(), processCwdPlugin()],
27
40
  });
28
41
  app.use(vite.middlewares);
29
42
  app.use('*', async (req, res, next) => {
package/dist/src/App.js CHANGED
@@ -1,9 +1,9 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { CollapsiblePanel, CollapsiblePanelGroup, Panel, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui';
3
- import { ReactFlowProvider } from '@xyflow/react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
2
  import { analytics } from '@/lib/analytics';
3
+ import { CollapsiblePanel, CollapsiblePanelGroup, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui';
4
+ import { ReactFlowProvider } from '@xyflow/react';
5
5
  import { File, GanttChart, Link2, LogsIcon } from 'lucide-react';
6
- import { useCallback, useEffect, useMemo, useState } from 'react';
6
+ import { useCallback, useMemo } from 'react';
7
7
  import { EndpointsPage } from './components/endpoints/endpoints-page';
8
8
  import { FlowPage } from './components/flow/flow-page';
9
9
  import { FlowTabMenuItem } from './components/flow/flow-tab-menu-item';
@@ -12,6 +12,7 @@ import { LogsPage } from './components/logs/logs-page';
12
12
  import { TracesPage } from './components/observability/traces-page';
13
13
  import { APP_SIDEBAR_CONTAINER_ID } from './components/sidebar/sidebar';
14
14
  import { StatesPage } from './components/states/states-page';
15
+ import { Tutorial } from './components/tutorial/tutorial';
15
16
  import { useTabsStore } from './stores/use-tabs-store';
16
17
  var TabLocation;
17
18
  (function (TabLocation) {
@@ -22,7 +23,6 @@ export const App = () => {
22
23
  const tab = useTabsStore((state) => state.tab);
23
24
  const setTopTab = useTabsStore((state) => state.setTopTab);
24
25
  const setBottomTab = useTabsStore((state) => state.setBottomTab);
25
- const [viewMode, setViewMode] = useState('system');
26
26
  const tabChangeCallbacks = useMemo(() => ({
27
27
  [TabLocation.TOP]: setTopTab,
28
28
  [TabLocation.BOTTOM]: setBottomTab,
@@ -31,26 +31,5 @@ export const App = () => {
31
31
  analytics.track(`${location} tab changed`, { [`new.${location}`]: newTab, tab });
32
32
  tabChangeCallbacks[location](newTab);
33
33
  }, [tabChangeCallbacks, tab]);
34
- useEffect(() => {
35
- const url = new URL(window.location.href);
36
- const viewMode = url.searchParams.get('view-mode');
37
- if (viewMode) {
38
- setViewMode(viewMode);
39
- }
40
- }, [setViewMode]);
41
- if (viewMode === 'project') {
42
- return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen ", children: [_jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsx(Panel, { tabs: [
43
- {
44
- label: 'Flow',
45
- labelComponent: _jsx(FlowTabMenuItem, {}),
46
- content: (_jsx(ReactFlowProvider, { children: _jsx("div", { className: "h-[calc(100vh-100px)] w-full", children: _jsx(FlowPage, {}) }) })),
47
- },
48
- {
49
- label: 'Endpoint',
50
- labelComponent: (_jsxs(_Fragment, { children: [_jsx(Link2, {}), "Endpoint"] })),
51
- content: _jsx(EndpointsPage, {}),
52
- },
53
- ] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
54
- }
55
- return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("div", { className: "col-span-2", children: _jsx(Header, {}) }), _jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsxs(CollapsiblePanelGroup, { autoSaveId: "app-panel", direction: "vertical", className: "gap-1 h-full", "aria-label": "Workbench panels", children: [_jsxs(CollapsiblePanel, { id: "top-panel", variant: 'tabs', defaultTab: tab.top, onTabChange: onTabChange(TabLocation.TOP), header: _jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "flow", "data-testid": "flows-link", children: _jsx(FlowTabMenuItem, {}) }), _jsxs(TabsTrigger, { value: "endpoint", "data-testid": "endpoints-link", className: "cursor-pointer", children: [_jsx(Link2, {}), "Endpoint"] })] }), children: [_jsx(TabsContent, { value: "flow", className: "h-full", asChild: true, children: _jsx(ReactFlowProvider, { children: _jsx(FlowPage, {}) }) }), _jsx(TabsContent, { value: "endpoint", asChild: true, children: _jsx(EndpointsPage, {}) })] }), _jsxs(CollapsiblePanel, { id: "bottom-panel", variant: 'tabs', defaultTab: tab.bottom, onTabChange: onTabChange(TabLocation.BOTTOM), header: _jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "tracing", "data-testid": "traces-link", className: "cursor-pointer", children: [_jsx(GanttChart, {}), " Tracing"] }), _jsxs(TabsTrigger, { value: "logs", "data-testid": "logs-link", className: "cursor-pointer", children: [_jsx(LogsIcon, {}), "Logs"] }), _jsxs(TabsTrigger, { value: "states", "data-testid": "states-link", className: "cursor-pointer", children: [_jsx(File, {}), "States"] })] }), children: [_jsx(TabsContent, { value: "tracing", className: "max-h-fit", asChild: true, children: _jsx(TracesPage, {}) }), _jsx(TabsContent, { value: "logs", asChild: true, children: _jsx(LogsPage, {}) }), _jsx(TabsContent, { value: "states", asChild: true, children: _jsx(StatesPage, {}) })] })] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
34
+ return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("div", { className: "col-span-2", children: _jsx(Header, {}) }), _jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsxs(CollapsiblePanelGroup, { autoSaveId: "app-panel", direction: "vertical", className: "gap-1 h-full", "aria-label": "Workbench panels", children: [_jsxs(CollapsiblePanel, { id: "top-panel", variant: 'tabs', defaultTab: tab.top, onTabChange: onTabChange(TabLocation.TOP), header: _jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "flow", "data-testid": "flows-link", children: _jsx(FlowTabMenuItem, {}) }), _jsxs(TabsTrigger, { value: "endpoint", "data-testid": "endpoints-link", className: "cursor-pointer", children: [_jsx(Link2, {}), "Endpoint"] })] }), children: [_jsx(TabsContent, { value: "flow", className: "h-full", asChild: true, children: _jsx(ReactFlowProvider, { children: _jsx(FlowPage, {}) }) }), _jsx(TabsContent, { value: "endpoint", asChild: true, children: _jsx(EndpointsPage, {}) })] }), _jsxs(CollapsiblePanel, { id: "bottom-panel", variant: 'tabs', defaultTab: tab.bottom, onTabChange: onTabChange(TabLocation.BOTTOM), header: _jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "tracing", "data-testid": "traces-link", className: "cursor-pointer", children: [_jsx(GanttChart, {}), " Tracing"] }), _jsxs(TabsTrigger, { value: "logs", "data-testid": "logs-link", className: "cursor-pointer", children: [_jsx(LogsIcon, {}), "Logs"] }), _jsxs(TabsTrigger, { value: "states", "data-testid": "states-link", className: "cursor-pointer", children: [_jsx(File, {}), "States"] })] }), children: [_jsx(TabsContent, { value: "tracing", className: "max-h-fit", asChild: true, children: _jsx(TracesPage, {}) }), _jsx(TabsContent, { value: "logs", asChild: true, children: _jsx(LogsPage, {}) }), _jsx(TabsContent, { value: "states", asChild: true, children: _jsx(StatesPage, {}) })] })] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID }), _jsx(Tutorial, {})] }));
56
35
  };
@@ -32,6 +32,7 @@ async function importFlow(flow, flowConfig) {
32
32
  return;
33
33
  }
34
34
  try {
35
+ console.log(path);
35
36
  const module = await import(/* @vite-ignore */ `/@fs/${path}`);
36
37
  const component = module.Node ?? module.default;
37
38
  nodeComponentCache.set(path, component);
@@ -1,39 +1,5 @@
1
1
  import { useNodesInitialized, useReactFlow } from '@xyflow/react';
2
- import dagre from 'dagre';
3
2
  import { useEffect, useRef } from 'react';
4
- const organizeNodes = (nodes, edges) => {
5
- const dagreGraph = new dagre.graphlib.Graph({ compound: true });
6
- dagreGraph.setDefaultEdgeLabel(() => ({}));
7
- dagreGraph.setGraph({ rankdir: 'TB', ranksep: 80, nodesep: 60, edgesep: 20, ranker: 'tight-tree' });
8
- nodes.forEach((node) => {
9
- dagreGraph.setNode(node.id, { width: node.measured?.width, height: node.measured?.height });
10
- });
11
- edges.forEach((edge) => {
12
- if (typeof edge.label === 'string') {
13
- dagreGraph.setEdge(edge.source, edge.target, {
14
- label: edge.label ?? '',
15
- width: edge.label.length * 40, // Add width for the label
16
- height: 30, // Add height for the label
17
- labelpos: 'c', // Position label in center
18
- });
19
- }
20
- else {
21
- dagreGraph.setEdge(edge.source, edge.target);
22
- }
23
- });
24
- dagre.layout(dagreGraph);
25
- return nodes.map((node) => {
26
- if (node.position.x !== 0 || node.position.y !== 0) {
27
- return node;
28
- }
29
- const { x, y } = dagreGraph.node(node.id);
30
- const position = {
31
- x: x - (node.measured?.width ?? 0) / 2,
32
- y: y - (node.measured?.height ?? 0) / 2,
33
- };
34
- return { ...node, position };
35
- });
36
- };
37
3
  export const NodeOrganizer = ({ onInitialized }) => {
38
4
  const { setNodes, getNodes, getEdges, fitView } = useReactFlow();
39
5
  const nodesInitialized = useNodesInitialized();
@@ -41,10 +7,10 @@ export const NodeOrganizer = ({ onInitialized }) => {
41
7
  useEffect(() => {
42
8
  if (nodesInitialized && !initialized.current) {
43
9
  initialized.current = true;
44
- const nodes = getNodes();
45
- const edges = getEdges();
46
- const organizedNodes = organizeNodes(nodes, edges);
47
- setNodes(organizedNodes);
10
+ // const nodes = getNodes() as Node<EventNodeData | ApiNodeData>[]
11
+ // const edges = getEdges() as Edge<EdgeData>[]
12
+ // const organizedNodes = organizeNodes(nodes, edges)
13
+ // setNodes(organizedNodes)
48
14
  onInitialized();
49
15
  setTimeout(async () => {
50
16
  await fitView();
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useThemeStore } from '@/stores/use-theme-store';
3
3
  import { ThemeToggle } from '../ui/theme-toggle';
4
- import { TutorialButton } from '../ui/tutorial-button';
4
+ import { TutorialButton } from '../tutorial/tutorial-button';
5
5
  export const Header = () => {
6
6
  const theme = useThemeStore((state) => state.theme);
7
7
  return (_jsxs("header", { className: "min-h-16 px-4 gap-4 flex items-center bg-default text-default-foreground border-b", children: [_jsx("img", { src: `/motia-${theme}.png`, className: "h-5", id: "logo-icon", "data-testid": "logo-icon" }), _jsx("div", { className: "flex-1" }), _jsx(TutorialButton, {}), _jsx(ThemeToggle, {})] }));
@@ -22,7 +22,7 @@ export const LogsPage = () => {
22
22
  log.step.toLowerCase().includes(search.toLowerCase()));
23
23
  });
24
24
  }, [logs, search]);
25
- return (_jsxs("div", { className: "h-full flex flex-row", "data-testid": "logs-container", children: [_jsxs("div", { className: "flex-1 overflow-y-auto overflow-x-auto", children: [_jsxs("div", { className: "flex p-2 border-b gap-4", "data-testid": "logs-search-container", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx(Input, { variant: "shade", value: search, onChange: (e) => setSearch(e.target.value), className: "pr-10 font-medium" }), _jsx(CircleX, { className: "cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground/50 hover:text-muted-foreground", onClick: () => setSearch('') })] }), _jsxs(Button, { variant: "outline", onClick: resetLogs, children: [_jsx(Trash, {}), " Clear"] })] }), _jsx(Table, { children: _jsx(TableBody, { className: "font-mono font-medium", children: filteredLogs.map((log, index) => (_jsxs(TableRow, { className: cn('font-mono font-semibold cursor-pointer border-0', {
25
+ return (_jsxs("div", { className: "h-full flex flex-row", "data-testid": "logs-container", children: [_jsxs("div", { className: "flex-1 overflow-y-auto overflow-x-auto", children: [_jsxs("div", { className: "flex p-2 border-b gap-4", "data-testid": "logs-search-container", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx(Input, { variant: "shade", value: search, onChange: (e) => setSearch(e.target.value), className: "pr-10 font-medium" }), _jsx(CircleX, { className: "cursor-pointer absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground/50 hover:text-muted-foreground", onClick: () => setSearch('') })] }), _jsxs(Button, { variant: "outline", onClick: resetLogs, children: [_jsx(Trash, {}), " Clear"] })] }), _jsx(Table, { children: _jsx(TableBody, { className: "font-mono font-medium", children: filteredLogs.map((log, index) => (_jsxs(TableRow, { "data-testid": "log-row", className: cn('font-mono font-semibold cursor-pointer border-0', {
26
26
  'bg-muted-foreground/10 hover:bg-muted-foreground/20': selectedLogId === log.id,
27
27
  'hover:bg-muted-foreground/10': selectedLogId !== log.id,
28
28
  }), onClick: () => selectLogId(log.id), children: [_jsxs(TableCell, { "data-testid": `time-${index}`, className: "whitespace-nowrap flex items-center gap-2 text-muted-foreground", children: [_jsx(LogLevelDot, { level: log.level }), formatTimestamp(log.time)] }), _jsx(TableCell, { "data-testid": `trace-${log.traceId}`, className: "whitespace-nowrap cursor-pointer hover:text-primary text-muted-foreground", onClick: () => setSearch(log.traceId), children: log.traceId }), _jsx(TableCell, { "data-testid": `step-${index}`, "aria-label": log.step, className: "whitespace-nowrap", children: log.step }), _jsx(TableCell, { "data-testid": `msg-${index}`, "aria-label": log.msg, className: "whitespace-nowrap max-w-[500px] truncate w-full", children: log.msg })] }, index))) }) })] }), _jsx(LogDetail, { log: selectedLog, onClose: () => selectLogId(undefined) })] }));
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '@/lib/utils';
3
3
  export const TraceItem = ({ trace, group, groupEndTime, onExpand }) => {
4
- return (_jsxs("div", { className: "flex hover:bg-muted-foreground/10 relative cursor-pointer", onClick: () => onExpand(trace.id), children: [_jsx("div", { className: "flex items-center min-w-[200px] max-w-[200px] h-[32px] max-h-[32px] py-4 px-2 text-sm font-semibold text-foreground truncate sticky left-0 bg-card z-9", children: trace.name }), _jsx("div", { className: "flex w-full flex-row items-center hover:bg-muted/50 rounded-md", "data-testid": "trace-timeline-item", children: _jsx("div", { className: "relative w-full h-[32px] flex items-center", children: _jsx("div", { className: cn('h-[24px] rounded-[4px] hover:opacity-80 transition-all duration-200', {
4
+ return (_jsxs("div", { className: "flex hover:bg-muted-foreground/10 relative cursor-pointer", onClick: () => onExpand(trace.id), "data-testid": "trace-timeline-item", children: [_jsx("div", { className: "flex items-center min-w-[200px] max-w-[200px] h-[32px] max-h-[32px] py-4 px-2 text-sm font-semibold text-foreground truncate sticky left-0 bg-card z-9", children: trace.name }), _jsx("div", { className: "flex w-full flex-row items-center hover:bg-muted/50 rounded-md", children: _jsx("div", { className: "relative w-full h-[32px] flex items-center", children: _jsx("div", { className: cn('h-[24px] rounded-[4px] hover:opacity-80 transition-all duration-200', {
5
5
  'bg-[repeating-linear-gradient(140deg,#BEFE29,#BEFE29_8px,#ABE625_8px,#ABE625_16px)]': trace.status === 'running',
6
6
  'bg-[repeating-linear-gradient(140deg,#2862FE,#2862FE_8px,#2358E5_8px,#2358E5_16px)]': trace.status === 'completed',
7
7
  'bg-[repeating-linear-gradient(140deg,#EA2069,#EA2069_8px,#D41E60_8px,#D41E60_16px)]': trace.status === 'failed',
@@ -11,5 +11,5 @@ export const TracesGroups = memo(({ groups, selectedGroupId, onGroupSelect }) =>
11
11
  return `${duration}ms`;
12
12
  return `${(duration / 1000).toFixed(1)}s`;
13
13
  };
14
- return (_jsx("div", { className: "overflow-auto", children: groups.length > 0 && (_jsx("div", { children: [...groups].reverse().map((group) => (_jsx("div", { "data-testid": `trace-${group.id}`, className: cn('cursor-pointer transition-colors', selectedGroupId === group.id ? 'bg-muted-foreground/10' : 'hover:bg-muted/70'), onClick: () => onGroupSelect(group), children: _jsxs("div", { className: "p-3 flex flex-col gap-1", children: [_jsxs("div", { className: "flex flex-row justify-between items-center gap-2", children: [_jsx("span", { className: "font-semibold text-lg", children: group.name }), _jsx(TraceStatusBadge, { status: group.status, duration: group.endTime ? formatDuration(group.endTime - group.startTime) : undefined })] }), _jsxs("div", { className: "text-xs text-muted-foreground space-y-1", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("div", { "data-testid": "trace-id", className: "text-xs text-muted-foreground font-mono tracking-[1px]", children: group.id }), _jsxs("span", { children: [group.metadata.totalSteps, " steps"] })] }), _jsxs("div", { className: "flex justify-between", children: [formatDistanceToNow(group.startTime), " ago"] }), group.metadata.activeSteps > 0 && (_jsxs("div", { className: "text-blue-600", children: [group.metadata.activeSteps, " active"] }))] })] }) }, group.id))) })) }));
14
+ return (_jsx("div", { className: "overflow-auto", children: groups.length > 0 && (_jsx("div", { children: [...groups].reverse().map((group) => (_jsx("div", { "data-testid": `trace-${group.id}`, className: cn('motia-trace-group cursor-pointer transition-colors', selectedGroupId === group.id ? 'bg-muted-foreground/10' : 'hover:bg-muted/70'), onClick: () => onGroupSelect(group), children: _jsxs("div", { className: "p-3 flex flex-col gap-1", children: [_jsxs("div", { className: "flex flex-row justify-between items-center gap-2", children: [_jsx("span", { className: "font-semibold text-lg", children: group.name }), _jsx(TraceStatusBadge, { status: group.status, duration: group.endTime ? formatDuration(group.endTime - group.startTime) : undefined })] }), _jsxs("div", { className: "text-xs text-muted-foreground space-y-1", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("div", { "data-testid": "trace-id", className: "text-xs text-muted-foreground font-mono tracking-[1px]", children: group.id }), _jsxs("span", { children: [group.metadata.totalSteps, " steps"] })] }), _jsxs("div", { className: "flex justify-between", children: [formatDistanceToNow(group.startTime), " ago"] }), group.metadata.activeSteps > 0 && (_jsxs("div", { className: "text-blue-600", children: [group.metadata.activeSteps, " active"] }))] })] }) }, group.id))) })) }));
15
15
  });
@@ -35,5 +35,5 @@ export const Sidebar = ({ initialWidth, onClose, ...props }) => {
35
35
  }, [sidebarId, onClose]);
36
36
  return createPortal(_jsxs("div", { ...getRootProps(), className: "pr-2 py-2 relative", children: [_jsx("div", { ...getHandleProps({
37
37
  reverse: true,
38
- }), className: "flex h-6 w-6 items-center justify-center rounded-full bg-background border border-border absolute top-1/2 -translate-y-1/2 -left-4 z-20", children: _jsx(Equal, { className: "rotate-90 w-4 h-4 text-muted-foreground" }) }), _jsx(Panel, { ...props, variant: "outlined", className: "max-h-[calc(100vh-80px)] h-full" })] }), document.querySelector(`#${APP_SIDEBAR_CONTAINER_ID}`));
38
+ }), className: "flex h-6 w-6 items-center justify-center rounded-full bg-background border border-border absolute top-1/2 -translate-y-1/2 -left-4 z-20", children: _jsx(Equal, { className: "rotate-90 w-4 h-4 text-muted-foreground" }) }), _jsx(Panel, { ...props, variant: "outlined", className: "max-h-[calc(100vh-80px)] h-full", "data-testid": "sidebar-panel" })] }), document.querySelector(`#${APP_SIDEBAR_CONTAINER_ID}`));
39
39
  };
@@ -0,0 +1,12 @@
1
+ import { TutorialStep } from './tutorial-types';
2
+ declare class Tutorial {
3
+ steps: TutorialStep[];
4
+ private onStepsRegisteredCallbacks;
5
+ private onOpenCallbacks;
6
+ register(steps: TutorialStep[]): void;
7
+ onStepsRegistered(callback: () => void): void;
8
+ onOpen(callback: () => void): void;
9
+ open(): void;
10
+ }
11
+ export declare const MotiaTutorial: Tutorial;
12
+ export {};
@@ -0,0 +1,36 @@
1
+ class Tutorial {
2
+ constructor() {
3
+ Object.defineProperty(this, "steps", {
4
+ enumerable: true,
5
+ configurable: true,
6
+ writable: true,
7
+ value: []
8
+ });
9
+ Object.defineProperty(this, "onStepsRegisteredCallbacks", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: []
14
+ });
15
+ Object.defineProperty(this, "onOpenCallbacks", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: []
20
+ });
21
+ }
22
+ register(steps) {
23
+ this.steps = steps;
24
+ this.onStepsRegisteredCallbacks.forEach((callback) => callback());
25
+ }
26
+ onStepsRegistered(callback) {
27
+ this.onStepsRegisteredCallbacks.push(callback);
28
+ }
29
+ onOpen(callback) {
30
+ this.onOpenCallbacks.push(callback);
31
+ }
32
+ open() {
33
+ this.onOpenCallbacks.forEach((callback) => callback());
34
+ }
35
+ }
36
+ export const MotiaTutorial = new Tutorial();
@@ -0,0 +1,22 @@
1
+ export type TutorialActionClick = {
2
+ type: 'click';
3
+ selector: string;
4
+ optional?: boolean;
5
+ };
6
+ export type TutorialActionEditor = {
7
+ type: 'fill-editor';
8
+ content: Record<string, any>;
9
+ };
10
+ export type TutorialAction = TutorialActionClick | TutorialActionEditor;
11
+ export type TutorialImage = {
12
+ height: number;
13
+ src: string;
14
+ };
15
+ export type TutorialStep = {
16
+ title: string;
17
+ description: React.FC<void>;
18
+ image?: TutorialImage;
19
+ link?: string;
20
+ elementXpath?: string;
21
+ before?: TutorialAction[];
22
+ };
@@ -0,0 +1,39 @@
1
+ export declare const workbenchXPath: {
2
+ sidebarContainer: string;
3
+ closePanelButton: string;
4
+ bottomPanel: string;
5
+ flows: {
6
+ feature: (featureId: string) => string;
7
+ previewButton: (stepId: string) => string;
8
+ node: (stepId: string) => string;
9
+ };
10
+ endpoints: {
11
+ endpoint: (method: string, path: string) => string;
12
+ callPanel: string;
13
+ callTab: string;
14
+ response: string;
15
+ playButton: string;
16
+ };
17
+ tracing: {
18
+ trace: (index: number) => string;
19
+ details: string;
20
+ timeline: (index: number) => string;
21
+ };
22
+ logs: {
23
+ container: string;
24
+ searchContainer: string;
25
+ traceColumn: (index: number) => string;
26
+ row: string;
27
+ };
28
+ states: {
29
+ container: string;
30
+ row: (index: number) => string;
31
+ };
32
+ links: {
33
+ flows: string;
34
+ endpoints: string;
35
+ tracing: string;
36
+ logs: string;
37
+ states: string;
38
+ };
39
+ };
@@ -0,0 +1,39 @@
1
+ export const workbenchXPath = {
2
+ sidebarContainer: '//div[@data-testid="sidebar-panel"]',
3
+ closePanelButton: '//div[@id="app-sidebar-container"]//button[@data-testid="close-panel"]',
4
+ bottomPanel: '//div[@id="bottom-panel"]',
5
+ flows: {
6
+ feature: (featureId) => `//div[@data-feature-id="${featureId}"]`,
7
+ previewButton: (stepId) => `//button[@data-testid="open-code-preview-button-${stepId}"]`,
8
+ node: (stepId) => `//div[@data-testid="node-${stepId}"]`,
9
+ },
10
+ endpoints: {
11
+ endpoint: (method, path) => `//div[@data-testid="endpoint-${method}-${path}"]`,
12
+ callPanel: '//div[@data-testid="endpoint-body-panel__call"]',
13
+ callTab: '//button[@data-testid="endpoint-call-tab"]',
14
+ response: '//div[@data-testid="endpoint-response-container"]',
15
+ playButton: '//button[@data-testid="endpoint-play-button"]',
16
+ },
17
+ tracing: {
18
+ trace: (index) => `(//div[contains(@class, 'motia-trace-group')])[${index}]`,
19
+ details: '//div[@data-testid="trace-details"]',
20
+ timeline: (index) => `(//div[@data-testid="trace-timeline-item"])[${index}]`,
21
+ },
22
+ logs: {
23
+ container: '//div[@data-testid="logs-container"]',
24
+ searchContainer: '//div[@data-testid="logs-search-container"]',
25
+ traceColumn: (index) => `(//td[starts-with(@data-testid, 'trace')])[${index}]`,
26
+ row: '//div[@data-testid="log-row"]',
27
+ },
28
+ states: {
29
+ container: '//div[@data-testid="states-container"]',
30
+ row: (index) => `(//tr[starts-with(@data-testid, 'item-')])[${index}]`,
31
+ },
32
+ links: {
33
+ flows: '//button[@data-testid="flows-link"]',
34
+ endpoints: '//button[@data-testid="endpoints-link"]',
35
+ tracing: '//button[@data-testid="traces-link"]',
36
+ logs: '//button[@data-testid="logs-link"]',
37
+ states: '//button[@data-testid="states-link"]',
38
+ },
39
+ };
@@ -0,0 +1 @@
1
+ export declare const waitForElementByXPath: (xpath: string, optional?: boolean, maxAttempts?: number, delayMs?: number) => Promise<HTMLElement | null>;
@@ -0,0 +1,17 @@
1
+ export const waitForElementByXPath = async (xpath, optional = false, maxAttempts = 50, delayMs = 50) => {
2
+ let attempts = 0;
3
+ while (attempts < maxAttempts) {
4
+ const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
5
+ const targetElement = result?.singleNodeValue;
6
+ if (targetElement) {
7
+ return targetElement;
8
+ }
9
+ else if (optional) {
10
+ return null;
11
+ }
12
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
13
+ attempts++;
14
+ }
15
+ console.warn(`Element not found after maximum attempts: ${xpath}`);
16
+ return null;
17
+ };
@@ -0,0 +1,14 @@
1
+ import { TutorialImage } from '../engine/tutorial-types';
2
+ export declare const useTutorialEngine: () => {
3
+ ref: import("react").RefObject<HTMLDivElement | null>;
4
+ highlighterRef: import("react").RefObject<HTMLDivElement | null>;
5
+ title: string;
6
+ description: import("react").ReactNode;
7
+ image: TutorialImage | undefined;
8
+ link: string | undefined;
9
+ currentStep: number;
10
+ totalSteps: number;
11
+ onClose: () => void;
12
+ moveStep: (stepNumber: number) => Promise<void>;
13
+ currentStepRef: import("react").RefObject<number>;
14
+ };
@@ -0,0 +1,162 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { waitForElementByXPath } from './tutorial-utils';
3
+ import { MotiaTutorial } from '../engine/tutorial-engine';
4
+ export const useTutorialEngine = () => {
5
+ const ref = useRef(null);
6
+ const highlighterRef = useRef(null);
7
+ const [title, setTitle] = useState('');
8
+ const [description, setDescription] = useState(undefined);
9
+ const [image, setImage] = useState(undefined);
10
+ const [link, setLink] = useState(undefined);
11
+ const [currentStep, setCurrentStep] = useState(0);
12
+ const [totalSteps, setTotalSteps] = useState(MotiaTutorial.steps.length);
13
+ const loading = useRef(false);
14
+ const currentStepRef = useRef(0);
15
+ const moveComponent = (x, y) => {
16
+ if (ref.current) {
17
+ ref.current.style.position = 'absolute';
18
+ ref.current.style.left = `${x}px`;
19
+ ref.current.style.top = `${y}px`;
20
+ }
21
+ };
22
+ const moveStep = async (stepNumber) => {
23
+ const container = ref.current;
24
+ if (container && !loading.current) {
25
+ if (stepNumber >= MotiaTutorial.steps.length) {
26
+ onClose();
27
+ return;
28
+ }
29
+ loading.current = true;
30
+ currentStepRef.current = stepNumber;
31
+ const step = MotiaTutorial.steps[stepNumber];
32
+ // Run any before actions
33
+ if (step.before) {
34
+ for (const action of step.before) {
35
+ if (action.type === 'click') {
36
+ const element = await waitForElementByXPath(action.selector, action.optional);
37
+ if (element) {
38
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
39
+ element.click();
40
+ element.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', keyCode: 13 }));
41
+ }
42
+ }
43
+ else if (action.type === 'fill-editor' && monaco) {
44
+ monaco.editor?.getEditors?.()?.[0]?.setValue(JSON.stringify(action.content, null, 2));
45
+ }
46
+ }
47
+ }
48
+ setCurrentStep(stepNumber + 1);
49
+ setTitle(step.title);
50
+ setDescription(await step.description());
51
+ setImage(step.image);
52
+ setLink(step.link);
53
+ setTimeout(async () => {
54
+ const { height, width } = container.getBoundingClientRect();
55
+ if (step.elementXpath) {
56
+ const targetElement = await waitForElementByXPath(step.elementXpath);
57
+ if (!targetElement) {
58
+ console.warn(`Element not found after maximum attempts: ${step.elementXpath}`);
59
+ loading.current = false;
60
+ return;
61
+ }
62
+ if (targetElement && highlighterRef.current) {
63
+ const { top, left, width, height } = targetElement.getBoundingClientRect();
64
+ highlighterRef.current.style.top = `${top - 0}px`;
65
+ highlighterRef.current.style.left = `${left - 0}px`;
66
+ highlighterRef.current.style.width = `${width + 0}px`;
67
+ highlighterRef.current.style.height = `${height + 0}px`;
68
+ }
69
+ // Position tutorial relative to target element
70
+ if (targetElement) {
71
+ const targetRect = targetElement.getBoundingClientRect();
72
+ const spaceBelow = window.innerHeight - targetRect.bottom;
73
+ const spaceAbove = targetRect.top;
74
+ const spaceRight = window.innerWidth - targetRect.right;
75
+ const spaceLeft = targetRect.left;
76
+ // Helper function to adjust horizontal position within viewport bounds
77
+ const adjustHorizontalPosition = (x) => {
78
+ // this is important to avoid the tutorial from overflowing at far left or right
79
+ return Math.max(20, Math.min(x, window.innerWidth - width - 20));
80
+ };
81
+ // Try to position below first
82
+ if (spaceBelow >= height + 20) {
83
+ const x = targetRect.left + targetRect.width / 2 - width / 2;
84
+ moveComponent(adjustHorizontalPosition(x), targetRect.bottom + 20);
85
+ }
86
+ // Try above if not enough space below
87
+ else if (spaceAbove >= height + 20) {
88
+ const x = targetRect.left + targetRect.width / 2 - width / 2;
89
+ moveComponent(adjustHorizontalPosition(x), targetRect.top - height - 20);
90
+ }
91
+ // Try right side
92
+ else if (spaceRight >= width + 20) {
93
+ moveComponent(targetRect.right + 20, targetRect.top + targetRect.height / 2 - height / 2);
94
+ }
95
+ // Try left side
96
+ else if (spaceLeft >= width + 20) {
97
+ moveComponent(targetRect.left - width - 20, targetRect.top + targetRect.height / 2 - height / 2);
98
+ }
99
+ }
100
+ }
101
+ else {
102
+ if (highlighterRef.current) {
103
+ highlighterRef.current.style.top = '50%';
104
+ highlighterRef.current.style.left = '50%';
105
+ highlighterRef.current.style.width = '1px';
106
+ highlighterRef.current.style.height = '1px';
107
+ }
108
+ // Fallback to center of screen
109
+ moveComponent(window.innerWidth / 2 - width / 2, window.innerHeight / 2 - height / 2);
110
+ }
111
+ }, 100);
112
+ loading.current = false;
113
+ }
114
+ };
115
+ const onClose = () => {
116
+ if (ref.current?.parentElement) {
117
+ ref.current.parentElement.style.transition = 'opacity 0.3s ease-out';
118
+ ref.current.parentElement.style.opacity = '0';
119
+ setTimeout(() => {
120
+ if (ref.current?.parentElement) {
121
+ ref.current.parentElement.style.display = 'none';
122
+ }
123
+ }, 300);
124
+ }
125
+ };
126
+ useEffect(() => {
127
+ importFile('tutorial.tsx').then((module) => {
128
+ if (Array.isArray(module.steps) && module.steps.length > 0) {
129
+ MotiaTutorial.register(module.steps);
130
+ }
131
+ });
132
+ }, []);
133
+ useEffect(() => {
134
+ const container = ref.current;
135
+ if (container?.parentElement) {
136
+ container.parentElement.style.display = 'none';
137
+ }
138
+ const onOpen = () => {
139
+ if (container?.parentElement) {
140
+ container.parentElement.style.opacity = '1';
141
+ container.parentElement.style.display = 'block';
142
+ setTotalSteps(MotiaTutorial.steps.length);
143
+ moveStep(0);
144
+ }
145
+ };
146
+ MotiaTutorial.onOpen(onOpen);
147
+ MotiaTutorial.onStepsRegistered(onOpen);
148
+ }, []);
149
+ return {
150
+ ref,
151
+ highlighterRef,
152
+ title,
153
+ description,
154
+ image,
155
+ link,
156
+ currentStep,
157
+ totalSteps,
158
+ onClose,
159
+ moveStep,
160
+ currentStepRef,
161
+ };
162
+ };
@@ -0,0 +1,5 @@
1
+ import { TutorialStep } from '../engine/tutorial-types';
2
+ export declare const useTutorial: () => {
3
+ open: () => void;
4
+ steps: TutorialStep[];
5
+ };