@assistkick/create 1.9.0 → 1.11.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 (206) hide show
  1. package/dist/src/scaffolder.d.ts +12 -1
  2. package/dist/src/scaffolder.js +40 -3
  3. package/dist/src/scaffolder.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/assistkick-product-system/package.json +1 -1
  6. package/templates/assistkick-product-system/packages/backend/package.json +1 -0
  7. package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
  9. package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
  10. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
  13. package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
  14. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
  15. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
  17. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
  18. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +391 -23
  19. package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
  20. package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
  21. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
  22. package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
  23. package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
  24. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
  25. package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
  26. package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
  27. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
  28. package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
  29. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
  30. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
  31. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
  32. package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
  33. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
  34. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
  35. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
  41. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
  42. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
  43. package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
  44. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
  45. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
  46. package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
  47. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
  48. package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
  49. package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
  50. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
  51. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
  52. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
  53. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
  54. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
  55. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
  56. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
  57. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
  58. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
  59. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
  60. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
  61. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +149 -14
  62. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
  63. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
  64. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
  65. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
  66. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
  67. package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
  68. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
  69. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
  70. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
  71. package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
  85. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
  86. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
  87. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
  88. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
  89. package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
  92. package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
  97. package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
  99. package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
  102. package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
  105. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
  106. package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
  107. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
  108. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
  109. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
  110. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
  111. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
  112. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
  113. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
  114. package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
  131. package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
  134. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
  138. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
  139. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
  140. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
  141. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
  142. package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
  143. package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
  144. package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
  145. package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
  146. package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
  147. package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
  148. package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
  149. package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
  150. package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
  151. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
  152. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
  153. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
  154. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
  155. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
  156. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
  157. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
  161. package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
  162. package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
  163. package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
  164. package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
  165. package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
  166. package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
  167. package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
  168. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
  169. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
  170. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
  171. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
  172. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
  173. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
  174. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
  175. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
  176. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
  177. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
  178. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  179. package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
  180. package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
  181. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
  182. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
  183. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
  184. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
  185. package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
  186. package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
  187. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
  188. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
  189. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
  190. package/templates/assistkick-product-system/packages/shared/package.json +2 -1
  191. package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
  192. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
  193. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
  194. package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
  195. package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
  196. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
  197. package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
  198. package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
  199. package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
  200. package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
  201. package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
  202. package/templates/skills/assistkick-debugger/SKILL.md +23 -23
  203. package/templates/skills/assistkick-developer/SKILL.md +59 -63
  204. package/templates/skills/assistkick-interview/SKILL.md +26 -26
  205. package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
  206. package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Commit Message Modal — prompts user to enter a commit message before switching branches
