@motiadev/workbench 0.5.11-beta.120-742949 → 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 (47) 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 +3 -2
  6. package/dist/src/components/endpoints/endpoint-body-panel.d.ts +1 -0
  7. package/dist/src/components/endpoints/endpoint-body-panel.js +2 -2
  8. package/dist/src/components/endpoints/endpoint-call.js +1 -1
  9. package/dist/src/components/endpoints/endpoint-description.js +1 -1
  10. package/dist/src/components/flow/hooks/use-get-flow-state.js +1 -0
  11. package/dist/src/components/flow/node-organizer.js +4 -38
  12. package/dist/src/components/header/header.js +1 -1
  13. package/dist/src/components/logs/logs-page.js +1 -1
  14. package/dist/src/components/observability/trace-item/trace-item.js +1 -1
  15. package/dist/src/components/observability/traces-groups.js +1 -1
  16. package/dist/src/components/sidebar/sidebar.js +1 -1
  17. package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +12 -0
  18. package/dist/src/components/tutorial/engine/tutorial-engine.js +36 -0
  19. package/dist/src/components/tutorial/engine/tutorial-types.d.ts +22 -0
  20. package/dist/src/components/tutorial/engine/tutorial-types.js +1 -0
  21. package/dist/src/components/tutorial/engine/workbench-xpath.d.ts +39 -0
  22. package/dist/src/components/tutorial/engine/workbench-xpath.js +39 -0
  23. package/dist/src/components/tutorial/hooks/tutorial-utils.d.ts +1 -0
  24. package/dist/src/components/tutorial/hooks/tutorial-utils.js +17 -0
  25. package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +14 -0
  26. package/dist/src/components/tutorial/hooks/use-tutorial-engine.js +162 -0
  27. package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +5 -0
  28. package/dist/src/components/tutorial/hooks/use-tutorial.js +10 -0
  29. package/dist/src/components/tutorial/tutorial-button.js +20 -0
  30. package/dist/src/components/tutorial/tutorial-step.d.ts +14 -0
  31. package/dist/src/components/tutorial/tutorial-step.js +18 -0
  32. package/dist/src/components/tutorial/tutorial.d.ts +2 -0
  33. package/dist/src/components/tutorial/tutorial.js +8 -0
  34. package/dist/src/publicComponents/base-node/base-node.js +3 -1
  35. package/dist/src/publicComponents/base-node/code-display.d.ts +9 -0
  36. package/dist/src/publicComponents/base-node/code-display.js +64 -0
  37. package/dist/src/publicComponents/base-node/feature-card.d.ts +9 -0
  38. package/dist/src/publicComponents/base-node/feature-card.js +5 -0
  39. package/dist/src/publicComponents/base-node/node-sidebar.d.ts +2 -0
  40. package/dist/src/publicComponents/base-node/node-sidebar.js +4 -5
  41. package/dist/src/types/file.d.ts +7 -0
  42. package/dist/src/types/file.js +1 -0
  43. package/dist/tsconfig.app.tsbuildinfo +1 -1
  44. package/dist/tsconfig.node.tsbuildinfo +1 -1
  45. package/package.json +13 -12
  46. package/dist/src/components/ui/tutorial-button.js +0 -69
  47. /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,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { analytics } from '@/lib/analytics';
2
3
  import { CollapsiblePanel, CollapsiblePanelGroup, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui';
3
4
  import { ReactFlowProvider } from '@xyflow/react';
4
- import { analytics } from '@/lib/analytics';
5
5
  import { File, GanttChart, Link2, LogsIcon } from 'lucide-react';
6
6
  import { useCallback, useMemo } from 'react';
7
7
  import { EndpointsPage } from './components/endpoints/endpoints-page';
@@ -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) {
@@ -30,5 +31,5 @@ export const App = () => {
30
31
  analytics.track(`${location} tab changed`, { [`new.${location}`]: newTab, tab });
31
32
  tabChangeCallbacks[location](newTab);
32
33
  }, [tabChangeCallbacks, tab]);
33
- 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, {})] }));
34
35
  };
@@ -6,6 +6,7 @@ type Props = {
6
6
  endpoint: ApiEndpoint;
7
7
  onChange?: (body: string) => void;
8
8
  onValidate?: (isValid: boolean) => void;
9
+ panelName: string;
9
10
  };
