@assistkick/create 1.6.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 (214) hide show
  1. package/package.json +2 -2
  2. package/templates/assistkick-product-system/.env.example +1 -0
  3. package/templates/assistkick-product-system/local.db +0 -0
  4. package/templates/assistkick-product-system/package.json +4 -2
  5. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  6. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  10. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  12. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  13. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  14. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  15. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  18. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  19. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  24. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  28. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  30. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  33. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  40. package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
  42. package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
  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 +383 -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 +193 -64
  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 +226 -291
  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 +40 -66
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
  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 +155 -77
  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 +270 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
  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 +207 -0
  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/useGraph.ts +6 -21
  107. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
  108. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  134. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  138. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  139. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
  140. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  157. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  161. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  163. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  165. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
  166. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  167. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  168. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  171. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  172. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  173. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  174. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  175. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  176. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  177. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  178. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  179. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  180. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  181. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  182. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  183. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  184. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  185. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  186. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  187. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  188. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  189. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  190. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  191. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  192. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  193. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  194. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  195. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  196. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  197. package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
  198. package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
  199. package/templates/skills/assistkick-debugger/SKILL.md +2 -2
  200. package/templates/skills/assistkick-developer/SKILL.md +6 -3
  201. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  202. package/templates/skills/assistkick-interview/SKILL.md +2 -2
  203. package/templates/skills/product-system/graph.json +1890 -0
  204. package/templates/skills/product-system/kanban.json +304 -0
  205. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  206. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  207. package/templates/skills/product-system/nodes/data_001.md +51 -0
  208. package/templates/skills/product-system/nodes/data_002.md +40 -0
  209. package/templates/skills/product-system/nodes/data_004.md +38 -0
  210. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  211. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  212. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  213. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  214. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,313 @@