3
+ * when there are uncommitted changes.
4
+ */
5
+
6
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
7
+
8
+ interface CommitMessageModalProps {
9
+ isOpen: boolean;
10
+ targetBranch: string;
11
+ onConfirm: (message: string) => void;
12
+ onCancel: () => void;
13
+ }
14
+
15
+ export const CommitMessageModal = ({ isOpen, targetBranch, onConfirm, onCancel }: CommitMessageModalProps) => {
16
+ const [message, setMessage] = useState('');
17
+ const inputRef = useRef<HTMLInputElement>(null);
18
+
19
+ useEffect(() => {
20
+ if (isOpen) {
21
+ setMessage('');
22
+ setTimeout(() => inputRef.current?.focus(), 50);
23
+ }
24
+ }, [isOpen]);
25
+
26
+ const handleSubmit = useCallback((e: React.FormEvent) => {
27
+ e.preventDefault();
28
+ const trimmed = message.trim();
29
+ if (!trimmed) return;
30
+ onConfirm(trimmed);
31
+ }, [message, onConfirm]);
32
+
33
+ if (!isOpen) return null;
34
+
35
+ return (
36
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[1000]" onClick={onCancel}>
37
+ <div className="bg-surface border border-edge rounded-xl p-6 w-[440px] max-w-[90vw] shadow-[0_8px_32px_rgba(0,0,0,0.3)]" onClick={e => e.stopPropagation()}>
38
+ <div className="flex justify-between items-center mb-4">
39
+ <h2 className="m-0 text-lg text-content">Uncommitted Changes</h2>
40
+ <button className="bg-none border-none text-[22px] text-content-secondary cursor-pointer px-1 leading-none hover:text-content" onClick={onCancel} type="button">&times;</button>
41
+ </div>
42
+
43
+ <p className="text-[13px] text-content-secondary mb-3">
44
+ You have uncommitted changes. Enter a commit message to save them before switching to <span className="font-mono text-accent">{targetBranch}</span>.
45
+ </p>
46
+
47
+ <form onSubmit={handleSubmit} noValidate>
48
+ <div className="flex flex-col gap-1">
49
+ <label className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider" htmlFor="commit-message">
50
+ Commit message
51
+ </label>
52
+ <input
53
+ ref={inputRef}
54
+ id="commit-message"
55
+ className="w-full h-9 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-[13px] outline-none transition-[border-color] duration-150 focus:border-accent placeholder:text-content-muted"
56
+ value={message}
57
+ onChange={e => setMessage(e.target.value)}
58
+ placeholder="Save work before switching branch..."
59
+ />
60
+ </div>
61
+
62
+ <div className="flex gap-2 mt-4">
63
+ <button
64
+ className="flex-1 h-9 bg-transparent border border-edge rounded text-content-secondary font-mono text-[13px] cursor-pointer transition-[background,color] duration-150 hover:bg-surface-raised"
65
+ type="button"
66
+ onClick={onCancel}
67
+ >
68
+ Cancel
69
+ </button>
70
+ <button
71
+ className="flex-1 h-9 bg-transparent border border-accent rounded text-accent font-mono text-[13px] cursor-pointer transition-[background,color] duration-150 hover:bg-accent hover:text-white disabled:opacity-40 disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-accent"
72
+ type="submit"
73
+ disabled={!message.trim()}
74
+ >
75
+ Commit & Switch
76
+ </button>
77
+ </div>
78
+ </form>
79
+ </div>
80
+ </div>
81
+ );
82
+ };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * DiagramOverlay — fullscreen overlay for viewing a mermaid SVG diagram.
3
+ *
4
+ * Supports zoom (scroll wheel / pinch) and pan (click-drag).
5
+ * Closed via the X button or pressing Escape.
6
+ */
7
+
8
+ import { useState, useRef, useCallback, useEffect } from 'react';
9
+ import { X, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react';
10
+ import { clampZoom } from '../lib/mermaid_helpers';
11
+
12
+ const ZOOM_STEP = 0.15;
13
+
14
+ interface DiagramOverlayProps {
15
+ svgHtml: string;
16
+ onClose: () => void;
17
+ }
18
+
19
+ export const DiagramOverlay = ({ svgHtml, onClose }: DiagramOverlayProps) => {
20
+ const [zoom, setZoom] = useState(1);
21
+ const [pan, setPan] = useState({ x: 0, y: 0 });
22
+ const [dragging, setDragging] = useState(false);
23
+ const dragStart = useRef({ x: 0, y: 0 });
24
+ const panStart = useRef({ x: 0, y: 0 });
25
+ const contentRef = useRef<HTMLDivElement>(null);
26
+
27
+ // Escape key handler
28
+ useEffect(() => {
29
+ const handleKeyDown = (e: KeyboardEvent) => {
30
+ if (e.key === 'Escape') {
31
+ onClose();
32
+ }
33
+ };
34
+
35
+ window.addEventListener('keydown', handleKeyDown);
36
+ return () => window.removeEventListener('keydown', handleKeyDown);
37
+ }, [onClose]);
38
+
39
+ // Prevent body scroll while overlay is open
40
+ useEffect(() => {
41
+ const prev = document.body.style.overflow;
42
+ document.body.style.overflow = 'hidden';
43
+ return () => {
44
+ document.body.style.overflow = prev;
45
+ };
46
+ }, []);
47
+
48
+ const handleWheel = useCallback((e: React.WheelEvent) => {
49
+ e.preventDefault();
50
+ const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
51
+ setZoom((z) => clampZoom(z, delta));
52
+ }, []);
53
+
54
+ const handlePointerDown = useCallback(
55
+ (e: React.PointerEvent) => {
56
+ if (e.button !== 0) return;
57
+ setDragging(true);
58
+ dragStart.current = { x: e.clientX, y: e.clientY };
59
+ panStart.current = { ...pan };
60
+ (e.target as HTMLElement).setPointerCapture(e.pointerId);
61
+ },
62
+ [pan],
63
+ );
64
+
65
+ const handlePointerMove = useCallback(
66
+ (e: React.PointerEvent) => {
67
+ if (!dragging) return;
68
+ const dx = e.clientX - dragStart.current.x;
69
+ const dy = e.clientY - dragStart.current.y;
70
+ setPan({ x: panStart.current.x + dx, y: panStart.current.y + dy });
71
+ },
72
+ [dragging],
73
+ );
74
+
75
+ const handlePointerUp = useCallback(() => {
76
+ setDragging(false);
77
+ }, []);
78
+
79
+ const resetView = useCallback(() => {
80
+ setZoom(1);
81
+ setPan({ x: 0, y: 0 });
82
+ }, []);
83
+
84
+ const zoomIn = useCallback(() => {
85
+ setZoom((z) => clampZoom(z, ZOOM_STEP));
86
+ }, []);
87
+
88
+ const zoomOut = useCallback(() => {
89
+ setZoom((z) => clampZoom(z, -ZOOM_STEP));
90
+ }, []);
91
+
92
+ return (
93
+ <div className="fixed inset-0 z-[1000] flex flex-col bg-black/80 backdrop-blur-sm">
94
+ {/* Toolbar */}
95
+ <div className="flex items-center justify-between px-4 py-2 bg-surface/90 border-b border-edge">
96
+ <div className="flex items-center gap-2">
97
+ <button
98
+ type="button"
99
+ onClick={zoomOut}
100
+ className="p-1.5 rounded hover:bg-surface-raised text-content-secondary hover:text-content cursor-pointer"
101
+ aria-label="Zoom out"
102
+ >
103
+ <ZoomOut size={16} />
104
+ </button>
105
+ <span className="text-xs text-content-secondary font-mono min-w-[3.5rem] text-center">
106
+ {Math.round(zoom * 100)}%
107
+ </span>
108
+ <button
109
+ type="button"
110
+ onClick={zoomIn}
111
+ className="p-1.5 rounded hover:bg-surface-raised text-content-secondary hover:text-content cursor-pointer"
112
+ aria-label="Zoom in"
113
+ >
114
+ <ZoomIn size={16} />
115
+ </button>
116
+ <button
117
+ type="button"
118
+ onClick={resetView}
119
+ className="p-1.5 rounded hover:bg-surface-raised text-content-secondary hover:text-content cursor-pointer"
120
+ aria-label="Reset view"
121
+ >
122
+ <RotateCcw size={16} />
123
+ </button>
124
+ </div>
125
+ <button
126
+ type="button"
127
+ onClick={onClose}
128
+ className="p-1.5 rounded hover:bg-surface-raised text-content-secondary hover:text-content cursor-pointer"
129
+ aria-label="Close overlay"
130
+ >
131
+ <X size={18} />
132
+ </button>
133
+ </div>
134
+
135
+ {/* Diagram viewport */}
136
+ <div
137
+ ref={contentRef}
138
+ className="flex-1 overflow-hidden select-none"
139
+ style={{ cursor: dragging ? 'grabbing' : 'grab' }}
140
+ onWheel={handleWheel}
141
+ onPointerDown={handlePointerDown}
142
+ onPointerMove={handlePointerMove}
143
+ onPointerUp={handlePointerUp}
144
+ >
145
+ <div
146
+ className="w-full h-full flex items-center justify-center"
147
+ style={{
148
+ transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
149
+ transformOrigin: 'center center',
150
+ }}
151
+ >
152
+ <div
153
+ className="[&_svg]:max-w-none [&_svg]:h-auto"
154
+ dangerouslySetInnerHTML={{ __html: svgHtml }}
155
+ />
156
+ </div>
157
+ </div>
158
+ </div>
159
+ );
160
+ };
@@ -31,19 +31,19 @@ export const EditorTabBar = ({ tabs, activeTabPath, dirtyPaths, onSelectTab, onC
31
31
  if (tabs.length === 0) return null;
32
32
 
33
33
  return (
34
- <div className="editor-tab-bar">
34
+ <div className="flex items-stretch h-[34px] bg-surface-alt border-b border-edge overflow-x-auto overflow-y-hidden shrink-0 [&::-webkit-scrollbar]:h-0">
35
35
  {tabs.map((tab) => (
36
36
  <button
37
37
  key={tab.path}
38
- className={`editor-tab ${tab.path === activeTabPath ? 'editor-tab--active' : ''}`}
38
+ className={`flex items-center gap-1.5 px-3 border-none border-r border-edge bg-transparent text-content-muted font-mono text-xs cursor-pointer whitespace-nowrap shrink-0 hover:text-content hover:bg-tab-hover ${tab.path === activeTabPath ? 'text-content bg-tab-active border-b-2 border-b-accent' : ''}`}
39
39
  onClick={() => onSelectTab(tab.path)}
40
40
  onMouseDown={(e) => handleMiddleClick(e, tab.path)}
41
41
  title={tab.path}
42
42
  >
43
- {dirtyPaths.has(tab.path) && <span className="editor-tab__dirty">●</span>}
44
- <span className="editor-tab__name">{tab.name}</span>
43
+ {dirtyPaths.has(tab.path) && <span className="text-accent-secondary text-[10px]">&bull;</span>}
44
+ <span className="max-w-[150px] overflow-hidden text-ellipsis">{tab.name}</span>
45
45
  <span
46
- className="editor-tab__close"
46
+ className="inline-flex items-center justify-center w-4 h-4 rounded-sm opacity-50 cursor-pointer hover:opacity-100 hover:bg-white/10"
47
47
  onClick={(e) => handleCloseClick(e, tab.path)}
48
48
  role="button"
49
49
  tabIndex={-1}
@@ -102,7 +102,7 @@ const FileTreeNode = ({
102
102
  return (
103
103
  <>
104
104
  <button
105
- className={`file-tree-node ${isSelected ? 'file-tree-node--selected' : ''}`}
105
+ className={`flex items-center gap-1 w-full h-[26px] border-none bg-transparent text-content-secondary font-mono text-xs cursor-pointer text-left whitespace-nowrap overflow-hidden text-ellipsis hover:bg-tab-hover hover:text-content ${isSelected ? 'bg-tab-active text-content' : ''}`}
106
106
  style={{ paddingLeft: `${depth * 16 + 8}px` }}
107
107
  onClick={handleClick}
108
108
  onContextMenu={handleContextMenu}
@@ -110,21 +110,21 @@ const FileTreeNode = ({
110
110
  >
111
111
  {entry.type === 'directory' ? (
112
112
  <>
113
- <span className="file-tree-node__chevron">
113
+ <span className="inline-flex items-center justify-center w-3.5 shrink-0">
114
114
  {expanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
115
115
  </span>
116
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" />
117
+ ? <FolderOpen size={ICON_SIZE} className="shrink-0 text-accent" />
118
+ : <Folder size={ICON_SIZE} className="shrink-0 text-accent" />
119
119
  }
120
120
  </>
121
121
  ) : (
122
122
  <>
123
- <span className="file-tree-node__chevron file-tree-node__chevron--hidden" />
124
- <File size={ICON_SIZE} className="file-tree-node__icon" />
123
+ <span className="inline-flex items-center justify-center w-3.5 shrink-0 invisible" />
124
+ <File size={ICON_SIZE} className="shrink-0 text-content-muted" />
125
125
  </>
126
126
  )}
127
- <span className="file-tree-node__name">{entry.name}</span>
127
+ <span className="overflow-hidden text-ellipsis">{entry.name}</span>
128
128
  </button>
129
129
  {entry.type === 'directory' && expanded && (
130
130
  <>
@@ -233,7 +233,7 @@ export const FileTree = ({ tree, callbacks, selectedPath, expandedPaths, onToggl
233
233
 
234
234
  const handleRootContextMenu = useCallback((e: React.MouseEvent) => {
235
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;
236
+ if ((e.target as HTMLElement).closest('button')) return;
237
237
 
238
238
  e.preventDefault();
239
239
  setContextMenu({
@@ -272,7 +272,7 @@ export const FileTree = ({ tree, callbacks, selectedPath, expandedPaths, onToggl
272
272
  const showRootCreateInput = inlineInput?.mode === 'create' && inlineInput.parentPath === '';
273
273
 
274
274
  return (
275
- <div className="file-tree" ref={treeRef} onContextMenu={handleRootContextMenu}>
275
+ <div className="flex flex-col py-1 select-none min-h-full" ref={treeRef} onContextMenu={handleRootContextMenu}>
276
276
  {showRootCreateInput && (
277
277
  <FileTreeInlineInput
278
278
  depth={0}
@@ -310,4 +310,3 @@ export const FileTree = ({ tree, callbacks, selectedPath, expandedPaths, onToggl
310
310
  </div>
311
311
  );
312
312
  };
313
-
@@ -51,17 +51,17 @@ export const FileTreeInlineInput = ({ depth, initialValue, type, onSubmit, onCan
51
51
 
52
52
  return (
53
53
  <div
54
- className="file-tree-inline-input"
54
+ className="flex items-center gap-1 h-[26px]"
55
55
  style={{ paddingLeft: `${depth * 16 + 8}px` }}
56
56
  >
57
- <span className="file-tree-node__chevron file-tree-node__chevron--hidden" />
57
+ <span className="inline-flex items-center justify-center w-3.5 shrink-0 invisible" />
58
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" />
59
+ ? <Folder size={ICON_SIZE} className="shrink-0 text-accent" />
60
+ : <File size={ICON_SIZE} className="shrink-0 text-content-muted" />
61
61
  }
62
62
  <input
63
63
  ref={inputRef}
64
- className="file-tree-inline-input__field"
64
+ className="flex-1 min-w-0 h-5 px-1 border border-accent rounded-sm bg-surface text-content font-mono text-xs outline-none"
65
65
  value={value}
66
66
  onChange={(e) => setValue(e.target.value)}
67
67
  onKeyDown={handleKeyDown}
@@ -2,6 +2,8 @@ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
2
  import Editor, { type OnMount } from '@monaco-editor/react';
3
3
  import { FileTree } from './FileTree';
4
4
  import { EditorTabBar } from './EditorTabBar';
5
+ import { UnifiedGitWidget } from './UnifiedGitWidget';
6
+ import { CommitMessageModal } from './CommitMessageModal';
5
7
  import { useTheme } from '../hooks/useTheme';
6
8
  import { useAutoSave } from '../hooks/useAutoSave';
7
9
  import { apiClient } from '../api/client';
@@ -17,19 +19,9 @@ interface FilesViewProps {
17
19
  projectId: string | null;
18
20
  }
19
21
 
20
- const collectExpandedPaths = (tree: TreeEntry[], depth: number): Set<string> => {
21
- const paths = new Set<string>();
22
- for (const entry of tree) {
23
- if (entry.type === 'directory' && depth === 0) {
24
- paths.add(entry.path);
25
- }
26
- if (entry.children) {
27
- for (const p of collectExpandedPaths(entry.children, depth + 1)) {
28
- paths.add(p);
29
- }
30
- }
31
- }
32
- return paths;
22
+ /** All directories start collapsed returns an empty set. */
23
+ const collectExpandedPaths = (_tree: TreeEntry[], _depth: number): Set<string> => {
24
+ return new Set<string>();
33
25
  };
34
26
 
35
27
  export const FilesView = ({ projectId }: FilesViewProps) => {
@@ -45,6 +37,8 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
45
37
  const [treeError, setTreeError] = useState<string | null>(null);
46
38
  const [treeWidth, setTreeWidth] = useState(DEFAULT_TREE_WIDTH);
47
39
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());
40
+ const [commitModalOpen, setCommitModalOpen] = useState(false);
41
+ const [pendingBranch, setPendingBranch] = useState<string | null>(null);
48
42
  const resizingRef = useRef(false);
49
43
  const containerRef = useRef<HTMLDivElement>(null);
50
44
  const initialExpandDone = useRef(false);
@@ -79,6 +73,60 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
79
73
  loadTree();
80
74
  }, [loadTree]);
81
75
 
76
+ // After a branch switch, reload the tree and reconcile open tabs
77
+ const handleBranchSwitch = useCallback(async () => {
78
+ if (!projectId) return;
79
+ initialExpandDone.current = false;
80
+ await loadTree();
81
+
82
+ // Reload open tabs — close those whose files no longer exist on the new branch
83
+ const newFileContents: Record<string, string> = {};
84
+ const survivingTabs: EditorTab[] = [];
85
+
86
+ for (const tab of tabs) {
87
+ try {
88
+ const data = await apiClient.fetchFileContent(projectId, tab.path);
89
+ if (data.binary) {
90
+ newFileContents[tab.path] = `[Binary file — ${data.size} bytes]`;
91
+ } else {
92
+ newFileContents[tab.path] = data.content;
93
+ }
94
+ survivingTabs.push(tab);
95
+ } catch {
96
+ // File doesn't exist on new branch — drop this tab
97
+ }
98
+ }
99
+
100
+ setTabs(survivingTabs);
101
+ setFileContents(newFileContents);
102
+
103
+ if (activeTabPath && !survivingTabs.find(t => t.path === activeTabPath)) {
104
+ setActiveTabPath(survivingTabs.length > 0 ? survivingTabs[0].path : null);
105
+ }
106
+ }, [projectId, loadTree, tabs, activeTabPath]);
107
+
108
+ const handleDirtyCheckout = useCallback((branch: string) => {
109
+ setPendingBranch(branch);
110
+ setCommitModalOpen(true);
111
+ }, []);
112
+
113
+ const handleCommitAndSwitch = useCallback(async (message: string) => {
114
+ if (!projectId || !pendingBranch) return;
115
+ setCommitModalOpen(false);
116
+ try {
117
+ await apiClient.checkoutBranch(projectId, pendingBranch, message);
118
+ setPendingBranch(null);
119
+ handleBranchSwitch();
120
+ } catch (err: any) {
121
+ console.error('Failed to commit and switch:', err);
122
+ }
123
+ }, [projectId, pendingBranch, handleBranchSwitch]);
124
+
125
+ const handleCommitModalCancel = useCallback(() => {
126
+ setCommitModalOpen(false);
127
+ setPendingBranch(null);
128
+ }, []);
129
+
82
130
  const handleToggleExpand = useCallback((path: string) => {
83
131
  setExpandedPaths((prev) => {
84
132
  const next = new Set(prev);
@@ -316,7 +364,7 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
316
364
  // No project selected
317
365
  if (!projectId) {
318
366
  return (
319
- <div className="files-view__empty">
367
+ <div className="flex items-center justify-center h-full w-full">
320
368
  <p className="text-content-muted">Select a project to browse files</p>
321
369
  </div>
322
370
  );
@@ -325,7 +373,7 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
325
373
  // Workspace error (no git repo connected)
326
374
  if (treeError) {
327
375
  return (
328
- <div className="files-view__empty">
376
+ <div className="flex items-center justify-center h-full w-full">
329
377
  <p className="text-content-muted">{treeError}</p>
330
378
  </div>
331
379
  );
@@ -335,13 +383,29 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
335
383
  const activeContent = activeTabPath ? (fileContents[activeTabPath] ?? '') : '';
336
384
 
337
385
  return (
338
- <div className="files-view" ref={containerRef}>
386
+ <div className="flex w-full h-full overflow-hidden" ref={containerRef}>
387
+ {/* Commit message modal for dirty branch switching */}
388
+ <CommitMessageModal
389
+ isOpen={commitModalOpen}
390
+ targetBranch={pendingBranch || ''}
391
+ onConfirm={handleCommitAndSwitch}
392
+ onCancel={handleCommitModalCancel}
393
+ />
394
+
339
395
  {/* File tree sidebar */}
340
- <div className="files-view__tree" style={{ width: `${treeWidth}px`, minWidth: `${treeWidth}px` }}>
341
- <div className="files-view__tree-header">
396
+ <div
397
+ className="flex flex-col h-full overflow-hidden border-r border-edge bg-surface-alt shrink-0"
398
+ style={{ width: `${treeWidth}px`, minWidth: `${treeWidth}px` }}
399
+ >
400
+ <UnifiedGitWidget
401
+ projectId={projectId}
402
+ onBranchSwitch={handleBranchSwitch}
403
+ onDirtyCheckout={handleDirtyCheckout}
404
+ />
405
+ <div className="px-3 pt-2 pb-1 select-none">
342
406
  <span className="text-[11px] uppercase tracking-wider font-medium text-content-muted">Explorer</span>
343
407
  </div>
344
- <div className="files-view__tree-content">
408
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
345
409
  {tree.length === 0 ? (
346
410
  <p className="text-content-muted text-xs p-3">No files found</p>
347
411
  ) : (
@@ -357,10 +421,13 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
357
421
  </div>
358
422
 
359
423
  {/* Resize handle */}
360
- <div className="files-view__resize-handle" onMouseDown={handleResizeStart} />
424
+ <div
425
+ className="w-1 shrink-0 cursor-col-resize transition-colors hover:bg-accent active:bg-accent"
426
+ onMouseDown={handleResizeStart}
427
+ />
361
428
 
362
429
  {/* Editor area */}
363
- <div className="files-view__editor">
430
+ <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
364
431
  <EditorTabBar
365
432
  tabs={tabs}
366
433
  activeTabPath={activeTabPath}
@@ -369,32 +436,36 @@ export const FilesView = ({ projectId }: FilesViewProps) => {
369
436
  onCloseTab={handleCloseTab}
370
437
  />
371
438
  {activeTab ? (
372
- <div className="files-view__editor-container">
439
+ <div className="flex-1 relative overflow-hidden">
373
440
  {loading && (
374
- <div className="files-view__loading">
441
+ <div className="absolute inset-0 flex items-center justify-center bg-surface opacity-70 z-5">
375
442
  <span className="text-content-muted text-sm">Loading...</span>
376
443
  </div>
377
444
  )}
378
- <Editor
379
- language={activeTab.language}
380
- value={activeContent}
381
- theme={theme === 'dark' ? 'vs-dark' : 'vs'}
382
- path={activeTab.path}
383
- onChange={handleEditorChange}
384
- onMount={handleEditorMount}
385
- options={{
386
- minimap: { enabled: true },
387
- fontSize: 13,
388
- lineNumbers: 'on',
389
- scrollBeyondLastLine: false,
390
- automaticLayout: true,
391
- tabSize: 2,
392
- wordWrap: 'off',
393
- }}
394
- />
445
+ <div className="absolute inset-0">
446
+ <Editor
447
+ width="100%"
448
+ height="100%"
449
+ language={activeTab.language}
450
+ value={activeContent}
451
+ theme={theme === 'dark' ? 'vs-dark' : 'vs'}
452
+ path={activeTab.path}
453
+ onChange={handleEditorChange}
454
+ onMount={handleEditorMount}
455
+ options={{
456
+ minimap: { enabled: true },
457
+ fontSize: 13,
458
+ lineNumbers: 'on',
459
+ scrollBeyondLastLine: false,
460
+ automaticLayout: true,
461
+ tabSize: 2,
462
+ wordWrap: 'off',
463
+ }}
464
+ />
465
+ </div>
395
466
  </div>
396
467
  ) : (
397
- <div className="files-view__empty-editor">
468
+ <div className="flex-1 flex items-center justify-center w-full">
398
469
  <p className="text-content-muted text-sm">Open a file from the explorer to start editing</p>
399
470
  </div>
400
471
  )}
@@ -72,9 +72,9 @@ export function GraphLegend({ visible, onTypeToggle, onSearchChange }: GraphLege
72
72
  <div className="mt-2 first:mt-0">
73
73
  <div className="text-[10px] text-content-muted uppercase tracking-wider mb-1">Node Size</div>
74
74
  <div className="flex items-center gap-1.5">
75
- <span className="w-2 h-2 rounded-full" style={{ background: 'var(--text-muted)' }} />
75
+ <span className="w-2 h-2 rounded-full bg-content-muted" />
76
76
  <span className="text-[10px] text-content-muted">{'\u2192'}</span>
77
- <span className="w-[18px] h-[18px] rounded-full" style={{ background: 'var(--text-muted)' }} />
77
+ <span className="w-[18px] h-[18px] rounded-full bg-content-muted" />
78
78
  <span className="text-[11px] text-content-secondary whitespace-nowrap">more connections = larger</span>
79
79
  </div>
80
80
  </div>