@motiadev/workbench 0.14.0-beta.165-285707 → 0.15.0-beta.165

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 (236) hide show
  1. package/dist/index.d.ts +189 -10
  2. package/dist/index.html +1 -1
  3. package/dist/index.js +1065 -7
  4. package/dist/middleware.d.ts +66 -8
  5. package/dist/middleware.js +694 -86
  6. package/dist/motia-plugin/__tests__/generator.test.ts +129 -0
  7. package/dist/motia-plugin/__tests__/resolver.test.ts +82 -0
  8. package/dist/motia-plugin/__tests__/validator.test.ts +71 -0
  9. package/dist/motia-plugin/{generator.js → generator.ts} +37 -35
  10. package/dist/motia-plugin/hmr.ts +123 -0
  11. package/dist/motia-plugin/index.ts +183 -0
  12. package/dist/motia-plugin/{resolver.d.ts → resolver.ts} +38 -5
  13. package/dist/motia-plugin/types.ts +198 -0
  14. package/dist/motia-plugin/{utils.d.ts → utils.ts} +17 -4
  15. package/dist/motia-plugin/validator.ts +197 -0
  16. package/dist/src/App.tsx +41 -0
  17. package/dist/src/components/NotFoundPage.tsx +11 -0
  18. package/dist/src/components/bottom-panel.tsx +39 -0
  19. package/dist/src/components/flow/base-edge.tsx +61 -0
  20. package/dist/src/components/flow/flow-loader.tsx +3 -0
  21. package/dist/src/components/flow/flow-page.tsx +75 -0
  22. package/dist/src/components/flow/flow-tab-menu-item.tsx +52 -0
  23. package/dist/src/components/flow/flow-view.tsx +66 -0
  24. package/dist/src/components/flow/hooks/use-get-flow-state.tsx +171 -0
  25. package/dist/src/components/flow/hooks/use-save-workflow-config.ts +25 -0
  26. package/dist/src/components/flow/node-organizer.tsx +103 -0
  27. package/dist/src/components/flow/nodes/api-flow-node.tsx +6 -0
  28. package/dist/src/components/flow/nodes/cron-flow-node.tsx +6 -0
  29. package/dist/src/components/flow/nodes/event-flow-node.tsx +6 -0
  30. package/dist/src/components/flow/nodes/noop-flow-node.tsx +6 -0
  31. package/dist/src/components/header/deploy-button.tsx +110 -0
  32. package/dist/src/components/header/header.tsx +39 -0
  33. package/dist/src/components/root-motia.tsx +10 -0
  34. package/dist/src/components/top-panel.tsx +40 -0
  35. package/dist/src/components/tutorial/engine/tutorial-engine.ts +26 -0
  36. package/dist/src/components/tutorial/engine/tutorial-types.ts +26 -0
  37. package/dist/src/components/tutorial/engine/workbench-xpath.ts +53 -0
  38. package/dist/src/components/tutorial/hooks/tutorial-utils.ts +26 -0
  39. package/dist/src/components/tutorial/hooks/use-tutorial-engine.ts +213 -0
  40. package/dist/src/components/tutorial/hooks/use-tutorial.ts +14 -0
  41. package/dist/src/components/tutorial/tutorial-button.tsx +46 -0
  42. package/dist/src/components/tutorial/tutorial-step.tsx +82 -0
  43. package/dist/src/components/tutorial/tutorial.tsx +59 -0
  44. package/dist/src/components/ui/json-editor.tsx +68 -0
  45. package/dist/src/components/ui/table.tsx +75 -0
  46. package/dist/src/components/ui/theme-toggle.tsx +54 -0
  47. package/dist/src/components/ui/tooltip.tsx +26 -0
  48. package/dist/src/hooks/use-debounced.ts +22 -0
  49. package/dist/src/hooks/use-fetch-flows.ts +33 -0
  50. package/dist/src/hooks/use-mobile.ts +19 -0
  51. package/dist/src/hooks/use-update-handle-positions.ts +42 -0
  52. package/dist/src/index.css +5 -5
  53. package/dist/src/lib/__tests__/utils.test.ts +110 -0
  54. package/dist/src/lib/motia-analytics.ts +140 -0
  55. package/dist/src/lib/plugins.tsx +132 -0
  56. package/dist/src/lib/utils.ts +37 -0
  57. package/dist/src/main.tsx +30 -0
  58. package/dist/src/project-view-mode.tsx +32 -0
  59. package/dist/src/publicComponents/api-node.tsx +26 -0
  60. package/dist/src/publicComponents/base-node/base-handle.tsx +50 -0
  61. package/dist/src/publicComponents/base-node/base-node.tsx +114 -0
  62. package/dist/src/publicComponents/base-node/code-display.tsx +119 -0
  63. package/dist/src/publicComponents/base-node/emits.tsx +17 -0
  64. package/dist/src/publicComponents/base-node/feature-card.tsx +32 -0
  65. package/dist/src/publicComponents/base-node/language-indicator.tsx +131 -0
  66. package/dist/src/publicComponents/base-node/node-header.tsx +49 -0
  67. package/dist/src/publicComponents/base-node/node-sidebar.tsx +41 -0
  68. package/dist/src/publicComponents/base-node/subscribe.tsx +13 -0
  69. package/dist/src/publicComponents/cron-node.tsx +24 -0
  70. package/dist/src/publicComponents/event-node.tsx +20 -0
  71. package/dist/src/publicComponents/node-props.tsx +15 -0
  72. package/dist/src/publicComponents/noop-node.tsx +19 -0
  73. package/dist/src/setupTests.ts +1 -0
  74. package/dist/src/stores/use-app-tabs-store.ts +49 -0
  75. package/dist/src/stores/use-flow-store.ts +31 -0
  76. package/dist/src/stores/use-global-store.ts +24 -0
  77. package/dist/src/stores/use-motia-config-store.ts +36 -0
  78. package/dist/src/stores/use-tabs-store.ts +34 -0
  79. package/dist/src/system-view-mode.tsx +28 -0
  80. package/dist/src/types/endpoint.ts +12 -0
  81. package/dist/src/types/file.ts +7 -0
  82. package/dist/src/types/flow.ts +103 -0
  83. package/eslint.config.cjs +22 -0
  84. package/jest.config.cjs +68 -0
  85. package/package.json +53 -51
  86. package/dist/motia-plugin/__tests__/generator.test.d.ts +0 -1
  87. package/dist/motia-plugin/__tests__/generator.test.js +0 -97
  88. package/dist/motia-plugin/__tests__/resolver.test.d.ts +0 -1
  89. package/dist/motia-plugin/__tests__/resolver.test.js +0 -64
  90. package/dist/motia-plugin/__tests__/validator.test.d.ts +0 -1
  91. package/dist/motia-plugin/__tests__/validator.test.js +0 -59
  92. package/dist/motia-plugin/generator.d.ts +0 -78
  93. package/dist/motia-plugin/hmr.d.ts +0 -22
  94. package/dist/motia-plugin/hmr.js +0 -100
  95. package/dist/motia-plugin/index.d.ts +0 -3
  96. package/dist/motia-plugin/index.js +0 -153
  97. package/dist/motia-plugin/resolver.js +0 -92
  98. package/dist/motia-plugin/types.d.ts +0 -169
  99. package/dist/motia-plugin/types.js +0 -36
  100. package/dist/motia-plugin/utils.js +0 -75
  101. package/dist/motia-plugin/validator.d.ts +0 -19
  102. package/dist/motia-plugin/validator.js +0 -163
  103. package/dist/src/App.d.ts +0 -2
  104. package/dist/src/App.js +0 -35
  105. package/dist/src/components/NotFoundPage.d.ts +0 -1
  106. package/dist/src/components/NotFoundPage.js +0 -3
  107. package/dist/src/components/bottom-panel.d.ts +0 -1
  108. package/dist/src/components/bottom-panel.js +0 -15
  109. package/dist/src/components/flow/base-edge.d.ts +0 -3
  110. package/dist/src/components/flow/base-edge.js +0 -39
  111. package/dist/src/components/flow/flow-loader.d.ts +0 -1
  112. package/dist/src/components/flow/flow-loader.js +0 -4
  113. package/dist/src/components/flow/flow-page.d.ts +0 -1
  114. package/dist/src/components/flow/flow-page.js +0 -25
  115. package/dist/src/components/flow/flow-tab-menu-item.d.ts +0 -1
  116. package/dist/src/components/flow/flow-tab-menu-item.js +0 -18
  117. package/dist/src/components/flow/flow-view.d.ts +0 -12
  118. package/dist/src/components/flow/flow-view.js +0 -22
  119. package/dist/src/components/flow/hooks/use-get-flow-state.d.ts +0 -10
  120. package/dist/src/components/flow/hooks/use-get-flow-state.js +0 -133
  121. package/dist/src/components/flow/hooks/use-save-workflow-config.d.ts +0 -2
  122. package/dist/src/components/flow/hooks/use-save-workflow-config.js +0 -22
  123. package/dist/src/components/flow/node-organizer.d.ts +0 -10
  124. package/dist/src/components/flow/node-organizer.js +0 -82
  125. package/dist/src/components/flow/nodes/api-flow-node.d.ts +0 -2
  126. package/dist/src/components/flow/nodes/api-flow-node.js +0 -5
  127. package/dist/src/components/flow/nodes/cron-flow-node.d.ts +0 -2
  128. package/dist/src/components/flow/nodes/cron-flow-node.js +0 -5
  129. package/dist/src/components/flow/nodes/event-flow-node.d.ts +0 -2
  130. package/dist/src/components/flow/nodes/event-flow-node.js +0 -5
  131. package/dist/src/components/flow/nodes/noop-flow-node.d.ts +0 -2
  132. package/dist/src/components/flow/nodes/noop-flow-node.js +0 -5
  133. package/dist/src/components/header/deploy-button.d.ts +0 -1
  134. package/dist/src/components/header/deploy-button.js +0 -28
  135. package/dist/src/components/header/header.d.ts +0 -2
  136. package/dist/src/components/header/header.js +0 -23
  137. package/dist/src/components/root-motia.d.ts +0 -2
  138. package/dist/src/components/root-motia.js +0 -7
  139. package/dist/src/components/top-panel.d.ts +0 -1
  140. package/dist/src/components/top-panel.js +0 -15
  141. package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +0 -12
  142. package/dist/src/components/tutorial/engine/tutorial-engine.js +0 -36
  143. package/dist/src/components/tutorial/engine/tutorial-types.d.ts +0 -22
  144. package/dist/src/components/tutorial/engine/tutorial-types.js +0 -1
  145. package/dist/src/components/tutorial/engine/workbench-xpath.d.ts +0 -45
  146. package/dist/src/components/tutorial/engine/workbench-xpath.js +0 -45
  147. package/dist/src/components/tutorial/hooks/tutorial-utils.d.ts +0 -1
  148. package/dist/src/components/tutorial/hooks/tutorial-utils.js +0 -17
  149. package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +0 -15
  150. package/dist/src/components/tutorial/hooks/use-tutorial-engine.js +0 -183
  151. package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +0 -5
  152. package/dist/src/components/tutorial/hooks/use-tutorial.js +0 -10
  153. package/dist/src/components/tutorial/tutorial-button.d.ts +0 -2
  154. package/dist/src/components/tutorial/tutorial-button.js +0 -21
  155. package/dist/src/components/tutorial/tutorial-step.d.ts +0 -14
  156. package/dist/src/components/tutorial/tutorial-step.js +0 -19
  157. package/dist/src/components/tutorial/tutorial.d.ts +0 -2
  158. package/dist/src/components/tutorial/tutorial.js +0 -32
  159. package/dist/src/components/ui/json-editor.d.ts +0 -12
  160. package/dist/src/components/ui/json-editor.js +0 -35
  161. package/dist/src/components/ui/table.d.ts +0 -10
  162. package/dist/src/components/ui/table.js +0 -20
  163. package/dist/src/components/ui/theme-toggle.d.ts +0 -2
  164. package/dist/src/components/ui/theme-toggle.js +0 -19
  165. package/dist/src/components/ui/tooltip.d.ts +0 -6
  166. package/dist/src/components/ui/tooltip.js +0 -3
  167. package/dist/src/hooks/use-debounced.d.ts +0 -1
  168. package/dist/src/hooks/use-debounced.js +0 -18
  169. package/dist/src/hooks/use-fetch-flows.d.ts +0 -1
  170. package/dist/src/hooks/use-fetch-flows.js +0 -26
  171. package/dist/src/hooks/use-mobile.d.ts +0 -1
  172. package/dist/src/hooks/use-mobile.js +0 -15
  173. package/dist/src/hooks/use-update-handle-positions.d.ts +0 -10
  174. package/dist/src/hooks/use-update-handle-positions.js +0 -35
  175. package/dist/src/lib/__tests__/utils.test.d.ts +0 -1
  176. package/dist/src/lib/__tests__/utils.test.js +0 -94
  177. package/dist/src/lib/motia-analytics.d.ts +0 -38
  178. package/dist/src/lib/motia-analytics.js +0 -132
  179. package/dist/src/lib/plugins.d.ts +0 -2
  180. package/dist/src/lib/plugins.js +0 -105
  181. package/dist/src/lib/utils.d.ts +0 -7
  182. package/dist/src/lib/utils.js +0 -34
  183. package/dist/src/main.d.ts +0 -2
  184. package/dist/src/main.js +0 -17
  185. package/dist/src/project-view-mode.d.ts +0 -1
  186. package/dist/src/project-view-mode.js +0 -20
  187. package/dist/src/publicComponents/api-node.d.ts +0 -5
  188. package/dist/src/publicComponents/api-node.js +0 -5
  189. package/dist/src/publicComponents/base-node/base-handle.d.ts +0 -9
  190. package/dist/src/publicComponents/base-node/base-handle.js +0 -8
  191. package/dist/src/publicComponents/base-node/base-node.d.ts +0 -15
  192. package/dist/src/publicComponents/base-node/base-node.js +0 -30
  193. package/dist/src/publicComponents/base-node/code-display.d.ts +0 -9
  194. package/dist/src/publicComponents/base-node/code-display.js +0 -64
  195. package/dist/src/publicComponents/base-node/emits.d.ts +0 -5
  196. package/dist/src/publicComponents/base-node/emits.js +0 -5
  197. package/dist/src/publicComponents/base-node/feature-card.d.ts +0 -10
  198. package/dist/src/publicComponents/base-node/feature-card.js +0 -5
  199. package/dist/src/publicComponents/base-node/language-indicator.d.ts +0 -10
  200. package/dist/src/publicComponents/base-node/language-indicator.js +0 -29
  201. package/dist/src/publicComponents/base-node/node-header.d.ts +0 -13
  202. package/dist/src/publicComponents/base-node/node-header.js +0 -30
  203. package/dist/src/publicComponents/base-node/node-sidebar.d.ts +0 -14
  204. package/dist/src/publicComponents/base-node/node-sidebar.js +0 -9
  205. package/dist/src/publicComponents/base-node/subscribe.d.ts +0 -4
  206. package/dist/src/publicComponents/base-node/subscribe.js +0 -4
  207. package/dist/src/publicComponents/cron-node.d.ts +0 -4
  208. package/dist/src/publicComponents/cron-node.js +0 -6
  209. package/dist/src/publicComponents/event-node.d.ts +0 -4
  210. package/dist/src/publicComponents/event-node.js +0 -5
  211. package/dist/src/publicComponents/node-props.d.ts +0 -21
  212. package/dist/src/publicComponents/node-props.js +0 -1
  213. package/dist/src/publicComponents/noop-node.d.ts +0 -4
  214. package/dist/src/publicComponents/noop-node.js +0 -5
  215. package/dist/src/setupTests.d.ts +0 -1
  216. package/dist/src/setupTests.js +0 -1
  217. package/dist/src/stores/use-app-tabs-store.d.ts +0 -16
  218. package/dist/src/stores/use-app-tabs-store.js +0 -31
  219. package/dist/src/stores/use-flow-store.d.ts +0 -21
  220. package/dist/src/stores/use-flow-store.js +0 -16
  221. package/dist/src/stores/use-global-store.d.ts +0 -18
  222. package/dist/src/stores/use-global-store.js +0 -12
  223. package/dist/src/stores/use-motia-config-store.d.ts +0 -12
  224. package/dist/src/stores/use-motia-config-store.js +0 -24
  225. package/dist/src/stores/use-tabs-store.d.ts +0 -19
  226. package/dist/src/stores/use-tabs-store.js +0 -22
  227. package/dist/src/system-view-mode.d.ts +0 -1
  228. package/dist/src/system-view-mode.js +0 -10
  229. package/dist/src/types/endpoint.d.ts +0 -14
  230. package/dist/src/types/endpoint.js +0 -1
  231. package/dist/src/types/file.d.ts +0 -7
  232. package/dist/src/types/file.js +0 -1
  233. package/dist/src/types/flow.d.ts +0 -115
  234. package/dist/src/types/flow.js +0 -1
  235. package/dist/tsconfig.app.tsbuildinfo +0 -1
  236. package/dist/tsconfig.node.tsbuildinfo +0 -1
