@assistkick/create 1.7.0 → 1.8.0

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 (200) hide show
  1. package/dist/bin/create.js +0 -0
  2. package/package.json +9 -7
  3. package/templates/assistkick-product-system/.env.example +1 -0
  4. package/templates/assistkick-product-system/local.db +0 -0
  5. package/templates/assistkick-product-system/package.json +4 -2
  6. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  10. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  11. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  13. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  14. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  15. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  16. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  18. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  19. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  24. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  28. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  30. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  33. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
  42. package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
  45. package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
  46. package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
  47. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
  48. package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
  49. package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
  50. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +20 -0
  51. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
  52. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
  53. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
  54. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
  55. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
  56. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +187 -56
  57. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
  58. package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
  59. package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
  60. package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
  61. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +202 -171
  62. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  63. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  64. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +32 -49
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
  66. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  67. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
  68. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  69. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +103 -87
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +81 -37
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  105. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  107. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  108. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  111. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  112. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
  119. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
  120. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
  121. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  128. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  129. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
  130. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  131. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  132. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  133. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  134. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  135. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  136. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  137. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  138. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  139. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  140. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  151. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  152. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  153. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  154. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  155. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
  156. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  157. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  158. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  159. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  160. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  161. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  162. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  163. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  164. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  165. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  166. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  167. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  168. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  169. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  170. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  171. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  172. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  173. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  174. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  175. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  176. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  177. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  178. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  179. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  180. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  181. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  182. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  183. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  184. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  185. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  186. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  187. package/templates/skills/assistkick-developer/SKILL.md +3 -0
  188. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  189. package/templates/skills/product-system/graph.json +1890 -0
  190. package/templates/skills/product-system/kanban.json +304 -0
  191. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  192. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  193. package/templates/skills/product-system/nodes/data_001.md +51 -0
  194. package/templates/skills/product-system/nodes/data_002.md +40 -0
  195. package/templates/skills/product-system/nodes/data_004.md +38 -0
  196. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  197. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  198. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  199. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  200. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,60 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Handle, Position, useReactFlow } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { RefreshCw } from 'lucide-react';