1
+ import React, { useState, useCallback, useRef } from 'react';
2
+ import { ChevronRight, ChevronDown, File, Folder, FolderOpen } from 'lucide-react';
3
+ import { FileTreeContextMenu } from './FileTreeContextMenu';
4
+ import { FileTreeInlineInput } from './FileTreeInlineInput';
5
+ import { getParentPath } from '../utils/file_utils';
6
+ import type { ContextMenuItem } from './FileTreeContextMenu';
7
+
8
+ export interface TreeEntry {
9
+ name: string;
10
+ path: string;
11
+ type: 'file' | 'directory';
12
+ children?: TreeEntry[];
13
+ }
14
+
15
+ export interface InlineInputState {
16
+ parentPath: string;
17
+ type: 'file' | 'directory';
18
+ mode: 'create' | 'rename';
19
+ initialValue: string;
20
+ targetPath?: string;
21
+ depth: number;
22
+ }
23
+
24
+ interface ContextMenuState {
25
+ x: number;
26
+ y: number;
27
+ items: ContextMenuItem[];
28
+ }
29
+
30
+ export interface FileTreeCallbacks {
31
+ onFileSelect: (path: string, name: string) => void;
32
+ onCreateFile: (parentPath: string, name: string, type: 'file' | 'directory') => void;
33
+ onRename: (oldPath: string, newName: string) => void;
34
+ onDelete: (path: string, type: 'file' | 'directory') => void;
35
+ }
36
+
37
+ interface FileTreeProps {
38
+ tree: TreeEntry[];
39
+ callbacks: FileTreeCallbacks;
40
+ selectedPath: string | null;
41
+ expandedPaths: Set<string>;
42
+ onToggleExpand: (path: string) => void;
43
+ }
44
+
45
+ interface FileTreeNodeProps {
46
+ entry: TreeEntry;
47
+ depth: number;
48
+ callbacks: FileTreeCallbacks;
49
+ selectedPath: string | null;
50
+ expandedPaths: Set<string>;
51
+ onToggleExpand: (path: string) => void;
52
+ inlineInput: InlineInputState | null;
53
+ onSetInlineInput: (state: InlineInputState | null) => void;
54
+ onContextMenu: (e: React.MouseEvent, entry: TreeEntry, depth: number) => void;
55
+ }
56
+
57
+ const ICON_SIZE = 14;
58
+
59
+ const FileTreeNode = ({
60
+ entry, depth, callbacks, selectedPath,
61
+ expandedPaths, onToggleExpand,
62
+ inlineInput, onSetInlineInput, onContextMenu,
63
+ }: FileTreeNodeProps) => {
64
+ const expanded = expandedPaths.has(entry.path);
65
+
66
+ const handleClick = useCallback(() => {
67
+ if (entry.type === 'directory') {
68
+ onToggleExpand(entry.path);
69
+ } else {
70
+ callbacks.onFileSelect(entry.path, entry.name);
71
+ }
72
+ }, [entry, callbacks, onToggleExpand]);
73
+
74
+ const handleContextMenu = useCallback((e: React.MouseEvent) => {
75
+ e.preventDefault();
76
+ e.stopPropagation();
77
+ onContextMenu(e, entry, depth);
78
+ }, [entry, depth, onContextMenu]);
79
+
80
+ const isSelected = entry.type === 'file' && entry.path === selectedPath;
81
+ const isRenaming = inlineInput?.mode === 'rename' && inlineInput.targetPath === entry.path;
82
+
83
+ // Show inline input for rename
84
+ if (isRenaming) {
85
+ return (
86
+ <FileTreeInlineInput
87
+ depth={depth}
88
+ initialValue={inlineInput!.initialValue}
89
+ type={entry.type}
90
+ onSubmit={(newName) => {
91
+ callbacks.onRename(entry.path, newName);
92
+ onSetInlineInput(null);
93
+ }}
94
+ onCancel={() => onSetInlineInput(null)}
95
+ />
96
+ );
97
+ }
98
+
99
+ // Check if there's a create-input targeting this directory
100
+ const showCreateInput = inlineInput?.mode === 'create' && inlineInput.parentPath === entry.path;
101
+
102
+ return (
103
+ <>
104
+ <button
105
+ className={`file-tree-node ${isSelected ? 'file-tree-node--selected' : ''}`}
106
+ style={{ paddingLeft: `${depth * 16 + 8}px` }}
107
+ onClick={handleClick}
108
+ onContextMenu={handleContextMenu}
109
+ title={entry.path}
110
+ >
111
+ {entry.type === 'directory' ? (
112
+ <>
113
+ <span className="file-tree-node__chevron">
114
+ {expanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
115
+ </span>
116
+ {expanded
117
+ ? <FolderOpen size={ICON_SIZE} className="file-tree-node__icon file-tree-node__icon--folder" />
118
+ : <Folder size={ICON_SIZE} className="file-tree-node__icon file-tree-node__icon--folder" />
119
+ }
120
+ </>
121
+ ) : (
122
+ <>
123
+ <span className="file-tree-node__chevron file-tree-node__chevron--hidden" />
124
+ <File size={ICON_SIZE} className="file-tree-node__icon" />
125
+ </>
126
+ )}
127
+ <span className="file-tree-node__name">{entry.name}</span>
128
+ </button>
129
+ {entry.type === 'directory' && expanded && (
130
+ <>
131
+ {showCreateInput && (
132
+ <FileTreeInlineInput
133
+ depth={depth + 1}
134
+ initialValue=""
135
+ type={inlineInput!.type}
136
+ onSubmit={(name) => {
137
+ callbacks.onCreateFile(entry.path, name, inlineInput!.type);
138
+ onSetInlineInput(null);
139
+ }}
140
+ onCancel={() => onSetInlineInput(null)}
141
+ />
142
+ )}
143
+ {entry.children?.map((child) => (
144
+ <FileTreeNode
145
+ key={child.path}
146
+ entry={child}
147
+ depth={depth + 1}
148
+ callbacks={callbacks}
149
+ selectedPath={selectedPath}
150
+ expandedPaths={expandedPaths}
151
+ onToggleExpand={onToggleExpand}
152
+ inlineInput={inlineInput}
153
+ onSetInlineInput={onSetInlineInput}
154
+ onContextMenu={onContextMenu}
155
+ />
156
+ ))}
157
+ </>
158
+ )}
159
+ </>
160
+ );
161
+ };
162
+
163
+ export const FileTree = ({ tree, callbacks, selectedPath, expandedPaths, onToggleExpand }: FileTreeProps) => {
164
+ const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
165
+ const [inlineInput, setInlineInput] = useState<InlineInputState | null>(null);
166
+ const treeRef = useRef<HTMLDivElement>(null);
167
+
168
+ const handleContextMenu = useCallback((e: React.MouseEvent, entry: TreeEntry, depth: number) => {
169
+ const items: ContextMenuItem[] = [];
170
+
171
+ if (entry.type === 'directory') {
172
+ items.push({
173
+ label: 'New File',
174
+ action: () => {
175
+ // Ensure directory is expanded
176
+ if (!expandedPaths.has(entry.path)) {
177
+ onToggleExpand(entry.path);
178
+ }
179
+ setInlineInput({
180
+ parentPath: entry.path,
181
+ type: 'file',
182
+ mode: 'create',
183
+ initialValue: '',
184
+ depth: depth + 1,
185
+ });
186
+ },
187
+ });
188
+ items.push({
189
+ label: 'New Folder',
190
+ action: () => {
191
+ if (!expandedPaths.has(entry.path)) {
192
+ onToggleExpand(entry.path);
193
+ }
194
+ setInlineInput({
195
+ parentPath: entry.path,
196
+ type: 'directory',
197
+ mode: 'create',
198
+ initialValue: '',
199
+ depth: depth + 1,
200
+ });
201
+ },
202
+ });
203
+ }
204
+
205
+ items.push({
206
+ label: 'Rename',
207
+ action: () => {
208
+ setInlineInput({
209
+ parentPath: entry.type === 'directory' ? entry.path : getParentPath(entry.path),
210
+ type: entry.type,
211
+ mode: 'rename',
212
+ initialValue: entry.name,
213
+ targetPath: entry.path,
214
+ depth,
215
+ });
216
+ },
217
+ });
218
+
219
+ items.push({
220
+ label: 'Delete',
221
+ action: () => {
222
+ const confirmMsg = entry.type === 'directory'
223
+ ? `Delete folder "${entry.name}" and all its contents?`
224
+ : `Delete "${entry.name}"?`;
225
+ if (window.confirm(confirmMsg)) {
226
+ callbacks.onDelete(entry.path, entry.type);
227
+ }
228
+ },
229
+ });
230
+
231
+ setContextMenu({ x: e.clientX, y: e.clientY, items });
232
+ }, [callbacks, expandedPaths, onToggleExpand]);
233
+
234
+ const handleRootContextMenu = useCallback((e: React.MouseEvent) => {
235
+ // Only trigger if clicking on the empty area (not on a tree node)
236
+ if ((e.target as HTMLElement).closest('.file-tree-node')) return;
237
+
238
+ e.preventDefault();
239
+ setContextMenu({
240
+ x: e.clientX,
241
+ y: e.clientY,
242
+ items: [
243
+ {
244
+ label: 'New File',
245
+ action: () => {
246
+ setInlineInput({
247
+ parentPath: '',
248
+ type: 'file',
249
+ mode: 'create',
250
+ initialValue: '',
251
+ depth: 0,
252
+ });
253
+ },
254
+ },
255
+ {
256
+ label: 'New Folder',
257
+ action: () => {
258
+ setInlineInput({
259
+ parentPath: '',
260
+ type: 'directory',
261
+ mode: 'create',
262
+ initialValue: '',
263
+ depth: 0,
264
+ });
265
+ },
266
+ },
267
+ ],
268
+ });
269
+ }, []);
270
+
271
+ // Check if there's a root-level create input (parentPath === '')
272
+ const showRootCreateInput = inlineInput?.mode === 'create' && inlineInput.parentPath === '';
273
+
274
+ return (
275
+ <div className="file-tree" ref={treeRef} onContextMenu={handleRootContextMenu}>
276
+ {showRootCreateInput && (
277
+ <FileTreeInlineInput
278
+ depth={0}
279
+ initialValue=""
280
+ type={inlineInput!.type}
281
+ onSubmit={(name) => {
282
+ callbacks.onCreateFile('', name, inlineInput!.type);
283
+ setInlineInput(null);
284
+ }}
285
+ onCancel={() => setInlineInput(null)}
286
+ />
287
+ )}
288
+ {tree.map((entry) => (
289
+ <FileTreeNode
290
+ key={entry.path}
291
+ entry={entry}
292
+ depth={0}
293
+ callbacks={callbacks}
294
+ selectedPath={selectedPath}
295
+ expandedPaths={expandedPaths}
296
+ onToggleExpand={onToggleExpand}
297
+ inlineInput={inlineInput}
298
+ onSetInlineInput={setInlineInput}
299
+ onContextMenu={handleContextMenu}
300
+ />
301
+ ))}
302
+ {contextMenu && (
303
+ <FileTreeContextMenu
304
+ x={contextMenu.x}
305
+ y={contextMenu.y}
306
+ items={contextMenu.items}
307
+ onClose={() => setContextMenu(null)}
308
+ />
309
+ )}
310
+ </div>
311
+ );
312
+ };
313
+
@@ -0,0 +1,61 @@
1
+ import React, { useEffect, useRef, useCallback } from 'react';
2
+
3
+ export interface ContextMenuItem {
4
+ label: string;
5
+ action: () => void;
6
+ }
7
+
8
+ interface FileTreeContextMenuProps {
9
+ x: number;
10
+ y: number;
11
+ items: ContextMenuItem[];
12
+ onClose: () => void;
13
+ }
14
+
15
+ export const FileTreeContextMenu = ({ x, y, items, onClose }: FileTreeContextMenuProps) => {
16
+ const menuRef = useRef<HTMLDivElement>(null);
17
+
18
+ const handleClickOutside = useCallback((e: MouseEvent) => {
19
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
20
+ onClose();
21
+ }
22
+ }, [onClose]);
23
+
24
+ useEffect(() => {
25
+ document.addEventListener('mousedown', handleClickOutside);
26
+ return () => document.removeEventListener('mousedown', handleClickOutside);
27
+ }, [handleClickOutside]);
28
+
29
+ useEffect(() => {
30
+ const handleEscape = (e: KeyboardEvent) => {
31
+ if (e.key === 'Escape') onClose();
32
+ };
33
+ document.addEventListener('keydown', handleEscape);
34
+ return () => document.removeEventListener('keydown', handleEscape);
35
+ }, [onClose]);
36
+
37
+ // Adjust position to stay within viewport
38
+ const style: React.CSSProperties = {
39
+ position: 'fixed',
40
+ left: x,
41
+ top: y,
42
+ zIndex: 1000,
43
+ };
44
+
45
+ return (
46
+ <div className="file-tree-context-menu" ref={menuRef} style={style}>
47
+ {items.map((item) => (
48
+ <button
49
+ key={item.label}
50
+ className="file-tree-context-menu__item"
51
+ onClick={() => {
52
+ item.action();
53
+ onClose();
54
+ }}
55
+ >
56
+ {item.label}
57
+ </button>
58
+ ))}
59
+ </div>
60
+ );
61
+ };
@@ -0,0 +1,73 @@
1
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
2
+ import { File, Folder } from 'lucide-react';
3
+
4
+ const ICON_SIZE = 14;
5
+
6
+ interface FileTreeInlineInputProps {
7
+ depth: number;
8
+ initialValue: string;
9
+ type: 'file' | 'directory';
10
+ onSubmit: (value: string) => void;
11
+ onCancel: () => void;
12
+ }
13
+
14
+ export const FileTreeInlineInput = ({ depth, initialValue, type, onSubmit, onCancel }: FileTreeInlineInputProps) => {
15
+ const [value, setValue] = useState(initialValue);
16
+ const inputRef = useRef<HTMLInputElement>(null);
17
+
18
+ useEffect(() => {
19
+ if (inputRef.current) {
20
+ inputRef.current.focus();
21
+ if (initialValue) {
22
+ // For rename: select the name part without extension
23
+ const dotIdx = initialValue.lastIndexOf('.');
24
+ if (dotIdx > 0 && type === 'file') {
25
+ inputRef.current.setSelectionRange(0, dotIdx);
26
+ } else {
27
+ inputRef.current.select();
28
+ }
29
+ }
30
+ }
31
+ }, [initialValue, type]);
32
+
33
+ const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
34
+ if (e.key === 'Enter') {
35
+ e.preventDefault();
36
+ const trimmed = value.trim();
37
+ if (trimmed) {
38
+ onSubmit(trimmed);
39
+ } else {
40
+ onCancel();
41
+ }
42
+ } else if (e.key === 'Escape') {
43
+ e.preventDefault();
44
+ onCancel();
45
+ }
46
+ }, [value, onSubmit, onCancel]);
47
+
48
+ const handleBlur = useCallback(() => {
49
+ onCancel();
50
+ }, [onCancel]);
51
+
52
+ return (
53
+ <div
54
+ className="file-tree-inline-input"
55
+ style={{ paddingLeft: `${depth * 16 + 8}px` }}
56
+ >
57
+ <span className="file-tree-node__chevron file-tree-node__chevron--hidden" />
58
+ {type === 'directory'
59
+ ? <Folder size={ICON_SIZE} className="file-tree-node__icon file-tree-node__icon--folder" />
60
+ : <File size={ICON_SIZE} className="file-tree-node__icon" />
61
+ }
62
+ <input
63
+ ref={inputRef}
64
+ className="file-tree-inline-input__field"
65
+ value={value}
66
+ onChange={(e) => setValue(e.target.value)}
67
+ onKeyDown={handleKeyDown}
68
+ onBlur={handleBlur}
69
+ spellCheck={false}
70
+ />
71
+ </div>
72
+ );
73
+ };