@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,82 @@
1
+ import { BackgroundEffect } from '@motiadev/ui'
2
+ import type React from 'react'
3
+ import { forwardRef, useEffect } from 'react'
4
+ import type { TutorialImage } from './engine/tutorial-types'
5
+
6
+ type TutorialStepProps = {
7
+ step: number
8
+ totalSteps: number
9
+ title: string
10
+ description: React.ReactNode
11
+ link?: string
12
+ image?: TutorialImage
13
+ onNext: () => void
14
+ onClose: () => void
15
+ }
16
+
17
+ export const TutorialStep = forwardRef<HTMLDivElement, TutorialStepProps>(
18
+ ({ step, totalSteps, title, description, link, image, onNext, onClose }, ref) => {
19
+ useEffect(() => {
20
+ const handleKeyDown = (e: KeyboardEvent) => {
21
+ if (step > 0 && e.key === 'Escape') {
22
+ onClose()
23
+ } else if (step > 0 && e.key === 'ArrowRight') {
24
+ onNext()
25
+ }
26
+ }
27
+ window.addEventListener('keydown', handleKeyDown)
28
+
29
+ return () => window.removeEventListener('keydown', handleKeyDown)
30
+ }, [onClose, onNext, step])
31
+
32
+ return (
33
+ <div ref={ref} className="driver-popover ">
34
+ {image && (
35
+ <img
36
+ src={image.src}
37
+ alt="Step visual"
38
+ className="driver-popover-image object-cover"
39
+ style={{ height: image.height, width: '100%' }}
40
+ />
41
+ )}
42
+
43
+ <div className="isolate relative">
44
+ <BackgroundEffect />
45
+ <div className="driver-popover-title">
46
+ <h2 className="popover-title">{title}</h2>
47
+ </div>
48
+
49
+ <div className="driver-popover-description">{description}</div>
50
+
51
+ {link && (
52
+ <a href={link} target="_blank" className="text-foreground text-xs font-semibold px-4 hover:underline">
53
+ Learn more
54
+ </a>
55
+ )}
56
+
57
+ <div className="driver-popover-footer flex items-center justify-between">
58
+ <div className="text-sm text-muted-foreground font-semibold">
59
+ {step} <span className="text-foreground">/</span> {totalSteps}
60
+ </div>
61
+
62
+ <div className="driver-popover-navigation-btns driver-popover-navigation-btns-hint flex gap-2">
63
+ <button className="driver-popover-next-btn" onClick={onNext}>
64
+ {step < totalSteps ? 'Continue' : 'Finish'}
65
+ </button>
66
+ </div>
67
+ </div>
68
+
69
+ {step < totalSteps && (
70
+ <div className="tutorial-opt-out-container">
71
+ <button className="tutorial-opt-out-button" onClick={onClose}>
72
+ Close
73
+ </button>
74
+ </div>
75
+ )}
76
+ </div>
77
+ </div>
78
+ )
79
+ },
80
+ )
81
+
82
+ TutorialStep.displayName = 'TutorialStep'
@@ -0,0 +1,59 @@
1
+ import { motiaAnalytics } from '../../lib/motia-analytics'
2
+ import { useTutorialEngine } from './hooks/use-tutorial-engine'
3
+ import { TutorialStep } from './tutorial-step'
4
+ import './tutorial.css'
5
+
6
+ export const Tutorial = () => {
7
+ const engine = useTutorialEngine()
8
+
9
+ const onNext = () => {
10
+ const currentStep = engine.currentStepRef.current
11
+ const nextStep = currentStep + 1
12
+
13
+ engine.moveStep(nextStep)
14
+
15
+ if (engine.currentStep === engine.totalSteps) {
16
+ motiaAnalytics.track('tutorial_completed', {
17
+ manualOpen: engine.manualOpenRef.current,
18
+ })
19
+ } else {
20
+ motiaAnalytics.track('tutorial_next_step', {
21
+ step: nextStep,
22
+ manualOpen: engine.manualOpenRef.current,
23
+ })
24
+ }
25
+ }
26
+
27
+ const onClose = () => {
28
+ motiaAnalytics.track('tutorial_closed', {
29
+ step: engine.currentStepRef.current,
30
+ manualOpen: engine.manualOpenRef.current,
31
+ })
32
+ engine.onClose()
33
+ }
34
+
35
+ return (
36
+ <div>
37
+ {/* backdrop container */}
38
+ <div className="fixed inset-0 z-[9999]" />
39
+
40
+ {/* Highlighter container */}
41
+ <div
42
+ className="absolute top-5 left-5 w-full h-full rounded-lg shadow-[0_0_0_9999px_rgba(0,0,0,0.5)] z-[10000] pointer-events-none"
43
+ ref={engine.highlighterRef}
44
+ />
45
+
46
+ <TutorialStep
47
+ ref={engine.ref}
48
+ step={engine.currentStep}
49
+ totalSteps={engine.totalSteps}
50
+ title={engine.title}
51
+ description={engine.description}
52
+ link={engine.link}
53
+ image={engine.image}
54
+ onNext={onNext}
55
+ onClose={onClose}
56
+ />
57
+ </div>
58
+ )
59
+ }
@@ -0,0 +1,68 @@
1
+ import Editor, { useMonaco } from '@monaco-editor/react'
2
+ import { useThemeStore } from '@motiadev/ui'
3
+ import { type FC, useEffect, useMemo } from 'react'
4
+
5
+ type JsonEditorProps = {
6
+ value: string
7
+ height?: number | string
8
+ schema?: Record<string, unknown>
9
+ onChange?: (value: string) => void
10
+ onValidate?: (isValid: boolean) => void
11
+ language?: 'json' | string
12
+ readOnly?: boolean
13
+ }
14
+
15
+ export const JsonEditor: FC<JsonEditorProps> = ({
16
+ value,
17
+ height = 300,
18
+ schema,
19
+ onChange,
20
+ onValidate,
21
+ language = 'json',
22
+ readOnly = false,
23
+ }) => {
24
+ const monaco = useMonaco()
25
+ const theme = useThemeStore((state) => state.theme)
26
+ const editorTheme = useMemo(() => (theme === 'dark' ? 'vs-dark' : 'light'), [theme])
27
+
28
+ useEffect(() => {
29
+ if (!monaco) return
30
+
31
+ // @ts-expect-error - monaco.languages.typescript.javascriptDefaults is not typed
32
+ monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ isolatedModules: true })
33
+ // @ts-expect-error - monaco.languages.json.jsonDefaults is not typed
34
+ monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
35
+ schemas: schema
36
+ ? [
37
+ {
38
+ uri: window.location.href,
39
+ fileMatch: ['*'],
40
+ schema,
41
+ },
42
+ ]
43
+ : [],
44
+ })
45
+ }, [monaco, schema, language])
46
+
47
+ return (
48
+ <Editor
49
+ data-testid="json-editor"
50
+ height={height}
51
+ language={language}
52
+ value={value}
53
+ theme={editorTheme}
54
+ onChange={(value) => {
55
+ if (!value) {
56
+ onValidate?.(false)
57
+ }
58
+ onChange?.(value ?? '')
59
+ }}
60
+ onValidate={(markers) => onValidate?.(markers.length === 0)}
61
+ options={{
62
+ readOnly,
63
+ scrollBeyondLastLine: false,
64
+ minimap: { enabled: false },
65
+ }}
66
+ />
67
+ )
68
+ }
@@ -0,0 +1,75 @@
1
+ import { cn } from '@motiadev/ui'
2
+ import * as React from 'react'
3
+
4
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div className="relative w-full overflow-auto">
7
+ <table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
8
+ </div>
9
+ ),
10
+ )
11
+ Table.displayName = 'Table'
12
+
13
+ const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
14
+ ({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,
15
+ )
16
+ TableHeader.displayName = 'TableHeader'
17
+
18
+ const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
19
+ ({ className, ...props }, ref) => (
20
+ <tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
21
+ ),
22
+ )
23
+ TableBody.displayName = 'TableBody'
24
+
25
+ const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
26
+ ({ className, ...props }, ref) => (
27
+ <tfoot ref={ref} className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)} {...props} />
28
+ ),
29
+ )
30
+ TableFooter.displayName = 'TableFooter'
31
+
32
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
33
+ ({ className, ...props }, ref) => (
34
+ <tr
35
+ ref={ref}
36
+ className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
37
+ {...props}
38
+ />
39
+ ),
40
+ )
41
+ TableRow.displayName = 'TableRow'
42
+
43
+ const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
44
+ ({ className, ...props }, ref) => (
45
+ <th
46
+ ref={ref}
47
+ className={cn(
48
+ 'h-10 px-2 text-left align-middle text-md font-medium bg-muted text-muted-foreground font-bold [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
49
+ className,
50
+ )}
51
+ {...props}
52
+ />
53
+ ),
54
+ )
55
+ TableHead.displayName = 'TableHead'
56
+
57
+ const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
58
+ ({ className, ...props }, ref) => (
59
+ <td
60
+ ref={ref}
61
+ className={cn('p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className)}
62
+ {...props}
63
+ />
64
+ ),
65
+ )
66
+ TableCell.displayName = 'TableCell'
67
+
68
+ const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
69
+ ({ className, ...props }, ref) => (
70
+ <caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
71
+ ),
72
+ )
73
+ TableCaption.displayName = 'TableCaption'
74
+
75
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
@@ -0,0 +1,54 @@
1
+ import { cn, type Theme, useThemeStore } from '@motiadev/ui'
2
+ import { Moon, Sun } from 'lucide-react'
3
+ import type React from 'react'
4
+ import { useEffect } from 'react'
5
+
6
+ export const ThemeToggle: React.FC = () => {
7
+ const theme = useThemeStore((state) => state.theme)
8
+ const setTheme = useThemeStore((state) => state.setTheme)
9
+
10
+ const toggleTheme = () => {
11
+ setTheme(theme === 'light' ? 'dark' : 'light')
12
+ }
13
+
14
+ useEffect(() => {
15
+ const url = new URL(window.location.href)
16
+ const colorScheme = url.searchParams.get('color-scheme') as Theme
17
+ if (colorScheme) {
18
+ setTheme(colorScheme)
19
+ }
20
+ }, [setTheme])
21
+
22
+ return (
23
+ <button
24
+ onClick={toggleTheme}
25
+ className="relative flex items-center cursor-pointer w-16 h-8 border bg-muted-foreground/10 rounded-full p-1 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
26
+ aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}
27
+ >
28
+ <div
29
+ className={cn(
30
+ 'absolute w-6 h-6 bg-background border border-border rounded-full shadow-sm transition-transform duration-200 ease-in-out',
31
+ theme === 'dark' ? 'translate-x-8' : 'translate-x-0',
32
+ )}
33
+ />
34
+
35
+ <div className="flex items-center justify-center w-6 h-6 z-10">
36
+ <Sun
37
+ className={cn(
38
+ 'h-3.5 w-3.5 transition-colors duration-200',
39
+ theme === 'light' ? 'text-foreground' : 'text-muted-foreground',
40
+ )}
41
+ />
42
+ </div>
43
+
44
+ <div className="flex items-center justify-center w-6 h-6 z-10 ml-2">
45
+ <Moon
46
+ className={cn(
47
+ 'h-3.5 w-3.5 transition-colors duration-200',
48
+ theme === 'dark' ? 'text-foreground' : 'text-muted-foreground',
49
+ )}
50
+ />
51
+ </div>
52
+ </button>
53
+ )
54
+ }
@@ -0,0 +1,26 @@
1
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip'
2
+ import type { ReactNode } from 'react'
3
+
4
+ export const Tooltip = ({
5
+ children,
6
+ content,
7
+ disabled,
8
+ }: {
9
+ children: ReactNode
10
+ content: string | ReactNode
11
+ disabled?: boolean
12
+ }) => (
13
+ <TooltipPrimitive.Provider disableHoverableContent={disabled}>
14
+ <TooltipPrimitive.Root>
15
+ <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
16
+ <TooltipPrimitive.Portal>
17
+ <TooltipPrimitive.Content className="TooltipContent" side="bottom">
18
+ <div className="p-2 bg-background text-popover-foreground text-sm rounded-lg border border-light-800">
19
+ {content}
20
+ </div>
21
+ <TooltipPrimitive.Arrow className="TooltipArrow" />
22
+ </TooltipPrimitive.Content>
23
+ </TooltipPrimitive.Portal>
24
+ </TooltipPrimitive.Root>
25
+ </TooltipPrimitive.Provider>
26
+ )
@@ -0,0 +1,22 @@
1
+ import { useCallback, useEffect, useRef } from 'react'
2
+
3
+ export const useDebounced = (fn: () => void, delay = 500) => {
4
+ const saveTimeoutRef = useRef<NodeJS.Timeout>(null)
5
+
6
+ const debouncedFn = useCallback(() => {
7
+ if (saveTimeoutRef.current) {
8
+ clearTimeout(saveTimeoutRef.current)
9
+ }
10
+ saveTimeoutRef.current = setTimeout(fn, delay)
11
+ }, [fn, delay])
12
+
13
+ useEffect(() => {
14
+ return () => {
15
+ if (saveTimeoutRef.current) {
16
+ clearTimeout(saveTimeoutRef.current)
17
+ }
18
+ }
19
+ }, [])
20
+
21
+ return debouncedFn
22
+ }
@@ -0,0 +1,33 @@
1
+ import { useStreamGroup } from '@motiadev/stream-client-react'
2
+ import { useEffect } from 'react'
3
+ import { useFlowStore } from '../stores/use-flow-store'
4
+ import type { FlowResponse } from '../types/flow'
5
+
6
+ const streamGroupArgs = { streamName: '__motia.flows', groupId: 'default' }
7
+
8
+ export const useFetchFlows = () => {
9
+ const setFlows = useFlowStore((state) => state.setFlows)
10
+ const selectFlowId = useFlowStore((state) => state.selectFlowId)
11
+ const clearSelectedFlowId = useFlowStore((state) => state.clearSelectedFlowId)
12
+ const selectedFlowId = useFlowStore((state) => state.selectedFlowId)
13
+
14
+ const { data: flows } = useStreamGroup<FlowResponse>(streamGroupArgs)
15
+
16
+ useEffect(() => {
17
+ if (flows) setFlows(flows.map((flow) => flow.id))
18
+ }, [flows, setFlows])
19
+
20
+ useEffect(() => {
21
+ const hasFlows = flows.length > 0
22
+ const isSelectedFlowValid = selectedFlowId && flows.some((flow) => flow.id === selectedFlowId)
23
+
24
+ if (!hasFlows && selectedFlowId) {
25
+ clearSelectedFlowId()
26
+ return
27
+ }
28
+
29
+ if (hasFlows && (!selectedFlowId || !isSelectedFlowValid)) {
30
+ selectFlowId(flows[0].id)
31
+ }
32
+ }, [flows, selectedFlowId, selectFlowId, clearSelectedFlowId])
33
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react'
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener('change', onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener('change', onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }
@@ -0,0 +1,42 @@
1
+ import { Position, useReactFlow, useUpdateNodeInternals } from '@xyflow/react'
2
+ import type { BaseNodeProps } from '../publicComponents/node-props'
3
+
4
+ export const useHandlePositions = (data: BaseNodeProps) => {
5
+ const reactFlow = useReactFlow()
6
+ const updateNodeInternals = useUpdateNodeInternals()
7
+ const sourcePosition = data.nodeConfig?.sourceHandlePosition === 'bottom' ? Position.Bottom : Position.Right
8
+ const targetPosition = data.nodeConfig?.targetHandlePosition === 'top' ? Position.Top : Position.Left
9
+
10
+ const updateSourcePosition = (position: 'bottom' | 'right') => {
11
+ reactFlow.updateNode(data.id, {
12
+ data: { ...data, nodeConfig: { ...data.nodeConfig, sourceHandlePosition: position } },
13
+ })
14
+ updateNodeInternals(data.id)
15
+ }
16
+
17
+ const updateTargetPosition = (position: 'top' | 'left') => {
18
+ reactFlow.updateNode(data.id, {
19
+ data: { ...data, nodeConfig: { ...data.nodeConfig, targetHandlePosition: position } },
20
+ })
21
+ updateNodeInternals(data.id)
22
+ }
23
+
24
+ const toggleTargetPosition = () => {
25
+ const newPosition = targetPosition === Position.Top ? Position.Left : Position.Top
26
+ updateTargetPosition(newPosition)
27
+ }
28
+
29
+ const toggleSourcePosition = () => {
30
+ const newPosition = sourcePosition === Position.Bottom ? Position.Right : Position.Bottom
31
+ updateSourcePosition(newPosition)
32
+ }
33
+
34
+ return {
35
+ sourcePosition,
36
+ targetPosition,
37
+ updateSourcePosition,
38
+ updateTargetPosition,
39
+ toggleTargetPosition,
40
+ toggleSourcePosition,
41
+ }
42
+ }
@@ -1,7 +1,7 @@
1
- @import "@motiadev/ui/styles.css";
2
- @import "@motiadev/ui/globals.css";
1
+ @import '@motiadev/ui/styles.css';
2
+ @import '@motiadev/ui/globals.css';
3
3
 
4
- @import "tw-animate-css";
4
+ @import 'tw-animate-css';
5
5
  @config "../tailwind.config.js";
6
6
 
7
7
  @keyframes flowDash {
@@ -27,7 +27,7 @@
27
27
  }
28
28
 
29
29
  .font-mono {
30
- font-family: "IBM Plex Mono", "Courier New", monospace;
30
+ font-family: 'IBM Plex Mono', 'Courier New', monospace;
31
31
  font-size: 14px;
32
32
  }
33
33
 
@@ -40,7 +40,7 @@
40
40
  .json-view {
41
41
  border-radius: 8px;
42
42
  padding: 12px 12px;
43
- font-family: "IBM Plex Mono", "Courier New", monospace;
43
+ font-family: 'IBM Plex Mono', 'Courier New', monospace;
44
44
  font-size: 14px;
45
45
  line-height: 1.5;
46
46
  font-weight: 500;
@@ -0,0 +1,110 @@
1
+ import { formatDuration } from '../utils'
2
+
3
+ describe('formatDuration', () => {
4
+ describe('milliseconds', () => {
5
+ it('should format values under 1 second as milliseconds', () => {
6
+ expect(formatDuration(0)).toBe('0ms')
7
+ expect(formatDuration(1)).toBe('1ms')
8
+ expect(formatDuration(250)).toBe('250ms')
9
+ expect(formatDuration(500)).toBe('500ms')
10
+ expect(formatDuration(999)).toBe('999ms')
11
+ })
12
+ })
13
+
14
+ describe('seconds', () => {
15
+ it('should format values between 1 second and 1 minute as seconds with 1 decimal', () => {
16
+ expect(formatDuration(1000)).toBe('1.0s')
17
+ expect(formatDuration(1500)).toBe('1.5s')
18
+ expect(formatDuration(15000)).toBe('15.0s')
19
+ expect(formatDuration(45500)).toBe('45.5s')
20
+ expect(formatDuration(59999)).toBe('60.0s')
21
+ })
22
+
23
+ it('should round to 1 decimal place', () => {
24
+ expect(formatDuration(1234)).toBe('1.2s')
25
+ expect(formatDuration(1567)).toBe('1.6s')
26
+ expect(formatDuration(12345)).toBe('12.3s')
27
+ })
28
+ })
29
+
30
+ describe('minutes', () => {
31
+ it('should format values between 1 minute and 1 hour as minutes with 1 decimal', () => {
32
+ expect(formatDuration(60000)).toBe('1.0min')
33
+ expect(formatDuration(90000)).toBe('1.5min')
34
+ expect(formatDuration(300000)).toBe('5.0min')
35
+ expect(formatDuration(1800000)).toBe('30.0min')
36
+ expect(formatDuration(3599999)).toBe('60.0min')
37
+ })
38
+
39
+ it('should round to 1 decimal place', () => {
40
+ expect(formatDuration(123456)).toBe('2.1min')
41
+ expect(formatDuration(567890)).toBe('9.5min')
42
+ })
43
+ })
44
+
45
+ describe('hours', () => {
46
+ it('should format values 1 hour or more as hours with 1 decimal', () => {
47
+ expect(formatDuration(3600000)).toBe('1.0h')
48
+ expect(formatDuration(5400000)).toBe('1.5h')
49
+ expect(formatDuration(7200000)).toBe('2.0h')
50
+ expect(formatDuration(36000000)).toBe('10.0h')
51
+ })
52
+
53
+ it('should round to 1 decimal place', () => {
54
+ expect(formatDuration(3661000)).toBe('1.0h')
55
+ expect(formatDuration(5432100)).toBe('1.5h')
56
+ expect(formatDuration(9000000)).toBe('2.5h')
57
+ })
58
+ })
59
+
60
+ describe('edge cases', () => {
61
+ it('should return "N/A" for undefined', () => {
62
+ expect(formatDuration(undefined)).toBe('N/A')
63
+ })
64
+
65
+ it('should return "N/A" for null', () => {
66
+ expect(formatDuration(null as any)).toBe('N/A')
67
+ })
68
+
69
+ it('should return "N/A" for 0 when treated as falsy', () => {
70
+ expect(formatDuration(0)).toBe('0ms')
71
+ })
72
+ })
73
+
74
+ describe('boundary values', () => {
75
+ it('should correctly handle millisecond-second boundary (999ms vs 1.0s)', () => {
76
+ expect(formatDuration(999)).toBe('999ms')
77
+ expect(formatDuration(1000)).toBe('1.0s')
78
+ })
79
+
80
+ it('should correctly handle second-minute boundary (59.9s vs 1.0min)', () => {
81
+ expect(formatDuration(59999)).toBe('60.0s')
82
+ expect(formatDuration(60000)).toBe('1.0min')
83
+ })
84
+
85
+ it('should correctly handle minute-hour boundary (59.9min vs 1.0h)', () => {
86
+ expect(formatDuration(3599999)).toBe('60.0min')
87
+ expect(formatDuration(3600000)).toBe('1.0h')
88
+ })
89
+ })
90
+
91
+ describe('real-world scenarios', () => {
92
+ it('should format typical API response times', () => {
93
+ expect(formatDuration(50)).toBe('50ms')
94
+ expect(formatDuration(150)).toBe('150ms')
95
+ expect(formatDuration(2500)).toBe('2.5s')
96
+ })
97
+
98
+ it('should format typical workflow execution times', () => {
99
+ expect(formatDuration(5000)).toBe('5.0s')
100
+ expect(formatDuration(30000)).toBe('30.0s')
101
+ expect(formatDuration(120000)).toBe('2.0min')
102
+ })
103
+
104
+ it('should format long-running tasks', () => {
105
+ expect(formatDuration(600000)).toBe('10.0min')
106
+ expect(formatDuration(1800000)).toBe('30.0min')
107
+ expect(formatDuration(7200000)).toBe('2.0h')
108
+ })
109
+ })
110
+ })