5
+
6
+ export function CheckCycleCountNode({ id, data, selected }: NodeProps) {
7
+ const { updateNodeData } = useReactFlow();
8
+ const maxCount = (data.maxCount as number) || 3;
9
+
10
+ const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
11
+ const val = parseInt(e.target.value, 10);
12
+ if (!isNaN(val) && val > 0) {
13
+ updateNodeData(id, { maxCount: val });
14
+ }
15
+ }, [id, updateNodeData]);
16
+
17
+ return (
18
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
19
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
20
+ }`}>
21
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
22
+ <div className="flex items-center gap-2 mb-2">
23
+ <RefreshCw size={14} className="text-orange-400" />
24
+ <span className="text-[12px] font-semibold">Check Cycle Count</span>
25
+ </div>
26
+ <div className="mb-2">
27
+ <label className="text-[10px] text-content-muted uppercase tracking-wider">Max Count</label>
28
+ <input
29
+ type="number"
30
+ min={1}
31
+ value={maxCount}
32
+ onChange={handleChange}
33
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
34
+ />
35
+ </div>
36
+ <div className="flex justify-between text-[9px] text-content-muted">
37
+ <div className="relative">
38
+ <span className="px-1.5 py-0.5 rounded bg-surface border border-edge">under limit</span>
39
+ <Handle
40
+ type="source"
41
+ position={Position.Bottom}
42
+ id="under_limit"
43
+ className="!w-2 !h-2 !bg-green-400 !border !border-surface"
44
+ style={{ left: '30%' }}
45
+ />
46
+ </div>
47
+ <div className="relative">
48
+ <span className="px-1.5 py-0.5 rounded bg-surface border border-edge">at limit</span>
49
+ <Handle
50
+ type="source"
51
+ position={Position.Bottom}
52
+ id="at_limit"
53
+ className="!w-2 !h-2 !bg-red-400 !border !border-surface"
54
+ style={{ left: '70%' }}
55
+ />
56
+ </div>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,42 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Handle, Position, useReactFlow } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { Square } from 'lucide-react';
5
+ import { END_STATUS_TYPES } from './workflow_types';
6
+ import type { EndStatusType } from './workflow_types';
7
+
8
+ const STATUS_COLORS: Record<EndStatusType, string> = {
9
+ success: 'text-accent',
10
+ blocked: 'text-amber-400',
11
+ failed: 'text-error',
12
+ };
13
+
14
+ export function EndNode({ id, data, selected }: NodeProps) {
15
+ const { updateNodeData } = useReactFlow();
16
+ const statusType = (data.statusType as EndStatusType) || 'success';
17
+
18
+ const handleChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
19
+ updateNodeData(id, { statusType: e.target.value });
20
+ }, [id, updateNodeData]);
21
+
22
+ return (
23
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[160px] ${
24
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
25
+ }`}>
26
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
27
+ <div className="flex items-center gap-2 mb-2">
28
+ <Square size={14} className={STATUS_COLORS[statusType]} />
29
+ <span className="text-[12px] font-semibold uppercase tracking-wider">End</span>
30
+ </div>
31
+ <select
32
+ value={statusType}
33
+ onChange={handleChange}
34
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
35
+ >
36
+ {END_STATUS_TYPES.map(s => (
37
+ <option key={s} value={s}>{s}</option>
38
+ ))}
39
+ </select>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,189 @@
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import {
3
+ Handle,
4
+ Position,
5
+ ReactFlow,
6
+ Background,
7
+ BackgroundVariant,
8
+ MiniMap,
9
+ useNodesState,
10
+ useEdgesState,
11
+ addEdge,
12
+ ReactFlowProvider,
13
+ type Connection,
14
+ type NodeTypes,
15
+ type NodeProps,
16
+ } from '@xyflow/react';
17
+ import { Layers, Maximize2, Minimize2 } from 'lucide-react';
18
+ import { StartNode } from './StartNode';
19
+ import { EndNode } from './EndNode';
20
+ import { TransitionCardNode } from './TransitionCardNode';
21
+ import { RunAgentNode } from './RunAgentNode';
22
+ import { CheckCardPositionNode } from './CheckCardPositionNode';
23
+ import { CheckCycleCountNode } from './CheckCycleCountNode';
24
+ import { SetCardMetadataNode } from './SetCardMetadataNode';
25
+
26
+ /** Node types available inside the group sub-canvas (no nested groups). */
27
+ const INNER_NODE_TYPE_MAP: NodeTypes = {
28
+ start: StartNode,
29
+ end: EndNode,
30
+ transitionCard: TransitionCardNode,
31
+ runAgent: RunAgentNode,
32
+ checkCardPosition: CheckCardPositionNode,
33
+ checkCycleCount: CheckCycleCountNode,
34
+ setCardMetadata: SetCardMetadataNode,
35
+ };
36
+
37
+ function GroupSubCanvas({
38
+ initialNodes,
39
+ initialEdges,
40
+ onChange,
41
+ }: {
42
+ initialNodes: any[];
43
+ initialEdges: any[];
44
+ onChange: (nodes: any[], edges: any[]) => void;
45
+ }) {
46
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
47
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
48
+ const nodeTypes = useMemo(() => INNER_NODE_TYPE_MAP, []);
49
+
50
+ const onConnect = useCallback(
51
+ (connection: Connection) => {
52
+ const edge = {
53
+ ...connection,
54
+ type: 'smoothstep',
55
+ animated: true,
56
+ style: { stroke: 'var(--accent)', strokeWidth: 2 },
57
+ markerEnd: { type: 'arrowclosed' as const, color: 'var(--accent)' },
58
+ };
59
+ setEdges((eds) => addEdge(edge, eds));
60
+ },
61
+ [setEdges],
62
+ );
63
+
64
+ // Notify parent when nodes/edges change
65
+ React.useEffect(() => {
66
+ onChange(nodes, edges);
67
+ }, [nodes, edges, onChange]);
68
+
69
+ return (
70
+ <ReactFlow
71
+ nodes={nodes}
72
+ edges={edges}
73
+ onNodesChange={onNodesChange}
74
+ onEdgesChange={onEdgesChange}
75
+ onConnect={onConnect}
76
+ nodeTypes={nodeTypes}
77
+ snapToGrid
78
+ snapGrid={[8, 8]}
79
+ fitView
80
+ deleteKeyCode={['Backspace', 'Delete']}
81
+ defaultEdgeOptions={{
82
+ type: 'smoothstep',
83
+ animated: true,
84
+ style: { stroke: 'var(--accent)', strokeWidth: 1.5 },
85
+ markerEnd: { type: 'arrowclosed' as const, color: 'var(--accent)' },
86
+ }}
87
+ proOptions={{ hideAttribution: true }}
88
+ className="group-sub-canvas"
89
+ >
90
+ <Background variant={BackgroundVariant.Dots} gap={8} size={0.5} color="var(--border-color)" />
91
+ <MiniMap
92
+ nodeStrokeColor="var(--accent)"
93
+ nodeColor="var(--bg-tertiary)"
94
+ maskColor="rgba(0,0,0,0.3)"
95
+ className="!bg-surface !border !border-edge !rounded !w-24 !h-16"
96
+ pannable
97
+ zoomable={false}
98
+ />
99
+ </ReactFlow>
100
+ );
101
+ }
102
+
103
+ export function GroupNode({ data, selected }: NodeProps) {
104
+ const [expanded, setExpanded] = useState(false);
105
+ const groupData = data as any;
106
+ const groupName = groupData.groupName || 'Group';
107
+ const internalNodes = groupData.internalNodes || [];
108
+ const internalEdges = groupData.internalEdges || [];
109
+ const inputHandles: string[] = groupData.inputHandles || ['input'];
110
+ const outputHandles: string[] = groupData.outputHandles || ['output'];
111
+
112
+ const handleToggle = useCallback(() => {
113
+ setExpanded((prev) => !prev);
114
+ }, []);
115
+
116
+ const handleInternalChange = useCallback(
117
+ (nodes: any[], edges: any[]) => {
118
+ // Update the group instance data (not the saved group definition)
119
+ groupData.internalNodes = nodes;
120
+ groupData.internalEdges = edges;
121
+ },
122
+ [groupData],
123
+ );
124
+
125
+ return (
126
+ <div
127
+ className={`rounded-lg border bg-surface-raised text-content ${
128
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
129
+ } ${expanded ? 'min-w-[500px] min-h-[350px]' : 'min-w-[160px]'}`}
130
+ >
131
+ {/* Input handles */}
132
+ {inputHandles.map((handleId, i) => (
133
+ <Handle
134
+ key={`in-${handleId}`}
135
+ type="target"
136
+ position={Position.Top}
137
+ id={handleId}
138
+ className="!w-3 !h-3 !bg-accent !border-2 !border-surface"
139
+ style={inputHandles.length > 1 ? { left: `${((i + 1) / (inputHandles.length + 1)) * 100}%` } : undefined}
140
+ />
141
+ ))}
142
+
143
+ {/* Header */}
144
+ <div className="flex items-center gap-2 px-3 py-2 border-b border-edge">
145
+ <Layers size={14} className="text-emerald-400 shrink-0" />
146
+ <span className="text-[12px] font-semibold flex-1 truncate">{groupName}</span>
147
+ <button
148
+ onClick={handleToggle}
149
+ className="p-0.5 rounded hover:bg-surface transition-colors"
150
+ title={expanded ? 'Collapse group' : 'Expand group'}
151
+ >
152
+ {expanded ? <Minimize2 size={12} /> : <Maximize2 size={12} />}
153
+ </button>
154
+ </div>
155
+
156
+ {/* Collapsed state — simple summary */}
157
+ {!expanded && (
158
+ <div className="px-3 py-2 text-[10px] text-content-muted">
159
+ {internalNodes.length} nodes
160
+ </div>
161
+ )}
162
+
163
+ {/* Expanded state — nested ReactFlow sub-canvas */}
164
+ {expanded && (
165
+ <div className="w-[480px] h-[280px] p-1">
166
+ <ReactFlowProvider>
167
+ <GroupSubCanvas
168
+ initialNodes={internalNodes}
169
+ initialEdges={internalEdges}
170
+ onChange={handleInternalChange}
171
+ />
172
+ </ReactFlowProvider>
173
+ </div>
174
+ )}
175
+
176
+ {/* Output handles */}
177
+ {outputHandles.map((handleId, i) => (
178
+ <Handle
179
+ key={`out-${handleId}`}
180
+ type="source"
181
+ position={Position.Bottom}
182
+ id={handleId}
183
+ className="!w-3 !h-3 !bg-accent !border-2 !border-surface"
184
+ style={outputHandles.length > 1 ? { left: `${((i + 1) / (outputHandles.length + 1)) * 100}%` } : undefined}
185
+ />
186
+ ))}
187
+ </div>
188
+ );
189
+ }
@@ -0,0 +1,123 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import {
3
+ Play, Square, ArrowRightLeft, Bot, GitBranch, RefreshCw, Tag, Layers, Trash2,
4
+ } from 'lucide-react';
5
+ import { NODE_PALETTE_ITEMS } from './workflow_types';
6
+ import type { WorkflowNodeType } from './workflow_types';
7
+ import { apiClient } from '../../api/client';
8
+
9
+ const NODE_ICONS: Record<string, React.ReactNode> = {
10
+ start: <Play size={14} className="text-accent" />,
11
+ end: <Square size={14} className="text-red-400" />,
12
+ transitionCard: <ArrowRightLeft size={14} className="text-blue-400" />,
13
+ runAgent: <Bot size={14} className="text-purple-400" />,
14
+ checkCardPosition: <GitBranch size={14} className="text-cyan-400" />,
15
+ checkCycleCount: <RefreshCw size={14} className="text-orange-400" />,
16
+ setCardMetadata: <Tag size={14} className="text-yellow-400" />,
17
+ };
18
+
19
+ interface WorkflowGroup {
20
+ id: string;
21
+ name: string;
22
+ projectId: string | null;
23
+ graphData: string;
24
+ }
25
+
26
+ interface NodePaletteProps {
27
+ projectId?: string;
28
+ }
29
+
30
+ export function NodePalette({ projectId }: NodePaletteProps) {
31
+ const [groups, setGroups] = useState<WorkflowGroup[]>([]);
32
+
33
+ const fetchGroups = useCallback(async () => {
34
+ try {
35
+ const resp = await apiClient.fetchWorkflowGroups(projectId);
36
+ setGroups(resp.groups || []);
37
+ } catch {
38
+ // Silent fail — groups are optional
39
+ }
40
+ }, [projectId]);
41
+
42
+ useEffect(() => {
43
+ fetchGroups();
44
+ }, [fetchGroups]);
45
+
46
+ const onDragStart = useCallback((event: React.DragEvent, nodeType: WorkflowNodeType) => {
47
+ event.dataTransfer.setData('application/reactflow', nodeType);
48
+ event.dataTransfer.effectAllowed = 'move';
49
+ }, []);
50
+
51
+ const onGroupDragStart = useCallback((event: React.DragEvent, group: WorkflowGroup) => {
52
+ event.dataTransfer.setData('application/workflow-group', JSON.stringify(group));
53
+ event.dataTransfer.effectAllowed = 'move';
54
+ }, []);
55
+
56
+ const handleDeleteGroup = useCallback(async (groupId: string) => {
57
+ try {
58
+ await apiClient.deleteWorkflowGroup(groupId);
59
+ setGroups((prev) => prev.filter((g) => g.id !== groupId));
60
+ } catch {
61
+ // Silent fail
62
+ }
63
+ }, []);
64
+
65
+ return (
66
+ <div className="space-y-4">
67
+ {/* Standard node types */}
68
+ <div className="space-y-1">
69
+ <div className="text-[11px] font-medium text-content-muted uppercase tracking-wider px-1 mb-2">Node Palette</div>
70
+ {NODE_PALETTE_ITEMS.map(item => (
71
+ <div
72
+ key={item.type}
73
+ draggable
74
+ onDragStart={(e) => onDragStart(e, item.type)}
75
+ className="flex items-center gap-2.5 px-2.5 py-2 rounded-md border border-edge bg-surface hover:bg-surface-raised hover:border-content/20 cursor-grab active:cursor-grabbing transition-all text-[12px]"
76
+ >
77
+ {NODE_ICONS[item.type]}
78
+ <div>
79
+ <div className="font-medium text-content">{item.label}</div>
80
+ <div className="text-[10px] text-content-muted">{item.description}</div>
81
+ </div>
82
+ </div>
83
+ ))}
84
+ </div>
85
+
86
+ {/* Reusable Groups section */}
87
+ <div className="space-y-1">
88
+ <div className="text-[11px] font-medium text-content-muted uppercase tracking-wider px-1 mb-2">Groups</div>
89
+ {groups.length === 0 ? (
90
+ <div className="px-2.5 py-2 text-[10px] text-content-muted italic">
91
+ No groups yet. Select nodes on the canvas, right-click, and choose "Save as Reusable Group".
92
+ </div>
93
+ ) : (
94
+ groups.map((group) => (
95
+ <div
96
+ key={group.id}
97
+ draggable
98
+ onDragStart={(e) => onGroupDragStart(e, group)}
99
+ className="flex items-center gap-2.5 px-2.5 py-2 rounded-md border border-edge bg-surface hover:bg-surface-raised hover:border-content/20 cursor-grab active:cursor-grabbing transition-all text-[12px] group/item"
100
+ >
101
+ <Layers size={14} className="text-emerald-400 shrink-0" />
102
+ <div className="flex-1 min-w-0">
103
+ <div className="font-medium text-content truncate">{group.name}</div>
104
+ <div className="text-[10px] text-content-muted">Reusable group</div>
105
+ </div>
106
+ <button
107
+ onClick={(e) => {
108
+ e.stopPropagation();
109
+ e.preventDefault();
110
+ handleDeleteGroup(group.id);
111
+ }}
112
+ className="opacity-0 group-hover/item:opacity-100 p-0.5 rounded hover:bg-error/10 text-content-muted hover:text-error transition-all"
113
+ title="Delete group"
114
+ >
115
+ <Trash2 size={10} />
116
+ </button>
117
+ </div>
118
+ ))
119
+ )}
120
+ </div>
121
+ </div>
122
+ );
123
+ }
@@ -0,0 +1,51 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { Handle, Position, useReactFlow } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { Bot } from 'lucide-react';
5
+ import { apiClient } from '../../api/client';
6
+
7
+ interface AgentOption {
8
+ id: string;
9
+ name: string;
10
+ }
11
+
12
+ export function RunAgentNode({ id, data, selected }: NodeProps) {
13
+ const { updateNodeData } = useReactFlow();
14
+ const agentId = (data.agentId as string) || '';
15
+ const [agents, setAgents] = useState<AgentOption[]>([]);
16
+
17
+ useEffect(() => {
18
+ apiClient.fetchAgents('global').then(resp => {
19
+ setAgents(resp.agents.map((a: AgentOption) => ({ id: a.id, name: a.name })));
20
+ }).catch(() => {});
21
+ }, []);
22
+
23
+ const handleChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
24
+ const selectedId = e.target.value;
25
+ const agent = agents.find(a => a.id === selectedId);
26
+ updateNodeData(id, { agentId: selectedId, agentName: agent?.name || '' });
27
+ }, [id, updateNodeData, agents]);
28
+
29
+ return (
30
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
31
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
32
+ }`}>
33
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
34
+ <div className="flex items-center gap-2 mb-2">
35
+ <Bot size={14} className="text-purple-400" />
36
+ <span className="text-[12px] font-semibold">Run Agent</span>
37
+ </div>
38
+ <select
39
+ value={agentId}
40
+ onChange={handleChange}
41
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
42
+ >
43
+ <option value="">Select agent...</option>
44
+ {agents.map(a => (
45
+ <option key={a.id} value={a.id}>{a.name}</option>
46
+ ))}
47
+ </select>
48
+ <Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,53 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Handle, Position, useReactFlow } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { Tag } from 'lucide-react';
5
+
6
+ export function SetCardMetadataNode({ id, data, selected }: NodeProps) {
7
+ const { updateNodeData } = useReactFlow();
8
+ const key = (data.key as string) || '';
9
+ const value = (data.value as string) || '';
10
+
11
+ const handleKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
12
+ updateNodeData(id, { key: e.target.value });
13
+ }, [id, updateNodeData]);
14
+
15
+ const handleValueChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
16
+ updateNodeData(id, { value: e.target.value });
17
+ }, [id, updateNodeData]);
18
+
19
+ return (
20
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
21
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
22
+ }`}>
23
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
24
+ <div className="flex items-center gap-2 mb-2">
25
+ <Tag size={14} className="text-yellow-400" />
26
+ <span className="text-[12px] font-semibold">Set Card Metadata</span>
27
+ </div>
28
+ <div className="space-y-1.5">
29
+ <div>
30
+ <label className="text-[10px] text-content-muted uppercase tracking-wider">Key</label>
31
+ <input
32
+ type="text"
33
+ value={key}
34
+ onChange={handleKeyChange}
35
+ placeholder="e.g. dev_blocked"
36
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
37
+ />
38
+ </div>
39
+ <div>
40
+ <label className="text-[10px] text-content-muted uppercase tracking-wider">Value</label>
41
+ <input
42
+ type="text"
43
+ value={value}
44
+ onChange={handleValueChange}
45
+ placeholder="e.g. true"
46
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content"
47
+ />
48
+ </div>
49
+ </div>
50
+ <Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { Handle, Position } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { Play } from 'lucide-react';
5
+
6
+ export function StartNode({ selected }: NodeProps) {
7
+ return (
8
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[120px] text-center ${
9
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
10
+ }`}>
11
+ <div className="flex items-center justify-center gap-2">
12
+ <Play size={14} className="text-accent" />
13
+ <span className="text-[12px] font-semibold uppercase tracking-wider">Start</span>
14
+ </div>
15
+ <Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,59 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Handle, Position, useReactFlow } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { ArrowRightLeft } from 'lucide-react';
5
+ import { KANBAN_COLUMNS } from './workflow_types';
6
+ import type { KanbanColumn } from './workflow_types';
7
+
8
+ export function TransitionCardNode({ id, data, selected }: NodeProps) {
9
+ const { updateNodeData } = useReactFlow();
10
+ const sourceColumn = (data.sourceColumn as KanbanColumn) || 'todo';
11
+ const targetColumn = (data.targetColumn as KanbanColumn) || 'in_progress';
12
+
13
+ const handleSourceChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
14
+ updateNodeData(id, { sourceColumn: e.target.value });
15
+ }, [id, updateNodeData]);
16
+
17
+ const handleTargetChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
18
+ updateNodeData(id, { targetColumn: e.target.value });
19
+ }, [id, updateNodeData]);
20
+
21
+ return (
22
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
23
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
24
+ }`}>
25
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
26
+ <div className="flex items-center gap-2 mb-2">
27
+ <ArrowRightLeft size={14} className="text-blue-400" />
28
+ <span className="text-[12px] font-semibold">Transition Card</span>
29
+ </div>
30
+ <div className="space-y-1.5">
31
+ <div>
32
+ <label className="text-[10px] text-content-muted uppercase tracking-wider">From</label>
33
+ <select
34
+ value={sourceColumn}
35
+ onChange={handleSourceChange}
36
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
37
+ >
38
+ {KANBAN_COLUMNS.map(c => (
39
+ <option key={c} value={c}>{c.replace('_', ' ')}</option>
40
+ ))}
41
+ </select>
42
+ </div>
43
+ <div>
44
+ <label className="text-[10px] text-content-muted uppercase tracking-wider">To</label>
45
+ <select
46
+ value={targetColumn}
47
+ onChange={handleTargetChange}
48
+ className="w-full px-2 py-1 rounded border border-edge bg-surface text-[11px] text-content appearance-none"
49
+ >
50
+ {KANBAN_COLUMNS.map(c => (
51
+ <option key={c} value={c}>{c.replace('_', ' ')}</option>
52
+ ))}
53
+ </select>
54
+ </div>
55
+ </div>
56
+ <Handle type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
57
+ </div>
58
+ );
59
+ }