10
11
  export declare const EndpointBodyPanel: FC<Props>;
11
12
  export {};
@@ -6,7 +6,7 @@ import ReactJson from 'react18-json-view';
6
6
  import 'react18-json-view/src/dark.css';
7
7
  import 'react18-json-view/src/style.css';
8
8
  import { convertJsonSchemaToJson } from './hooks/utils';
9
- export const EndpointBodyPanel = ({ endpoint, onChange, onValidate }) => {
9
+ export const EndpointBodyPanel = ({ endpoint, onChange, onValidate, panelName }) => {
10
10
  const shouldHaveBody = ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
11
11
  const { body, setBody } = useJsonSchemaToJson(endpoint.bodySchema);
12
12
  const handleBodyChange = (body) => {
@@ -16,5 +16,5 @@ export const EndpointBodyPanel = ({ endpoint, onChange, onValidate }) => {
16
16
  if (!shouldHaveBody) {
17
17
  return null;
18
18
  }
19
- return (_jsx(Panel, { title: "Body", size: "sm", contentClassName: "p-0", "data-testid": "endpoint-body-panel", children: onChange ? (_jsx(JsonEditor, { value: body, schema: endpoint.bodySchema, onChange: handleBodyChange, onValidate: onValidate })) : (_jsx(ReactJson, { src: convertJsonSchemaToJson(endpoint.bodySchema), theme: "default", enableClipboard: false, style: { backgroundColor: 'transparent' } })) }));
19
+ return (_jsx(Panel, { title: "Body", size: "sm", contentClassName: "p-0", "data-testid": `endpoint-body-panel__${panelName}`, children: onChange ? (_jsx(JsonEditor, { value: body, schema: endpoint.bodySchema, onChange: handleBodyChange, onValidate: onValidate })) : (_jsx(ReactJson, { src: convertJsonSchemaToJson(endpoint.bodySchema), theme: "default", enableClipboard: false, style: { backgroundColor: 'transparent' } })) }));
20
20
  };
@@ -53,5 +53,5 @@ export const EndpointCall = ({ endpoint }) => {
53
53
  setExecutionTime(executionTime);
54
54
  setIsRequestLoading(false);
55
55
  };
56
- return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint, onChange: setPathParamsValues }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint, onChange: setQueryParamsValues }), _jsx(EndpointBodyPanel, { endpoint: endpoint, onChange: setBody, onValidate: setIsBodyValid }), _jsxs(Button, { className: "w-fit", onClick: handleRequest, variant: "accent", "data-testid": "endpoint-play-button", disabled: isRequestLoading || !isPlayEnabled, children: [isRequestLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(Play, {}), " Play"] }), _jsx(EndpointResponse, { responseCode: responseCode, responseBody: responseBody, executionTime: executionTime })] }));
56
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint, onChange: setPathParamsValues }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint, onChange: setQueryParamsValues }), _jsx(EndpointBodyPanel, { endpoint: endpoint, onChange: setBody, onValidate: setIsBodyValid, panelName: "call" }), _jsxs(Button, { className: "w-fit", onClick: handleRequest, variant: "accent", "data-testid": "endpoint-play-button", disabled: isRequestLoading || !isPlayEnabled, children: [isRequestLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(Play, {}), " Play"] }), _jsx(EndpointResponse, { responseCode: responseCode, responseBody: responseBody, executionTime: executionTime })] }));
57
57
  };
@@ -4,7 +4,7 @@ import { EndpointPathParamsPanel } from './endpoint-path-params-panel';
4
4
  import { EndpointQueryParamsPanel } from './endpoint-query-params-panel';
5
5
  import { EndpointBodyPanel } from './endpoint-body-panel';
6
6
  export const EndpointDescription = ({ endpoint }) => {
7
- return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint }), _jsx(EndpointBodyPanel, { endpoint: endpoint }), _jsx(EndpointResponseSchema, { items: Object.entries(endpoint?.responseSchema ?? {}).map(([status, schema]) => ({
7
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint }), _jsx(EndpointBodyPanel, { endpoint: endpoint, panelName: "details" }), _jsx(EndpointResponseSchema, { items: Object.entries(endpoint?.responseSchema ?? {}).map(([status, schema]) => ({
8
8
  responseCode: status,
9
9
  bodySchema: schema,
10
10
  })) })] }));
@@ -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
+ };