@@ -0,0 +1,6 @@
1
+ import type { NoopNodeProps } from '../../../publicComponents/node-props'
2
+ import { NoopNode } from '../../../publicComponents/noop-node'
3
+
4
+ export const NoopFlowNode = ({ data }: NoopNodeProps) => {
5
+ return <NoopNode data={data} />
6
+ }
@@ -0,0 +1,110 @@
1
+ import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@motiadev/ui'
2
+ import { Rocket } from 'lucide-react'
3
+ import { useState } from 'react'
4
+ import { motiaAnalytics } from '../../lib/motia-analytics'
5
+
6
+ export const DeployButton = () => {
7
+ const [isOpen, setIsOpen] = useState(false)
8
+
9
+ const onDeployButtonClick = () => {
10
+ motiaAnalytics.track('deploy_button_clicked')
11
+ }
12
+
13
+ const onDeployClick = () => {
14
+ setIsOpen(false)
15
+ motiaAnalytics.track('deploy_button_deploy_clicked')
16
+ }
17
+
18
+ const onClose = () => {
19
+ setIsOpen(false)
20
+ motiaAnalytics.track('deploy_button_closed')
21
+ }
22
+
23
+ const onMotiaCloudClick = () => {
24
+ setIsOpen(true)
25
+ motiaAnalytics.track('deploy_button_motia_cloud_clicked')
26
+ }
27
+
28
+ const onSelfHostedClick = () => {
29
+ motiaAnalytics.track('deploy_button_self_hosted_clicked')
30
+ window.open('https://www.motia.dev/docs/deployment-guide/self-hosted', '_blank')
31
+ }
32
+
33
+ return (
34
+ <>
35
+ {isOpen && (
36
+ <div>
37
+ {/* backdrop container */}
38
+ <div className="fixed inset-0 z-[9999] bg-black/20 backdrop-blur-sm" onClick={() => setIsOpen(false)} />
39
+
40
+ <div className="driver-popover w-[600px]! fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[10000] animate-in fade-in-0 zoom-in-95">
41
+ <img
42
+ src="https://oxhhfuuoqzsaqthfairn.supabase.co/storage/v1/object/public/public-images/preview.png"
43
+ alt="Motia Cloud"
44
+ className="driver-popover-image object-cover"
45
+ style={{ height: 393, width: '100%' }}
46
+ />
47
+
48
+ <div className="driver-popover-title">
49
+ <h2 className="popover-title">Motia Cloud is Live!</h2>
50
+ </div>
51
+
52
+ <div className="driver-popover-description">
53
+ Deploy to production in minutes, not hours. One click gets your Motia project live with enterprise-grade
54
+ reliability. Seamlessly scale, rollback instantly, and monitor everything in real-time. Your code deserves
55
+ infrastructure that just works.
56
+ </div>
57
+
58
+ <a
59
+ href="https://www.motia.dev/docs/deployment-guide/motia-cloud/features"
60
+ target="_blank"
61
+ className="text-foreground text-xs font-semibold px-4 hover:underline"
62
+ rel="noopener"
63
+ >
64
+ Learn more about Motia Cloud
65
+ </a>
66
+
67
+ <div className="driver-popover-footer flex items-center justify-end">
68
+ <div className="driver-popover-navigation-btns flex gap-6">
69
+ <button
70
+ className="tutorial-opt-out-button text-sm! font-semibold! text-muted-foreground!"
71
+ onClick={onClose}
72
+ >
73
+ Close
74
+ </button>
75
+ <a
76
+ href="https://motia.cloud?utm_source=workbench&utm_medium=referral"
77
+ target="_blank"
78
+ onClick={onDeployClick}
79
+ rel="noopener"
80
+ >
81
+ <button className="driver-popover-next-btn">Deploy!</button>
82
+ </a>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ )}
88
+ <DropdownMenu>
89
+ <DropdownMenuTrigger asChild>
90
+ <Button
91
+ variant="ghost"
92
+ className="font-semibold text-sm dark:bg-white dark:text-black dark:hover:bg-white/90 bg-black/90 hover:bg-black/80 text-white"
93
+ onClick={onDeployButtonClick}
94
+ >
95
+ <Rocket />
96
+ Deploy
97
+ </Button>
98
+ </DropdownMenuTrigger>
99
+ <DropdownMenuContent className="bg-background text-foreground w-56">
100
+ <DropdownMenuItem className="cursor-pointer h-10 font-semibold" onClick={onMotiaCloudClick}>
101
+ Motia Cloud
102
+ </DropdownMenuItem>
103
+ <DropdownMenuItem className="cursor-pointer h-10 font-semibold" onClick={onSelfHostedClick}>
104
+ Self-Hosted (Docker)
105
+ </DropdownMenuItem>
106
+ </DropdownMenuContent>
107
+ </DropdownMenu>
108
+ </>
109
+ )
110
+ }
@@ -0,0 +1,39 @@
1
+ import { useThemeStore } from '@motiadev/ui'
2
+ import type React from 'react'
3
+ import { memo, useEffect } from 'react'
4
+ // @ts-expect-error
5
+ import motiaLogoDark from '../../assets/motia-dark.png'
6
+ // @ts-expect-error
7
+ import motiaLogoLight from '../../assets/motia-light.png'
8
+ import { useMotiaConfigStore } from '../../stores/use-motia-config-store'
9
+ import { Tutorial } from '../tutorial/tutorial'
10
+ import { TutorialButton } from '../tutorial/tutorial-button'
11
+ import { ThemeToggle } from '../ui/theme-toggle'
12
+ import { DeployButton } from './deploy-button'
13
+
14
+ export const Header: React.FC = memo(() => {
15
+ const theme = useThemeStore((state) => state.theme)
16
+ const logo = theme === 'light' ? motiaLogoLight : motiaLogoDark
17
+
18
+ const config = useMotiaConfigStore((state) => state.config)
19
+ const fetchConfig = useMotiaConfigStore((state) => state.fetchConfig)
20
+
21
+ useEffect(() => {
22
+ fetchConfig()
23
+ }, [])
24
+
25
+ const isDevMode = config?.isDev ?? false
26
+ const isTutorialDisabled = config?.isTutorialDisabled ?? true
27
+
28
+ return (
29
+ <header className="min-h-16 px-4 gap-4 flex items-center bg-default text-default-foreground border-b">
30
+ <img src={logo} className="h-5" id="logo-icon" data-testid="logo-icon" />
31
+ <div className="flex-1" />
32
+ <ThemeToggle />
33
+ {isDevMode && !isTutorialDisabled && <TutorialButton />}
34
+ {isDevMode && <DeployButton />}
35
+ {!isTutorialDisabled && <Tutorial />}
36
+ </header>
37
+ )
38
+ })
39
+ Header.displayName = 'Header'
@@ -0,0 +1,10 @@
1
+ import type { PropsWithChildren } from 'react'
2
+ import { memo } from 'react'
3
+ import { useAnalytics } from '../lib/motia-analytics'
4
+
5
+ export const RootMotia: React.FC<PropsWithChildren> = memo(({ children }) => {
6
+ useAnalytics()
7
+
8
+ return children
9
+ })
10
+ RootMotia.displayName = 'RootMotia'
@@ -0,0 +1,40 @@
1
+ import { CollapsiblePanel, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui'
2
+ import { memo } from 'react'
3
+ import { useShallow } from 'zustand/react/shallow'
4
+ import { type AppTabsState, TabLocation, useAppTabsStore } from '../stores/use-app-tabs-store'
5
+ import { useTabsStore } from '../stores/use-tabs-store'
6
+
7
+ const topTabsSelector = (state: AppTabsState) => state.tabs[TabLocation.TOP]
8
+ const topPanelId = 'top-panel'
9
+
10
+ export const TopPanel = memo(() => {
11
+ const defaultTab = useTabsStore((state) => state.tab.top)
12
+ const setTopTab = useTabsStore((state) => state.setTopTab)
13
+ const tabs = useAppTabsStore(useShallow(topTabsSelector))
14
+
15
+ return (
16
+ <CollapsiblePanel
17
+ id={topPanelId}
18
+ variant={'tabs'}
19
+ defaultTab={defaultTab}
20
+ onTabChange={setTopTab}
21
+ withResizeHandle
22
+ header={
23
+ <TabsList>
24
+ {tabs.map(({ id, tabLabel: Label }) => (
25
+ <TabsTrigger key={id} value={id} data-testid={`${id.toLowerCase()}-link`} className="cursor-pointer">
26
+ <Label />
27
+ </TabsTrigger>
28
+ ))}
29
+ </TabsList>
30
+ }
31
+ >
32
+ {tabs.map(({ id, content: Element }) => (
33
+ <TabsContent key={id} value={id} className="h-full">
34
+ <Element />
35
+ </TabsContent>
36
+ ))}
37
+ </CollapsiblePanel>
38
+ )
39
+ })
40
+ TopPanel.displayName = 'TopPanel'
@@ -0,0 +1,26 @@
1
+ import type { TutorialStep } from './tutorial-types'
2
+
3
+ class Tutorial {
4
+ public steps: TutorialStep[] = []
5
+ private onStepsRegisteredCallbacks: (() => void)[] = []
6
+ private onOpenCallbacks: (() => void)[] = []
7
+
8
+ register(steps: TutorialStep[]) {
9
+ this.steps = steps
10
+ this.onStepsRegisteredCallbacks.forEach((callback) => callback())
11
+ }
12
+
13
+ onStepsRegistered(callback: () => void) {
14
+ this.onStepsRegisteredCallbacks.push(callback)
15
+ }
16
+
17
+ onOpen(callback: () => void) {
18
+ this.onOpenCallbacks.push(callback)
19
+ }
20
+
21
+ open() {
22
+ this.onOpenCallbacks.forEach((callback) => callback())
23
+ }
24
+ }
25
+
26
+ export const MotiaTutorial = new Tutorial()
@@ -0,0 +1,26 @@
1
+ export type TutorialActionClick = {
2
+ type: 'click'
3
+ selector: string
4
+ optional?: boolean
5
+ }
6
+
7
+ export type TutorialActionEditor = {
8
+ type: 'fill-editor'
9
+ content: Record<string, any>
10
+ }
11
+
12
+ export type TutorialAction = TutorialActionClick | TutorialActionEditor
13
+
14
+ export type TutorialImage = {
15
+ height: number
16
+ src: string
17
+ }
18
+
19
+ export type TutorialStep = {
20
+ title: string
21
+ description: React.FC<void>
22
+ image?: TutorialImage
23
+ link?: string
24
+ elementXpath?: string
25
+ before?: TutorialAction[]
26
+ }
@@ -0,0 +1,53 @@
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
+
6
+ flows: {
7
+ dropdownFlow: (flowId: string) => `//div[@data-testid="dropdown-${flowId}"]`,
8
+ feature: (featureId: string) => `//div[@data-feature-id="${featureId}"]`,
9
+ previewButton: (stepId: string) => `//button[@data-testid="open-code-preview-button-${stepId}"]`,
10
+ node: (stepId: string) => `//div[@data-testid="node-${stepId}"]`,
11
+ },
12
+
13
+ endpoints: {
14
+ endpointsList: '//div[@data-testid="endpoints-list"]',
15
+ endpoint: (method: string, path: string) => `//div[@data-testid="endpoint-${method}-${path}"]`,
16
+ callPanel: '//div[@data-testid="endpoint-details-panel"]',
17
+ specButton: '//button[@data-testid="endpoint-spec-button"]',
18
+
19
+ bodyTab: '//button[@data-testid="endpoint-body-tab"]',
20
+ headersTab: '//button[@data-testid="endpoint-headers-tab"]',
21
+ paramsTab: '//button[@data-testid="endpoint-params-tab"]',
22
+
23
+ callTab: '//button[@data-testid="endpoint-body-tab"]', // deprecated
24
+ response: '//div[@data-testid="endpoint-response-container"]',
25
+ playButton: '//button[@data-testid="endpoint-play-button"]',
26
+ },
27
+
28
+ tracing: {
29
+ trace: (index: number) => `(//button[contains(@class, 'motia-trace-group')])[${index}]`,
30
+ details: '//div[@data-testid="trace-details"]',
31
+ timeline: (index: number) => `(//div[@data-testid="trace-timeline-item"])[${index}]`,
32
+ },
33
+
34
+ logs: {
35
+ container: '//div[@data-testid="logs-container"]',
36
+ searchContainer: '//div[@data-testid="logs-search-container"]',
37
+ traceColumn: (index: number) => `(//td[starts-with(@data-testid, 'trace')])[${index}]`,
38
+ row: '//div[@data-testid="log-row"]',
39
+ },
40
+
41
+ states: {
42
+ container: '//div[@data-testid="states-container"]',
43
+ row: (index: number) => `(//tr[starts-with(@data-testid, 'item-')])[${index}]`,
44
+ },
45
+
46
+ links: {
47
+ flows: '//div[@data-testid="flows-dropdown-trigger"]',
48
+ endpoints: '//button[@data-testid="endpoints-link"]',
49
+ tracing: '//button[@data-testid="tracing-link"]',
50
+ logs: '//button[@data-testid="logs-link"]',
51
+ states: '//button[@data-testid="states-link"]',
52
+ },
53
+ }
@@ -0,0 +1,26 @@
1
+ export const waitForElementByXPath = async (
2
+ xpath: string,
3
+ optional: boolean = false,
4
+ maxAttempts: number = 50,
5
+ delayMs: number = 50,
6
+ ): Promise<HTMLElement | null> => {
7
+ let attempts = 0
8
+
9
+ while (attempts < maxAttempts) {
10
+ const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
11
+ const targetElement = result?.singleNodeValue as HTMLElement | undefined
12
+
13
+ if (targetElement) {
14
+ return targetElement
15
+ } else if (optional) {
16
+ return null
17
+ }
18
+
19
+ await new Promise((resolve) => setTimeout(resolve, delayMs))
20
+ attempts++
21
+ }
22
+
23
+ console.warn(`Element not found after maximum attempts: ${xpath}`)
24
+
25
+ return null
26
+ }
@@ -0,0 +1,213 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { MotiaTutorial } from '../engine/tutorial-engine'
3
+ import type { TutorialImage } from '../engine/tutorial-types'
4
+ import { waitForElementByXPath } from './tutorial-utils'
5
+
6
+ export const useTutorialEngine = () => {
7
+ const ref = useRef<HTMLDivElement>(null)
8
+ const highlighterRef = useRef<HTMLDivElement>(null)
9
+
10
+ const [title, setTitle] = useState('')
11
+ const [description, setDescription] = useState<React.ReactNode | undefined>(undefined)
12
+ const [image, setImage] = useState<TutorialImage | undefined>(undefined)
13
+ const [link, setLink] = useState<string | undefined>(undefined)
14
+ const [currentStep, setCurrentStep] = useState(0)
15
+ const [totalSteps, setTotalSteps] = useState(MotiaTutorial.steps.length)
16
+
17
+ const manualOpenRef = useRef(false)
18
+ const loading = useRef(false)
19
+ const currentStepRef = useRef(0)
20
+
21
+ const moveComponent = (x: number, y: number) => {
22
+ if (ref.current) {
23
+ ref.current.style.position = 'absolute'
24
+ ref.current.style.left = `${x}px`
25
+ ref.current.style.top = `${y}px`
26
+ }
27
+ }
28
+
29
+ const moveStep = async (stepNumber: number) => {
30
+ const container = ref.current
31
+
32
+ if (container && !loading.current) {
33
+ if (stepNumber >= MotiaTutorial.steps.length) {
34
+ onClose()
35
+ return
36
+ }
37
+
38
+ if (container.parentElement) {
39
+ container.style.transition = 'all 0.3s ease-in-out'
40
+ container.parentElement.style.opacity = '1'
41
+ container.parentElement.style.display = 'block'
42
+ }
43
+
44
+ loading.current = true
45
+ currentStepRef.current = stepNumber
46
+
47
+ const step = MotiaTutorial.steps[stepNumber]
48
+
49
+ // Run any before actions
50
+ if (step.before) {
51
+ for (const action of step.before) {
52
+ const monaco = (window as any).monaco
53
+ if (action.type === 'click') {
54
+ const element = await waitForElementByXPath(action.selector, action.optional)
55
+
56
+ if (element) {
57
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' })
58
+ element.click()
59
+ element.dispatchEvent(
60
+ new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', keyCode: 13 }),
61
+ )
62
+ }
63
+ } else if (action.type === 'fill-editor' && monaco) {
64
+ monaco.editor?.getEditors?.()?.[0]?.setValue(JSON.stringify(action.content, null, 2))
65
+ }
66
+ }
67
+ }
68
+
69
+ setCurrentStep(stepNumber + 1)
70
+ setTitle(step.title)
71
+ setDescription(await step.description())
72
+ setImage(step.image)
73
+ setLink(step.link)
74
+
75
+ setTimeout(async () => {
76
+ const { height, width } = container.getBoundingClientRect()
77
+
78
+ if (step.elementXpath) {
79
+ const targetElement = await waitForElementByXPath(step.elementXpath)
80
+
81
+ if (!targetElement) {
82
+ console.warn(`Element not found after maximum attempts: ${step.elementXpath}`)
83
+ loading.current = false
84
+ return
85
+ }
86
+
87
+ if (targetElement && highlighterRef.current) {
88
+ const { top, left, width, height } = targetElement.getBoundingClientRect()
89
+ highlighterRef.current.style.top = `${top - 0}px`
90
+ highlighterRef.current.style.left = `${left - 0}px`
91
+ highlighterRef.current.style.width = `${width + 0}px`
92
+ highlighterRef.current.style.height = `${height + 0}px`
93
+ }
94
+
95
+ // Position tutorial relative to target element
96
+ if (targetElement) {
97
+ const targetRect = targetElement.getBoundingClientRect()
98
+ const spaceBelow = window.innerHeight - targetRect.bottom
99
+ const spaceAbove = targetRect.top
100
+ const spaceRight = window.innerWidth - targetRect.right
101
+ const spaceLeft = targetRect.left
102
+
103
+ // Helper function to adjust horizontal position within viewport bounds
104
+ const adjustHorizontalPosition = (x: number) => {
105
+ // this is important to avoid the tutorial from overflowing at far left or right
106
+ return Math.max(20, Math.min(x, window.innerWidth - width - 20))
107
+ }
108
+
109
+ // Try to position below first
110
+ if (spaceBelow >= height + 20) {
111
+ const x = targetRect.left + targetRect.width / 2 - width / 2
112
+ moveComponent(adjustHorizontalPosition(x), targetRect.bottom + 20)
113
+ }
114
+ // Try above if not enough space below
115
+ else if (spaceAbove >= height + 20) {
116
+ const x = targetRect.left + targetRect.width / 2 - width / 2
117
+ moveComponent(adjustHorizontalPosition(x), targetRect.top - height - 20)
118
+ }
119
+ // Try right side
120
+ else if (spaceRight >= width + 20) {
121
+ moveComponent(targetRect.right + 20, targetRect.top + targetRect.height / 2 - height / 2)
122
+ }
123
+ // Try left side
124
+ else if (spaceLeft >= width + 20) {
125
+ moveComponent(targetRect.left - width - 20, targetRect.top + targetRect.height / 2 - height / 2)
126
+ }
127
+ }
128
+ } else {
129
+ if (highlighterRef.current) {
130
+ highlighterRef.current.style.top = '50%'
131
+ highlighterRef.current.style.left = '50%'
132
+ highlighterRef.current.style.width = '1px'
133
+ highlighterRef.current.style.height = '1px'
134
+ }
135
+ // Fallback to center of screen
136
+ moveComponent(window.innerWidth / 2 - width / 2, window.innerHeight / 2 - height / 2)
137
+ }
138
+ }, 1)
139
+
140
+ loading.current = false
141
+ }
142
+ }
143
+
144
+ const onClose = () => {
145
+ if (ref.current?.parentElement) {
146
+ ref.current.parentElement.style.transition = 'opacity 0.3s ease-out'
147
+ ref.current.parentElement.style.opacity = '0'
148
+ localStorage.setItem('motia-tutorial-closed', 'true')
149
+
150
+ setTimeout(() => {
151
+ if (ref.current?.parentElement) {
152
+ ref.current.parentElement.style.display = 'none'
153
+ setCurrentStep(0)
154
+ }
155
+ }, 300)
156
+ }
157
+ }
158
+
159
+ useEffect(() => {
160
+ importFile('tutorial/tutorial.tsx')
161
+ .then((module) => {
162
+ if (Array.isArray(module.steps) && module.steps.length > 0) {
163
+ MotiaTutorial.register(module.steps)
164
+ }
165
+ })
166
+ .catch((error) => {
167
+ // Tutorial file is optional, so we don't need to throw an error
168
+ console.log('Tutorial file not found or could not be loaded:', error.message)
169
+ })
170
+ }, [])
171
+
172
+ useEffect(() => {
173
+ const container = ref.current
174
+
175
+ if (container?.parentElement) {
176
+ container.parentElement.style.display = 'none'
177
+ }
178
+
179
+ const onOpen = () => {
180
+ if (container?.parentElement) {
181
+ setTotalSteps(MotiaTutorial.steps.length)
182
+ moveStep(0)
183
+ }
184
+ }
185
+
186
+ MotiaTutorial.onOpen(() => {
187
+ manualOpenRef.current = true
188
+ onOpen()
189
+ })
190
+
191
+ MotiaTutorial.onStepsRegistered(() => {
192
+ if (localStorage.getItem('motia-tutorial-closed') !== 'true') {
193
+ manualOpenRef.current = false
194
+ onOpen()
195
+ }
196
+ })
197
+ }, [])
198
+
199
+ return {
200
+ ref,
201
+ highlighterRef,
202
+ title,
203
+ description,
204
+ image,
205
+ link,
206
+ currentStep,
207
+ totalSteps,
208
+ onClose,
209
+ moveStep,
210
+ currentStepRef,
211
+ manualOpenRef,
212
+ }
213
+ }
@@ -0,0 +1,14 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { MotiaTutorial } from '../engine/tutorial-engine'
3
+ import type { TutorialStep } from '../engine/tutorial-types'
4
+
5
+ export const useTutorial = () => {
6
+ const open = () => MotiaTutorial.open()
7
+ const [steps, setSteps] = useState<TutorialStep[]>([])
8
+
9
+ useEffect(() => {
10
+ MotiaTutorial.onStepsRegistered(() => setSteps(MotiaTutorial.steps))
11
+ }, [])
12
+
13
+ return { open, steps }
14
+ }
@@ -0,0 +1,46 @@
1
+ import { Button } from '@motiadev/ui'
2
+ import { Book } from 'lucide-react'
3
+ import type { FC } from 'react'
4
+ import { motiaAnalytics } from '../../lib/motia-analytics'
5
+ import { Tooltip } from '../ui/tooltip'
6
+ import { useTutorial } from './hooks/use-tutorial'
7
+
8
+ export const TutorialButton: FC = () => {
9
+ const { open, steps } = useTutorial()
10
+ const isTutorialFlowMissing = steps.length === 0
11
+
12
+ const onTutorialButtonClick = () => {
13
+ if (!isTutorialFlowMissing) {
14
+ open()
15
+ }
16
+
17
+ motiaAnalytics.track('tutorial_button_clicked', { isTutorialFlowMissing })
18
+ }
19
+
20
+ const trigger = (
21
+ <Button data-testid="tutorial-trigger" variant="default" onClick={() => onTutorialButtonClick()}>
22
+ <Book className="h-4 w-4" />
23
+ Tutorial
24
+ </Button>
25
+ )
26
+
27
+ if (isTutorialFlowMissing) {
28
+ return (
29
+ <Tooltip
30
+ content={
31
+ <div className="flex flex-col gap-4 p-4 max-w-[320px]">
32
+ <p className="text-sm wrap-break-word p-0 m-0">
33
+ In order to start the tutorial, you need to download the tutorial steps using the Motia CLI. In your
34
+ terminal execute the following command to create a new project:
35
+ </p>
36
+ <pre className="text-sm font-bold">npx motia@latest create</pre>
37
+ </div>
38
+ }
39
+ >
40
+ {trigger}
41
+ </Tooltip>
42
+ )
43
+ }
44
+
45
+ return trigger
46
+ }