@myrialabs/clopen 0.0.1

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 (456) hide show
  1. package/.env.example +6 -0
  2. package/.github/workflows/release.yml +60 -0
  3. package/.github/workflows/test.yml +40 -0
  4. package/CONTRIBUTING.md +499 -0
  5. package/LICENSE +21 -0
  6. package/README.md +209 -0
  7. package/backend/index.ts +156 -0
  8. package/backend/lib/chat/helpers.ts +42 -0
  9. package/backend/lib/chat/index.ts +2 -0
  10. package/backend/lib/chat/stream-manager.ts +1126 -0
  11. package/backend/lib/database/README.md +77 -0
  12. package/backend/lib/database/index.ts +119 -0
  13. package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
  14. package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
  15. package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
  16. package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
  17. package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
  18. package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
  19. package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
  20. package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
  21. package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
  22. package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
  23. package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
  24. package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
  25. package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
  26. package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
  27. package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
  28. package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
  29. package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
  30. package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
  31. package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
  32. package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
  33. package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
  34. package/backend/lib/database/migrations/index.ts +154 -0
  35. package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
  36. package/backend/lib/database/queries/engine-queries.ts +75 -0
  37. package/backend/lib/database/queries/index.ts +9 -0
  38. package/backend/lib/database/queries/message-queries.ts +472 -0
  39. package/backend/lib/database/queries/project-queries.ts +117 -0
  40. package/backend/lib/database/queries/session-queries.ts +271 -0
  41. package/backend/lib/database/queries/settings-queries.ts +34 -0
  42. package/backend/lib/database/queries/snapshot-queries.ts +326 -0
  43. package/backend/lib/database/queries/utils-queries.ts +59 -0
  44. package/backend/lib/database/seeders/index.ts +13 -0
  45. package/backend/lib/database/seeders/settings_seeder.ts +84 -0
  46. package/backend/lib/database/utils/connection.ts +174 -0
  47. package/backend/lib/database/utils/index.ts +4 -0
  48. package/backend/lib/database/utils/migration-runner.ts +118 -0
  49. package/backend/lib/database/utils/seeder-runner.ts +121 -0
  50. package/backend/lib/engine/adapters/claude/environment.ts +164 -0
  51. package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
  52. package/backend/lib/engine/adapters/claude/index.ts +1 -0
  53. package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
  54. package/backend/lib/engine/adapters/claude/stream.ts +177 -0
  55. package/backend/lib/engine/adapters/opencode/index.ts +2 -0
  56. package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
  57. package/backend/lib/engine/adapters/opencode/server.ts +104 -0
  58. package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
  59. package/backend/lib/engine/index.ts +196 -0
  60. package/backend/lib/engine/types.ts +58 -0
  61. package/backend/lib/files/file-operations.ts +478 -0
  62. package/backend/lib/files/file-reading.ts +308 -0
  63. package/backend/lib/files/file-watcher.ts +383 -0
  64. package/backend/lib/files/path-browsing.ts +382 -0
  65. package/backend/lib/git/git-executor.ts +88 -0
  66. package/backend/lib/git/git-parser.ts +411 -0
  67. package/backend/lib/git/git-service.ts +505 -0
  68. package/backend/lib/mcp/README.md +1144 -0
  69. package/backend/lib/mcp/config.ts +316 -0
  70. package/backend/lib/mcp/index.ts +35 -0
  71. package/backend/lib/mcp/project-context.ts +236 -0
  72. package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
  73. package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
  74. package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
  75. package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
  76. package/backend/lib/mcp/servers/helper.ts +143 -0
  77. package/backend/lib/mcp/servers/index.ts +45 -0
  78. package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
  79. package/backend/lib/mcp/servers/weather/index.ts +31 -0
  80. package/backend/lib/mcp/stdio-server.ts +103 -0
  81. package/backend/lib/mcp/types.ts +65 -0
  82. package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
  83. package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
  84. package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
  85. package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
  86. package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
  87. package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
  88. package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
  89. package/backend/lib/preview/browser/browser-pool.ts +357 -0
  90. package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
  91. package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
  92. package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
  93. package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
  94. package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
  95. package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
  96. package/backend/lib/preview/browser/types.ts +359 -0
  97. package/backend/lib/preview/index.ts +24 -0
  98. package/backend/lib/project/index.ts +2 -0
  99. package/backend/lib/project/status-manager.ts +182 -0
  100. package/backend/lib/shared/index.ts +2 -0
  101. package/backend/lib/shared/port-utils.ts +25 -0
  102. package/backend/lib/shared/process-manager.ts +281 -0
  103. package/backend/lib/snapshot/blob-store.ts +227 -0
  104. package/backend/lib/snapshot/gitignore.ts +307 -0
  105. package/backend/lib/snapshot/helpers.ts +397 -0
  106. package/backend/lib/snapshot/snapshot-service.ts +483 -0
  107. package/backend/lib/terminal/helpers.ts +14 -0
  108. package/backend/lib/terminal/index.ts +8 -0
  109. package/backend/lib/terminal/pty-manager.ts +4 -0
  110. package/backend/lib/terminal/pty-session-manager.ts +387 -0
  111. package/backend/lib/terminal/shell-utils.ts +313 -0
  112. package/backend/lib/terminal/stream-manager.ts +293 -0
  113. package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
  114. package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
  115. package/backend/lib/user/helpers.ts +87 -0
  116. package/backend/lib/utils/ws.ts +944 -0
  117. package/backend/lib/vite-dev.ts +353 -0
  118. package/backend/middleware/cors.ts +15 -0
  119. package/backend/middleware/error-handler.ts +49 -0
  120. package/backend/middleware/logger.ts +9 -0
  121. package/backend/types/api.ts +24 -0
  122. package/backend/ws/README.md +1505 -0
  123. package/backend/ws/chat/background.ts +198 -0
  124. package/backend/ws/chat/index.ts +21 -0
  125. package/backend/ws/chat/stream.ts +707 -0
  126. package/backend/ws/engine/claude/accounts.ts +401 -0
  127. package/backend/ws/engine/claude/index.ts +13 -0
  128. package/backend/ws/engine/claude/status.ts +43 -0
  129. package/backend/ws/engine/index.ts +14 -0
  130. package/backend/ws/engine/opencode/index.ts +11 -0
  131. package/backend/ws/engine/opencode/status.ts +30 -0
  132. package/backend/ws/engine/utils.ts +36 -0
  133. package/backend/ws/files/index.ts +30 -0
  134. package/backend/ws/files/read.ts +189 -0
  135. package/backend/ws/files/search.ts +453 -0
  136. package/backend/ws/files/watch.ts +124 -0
  137. package/backend/ws/files/write.ts +143 -0
  138. package/backend/ws/git/branch.ts +106 -0
  139. package/backend/ws/git/commit.ts +39 -0
  140. package/backend/ws/git/conflict.ts +68 -0
  141. package/backend/ws/git/diff.ts +69 -0
  142. package/backend/ws/git/index.ts +24 -0
  143. package/backend/ws/git/log.ts +41 -0
  144. package/backend/ws/git/remote.ts +214 -0
  145. package/backend/ws/git/staging.ts +84 -0
  146. package/backend/ws/git/status.ts +90 -0
  147. package/backend/ws/index.ts +69 -0
  148. package/backend/ws/mcp/index.ts +61 -0
  149. package/backend/ws/messages/crud.ts +74 -0
  150. package/backend/ws/messages/index.ts +14 -0
  151. package/backend/ws/preview/browser/cleanup.ts +129 -0
  152. package/backend/ws/preview/browser/console.ts +114 -0
  153. package/backend/ws/preview/browser/interact.ts +513 -0
  154. package/backend/ws/preview/browser/mcp.ts +129 -0
  155. package/backend/ws/preview/browser/native-ui.ts +235 -0
  156. package/backend/ws/preview/browser/stats.ts +55 -0
  157. package/backend/ws/preview/browser/tab-info.ts +126 -0
  158. package/backend/ws/preview/browser/tab.ts +166 -0
  159. package/backend/ws/preview/browser/webcodecs.ts +293 -0
  160. package/backend/ws/preview/index.ts +146 -0
  161. package/backend/ws/projects/crud.ts +113 -0
  162. package/backend/ws/projects/index.ts +25 -0
  163. package/backend/ws/projects/presence.ts +46 -0
  164. package/backend/ws/projects/status.ts +116 -0
  165. package/backend/ws/sessions/crud.ts +327 -0
  166. package/backend/ws/sessions/index.ts +33 -0
  167. package/backend/ws/settings/crud.ts +112 -0
  168. package/backend/ws/settings/index.ts +14 -0
  169. package/backend/ws/snapshot/index.ts +17 -0
  170. package/backend/ws/snapshot/restore.ts +173 -0
  171. package/backend/ws/snapshot/timeline.ts +141 -0
  172. package/backend/ws/system/index.ts +14 -0
  173. package/backend/ws/system/operations.ts +49 -0
  174. package/backend/ws/terminal/index.ts +40 -0
  175. package/backend/ws/terminal/persistence.ts +153 -0
  176. package/backend/ws/terminal/session.ts +382 -0
  177. package/backend/ws/terminal/stream.ts +79 -0
  178. package/backend/ws/tunnel/index.ts +14 -0
  179. package/backend/ws/tunnel/operations.ts +91 -0
  180. package/backend/ws/types.ts +20 -0
  181. package/backend/ws/user/crud.ts +156 -0
  182. package/backend/ws/user/index.ts +14 -0
  183. package/bin/clopen.ts +307 -0
  184. package/bun.lock +1352 -0
  185. package/frontend/App.svelte +34 -0
  186. package/frontend/app.css +313 -0
  187. package/frontend/lib/app-environment.ts +10 -0
  188. package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
  189. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
  190. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
  191. package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
  192. package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
  193. package/frontend/lib/components/chat/formatters/index.ts +3 -0
  194. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
  195. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
  196. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
  197. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
  198. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
  199. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
  200. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
  201. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
  202. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
  203. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
  204. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
  205. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
  206. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
  207. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
  208. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
  209. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
  210. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
  211. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
  212. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
  213. package/frontend/lib/components/chat/shared/index.ts +2 -0
  214. package/frontend/lib/components/chat/shared/utils.ts +116 -0
  215. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
  216. package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
  217. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
  218. package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
  219. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
  220. package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
  221. package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
  222. package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
  223. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
  224. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
  225. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
  226. package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
  227. package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
  228. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
  229. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
  230. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
  231. package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
  232. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
  233. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
  234. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
  235. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
  236. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
  237. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
  238. package/frontend/lib/components/chat/tools/components/index.ts +7 -0
  239. package/frontend/lib/components/chat/tools/index.ts +26 -0
  240. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
  241. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
  242. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
  243. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
  244. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
  245. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
  246. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
  247. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
  248. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
  249. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
  250. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
  251. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
  252. package/frontend/lib/components/common/Alert.svelte +139 -0
  253. package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
  254. package/frontend/lib/components/common/Button.svelte +71 -0
  255. package/frontend/lib/components/common/Card.svelte +102 -0
  256. package/frontend/lib/components/common/Checkbox.svelte +48 -0
  257. package/frontend/lib/components/common/Dialog.svelte +249 -0
  258. package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
  259. package/frontend/lib/components/common/Icon.svelte +58 -0
  260. package/frontend/lib/components/common/Input.svelte +72 -0
  261. package/frontend/lib/components/common/Lightbox.svelte +233 -0
  262. package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
  263. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
  264. package/frontend/lib/components/common/Modal.svelte +177 -0
  265. package/frontend/lib/components/common/ModalProvider.svelte +28 -0
  266. package/frontend/lib/components/common/ModelSelector.svelte +110 -0
  267. package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
  268. package/frontend/lib/components/common/NotificationToast.svelte +113 -0
  269. package/frontend/lib/components/common/PageTemplate.svelte +76 -0
  270. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
  271. package/frontend/lib/components/common/Select.svelte +98 -0
  272. package/frontend/lib/components/common/Textarea.svelte +80 -0
  273. package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
  274. package/frontend/lib/components/common/lucide-icons.ts +1642 -0
  275. package/frontend/lib/components/common/material-icons.ts +1082 -0
  276. package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
  277. package/frontend/lib/components/common/xterm/index.ts +16 -0
  278. package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
  279. package/frontend/lib/components/common/xterm/types.ts +31 -0
  280. package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
  281. package/frontend/lib/components/files/FileNode.svelte +384 -0
  282. package/frontend/lib/components/files/FileTree.svelte +681 -0
  283. package/frontend/lib/components/files/FileViewer.svelte +728 -0
  284. package/frontend/lib/components/files/SearchResults.svelte +303 -0
  285. package/frontend/lib/components/git/BranchManager.svelte +458 -0
  286. package/frontend/lib/components/git/ChangesSection.svelte +107 -0
  287. package/frontend/lib/components/git/CommitForm.svelte +76 -0
  288. package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
  289. package/frontend/lib/components/git/DiffViewer.svelte +364 -0
  290. package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
  291. package/frontend/lib/components/git/GitButton.svelte +33 -0
  292. package/frontend/lib/components/git/GitLog.svelte +361 -0
  293. package/frontend/lib/components/git/GitModal.svelte +80 -0
  294. package/frontend/lib/components/history/HistoryModal.svelte +563 -0
  295. package/frontend/lib/components/history/HistoryView.svelte +615 -0
  296. package/frontend/lib/components/index.ts +34 -0
  297. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
  298. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
  299. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
  300. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
  301. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
  302. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
  303. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
  304. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
  305. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
  306. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
  307. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
  308. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
  309. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
  310. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
  311. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
  312. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
  313. package/frontend/lib/components/preview/index.ts +2 -0
  314. package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
  315. package/frontend/lib/components/settings/SettingsView.svelte +36 -0
  316. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
  317. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
  318. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
  319. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
  320. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
  321. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
  322. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
  323. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
  324. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
  325. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
  326. package/frontend/lib/components/terminal/Terminal.svelte +368 -0
  327. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
  328. package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
  329. package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
  330. package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
  331. package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
  332. package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
  333. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
  334. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
  335. package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
  336. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
  337. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
  338. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
  339. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
  340. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
  341. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
  342. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
  343. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
  344. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
  345. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
  346. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
  347. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
  348. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
  349. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
  350. package/frontend/lib/constants/preview.ts +45 -0
  351. package/frontend/lib/services/chat/chat.service.ts +704 -0
  352. package/frontend/lib/services/chat/index.ts +7 -0
  353. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
  354. package/frontend/lib/services/notification/index.ts +8 -0
  355. package/frontend/lib/services/notification/push.service.ts +144 -0
  356. package/frontend/lib/services/notification/sound.service.ts +127 -0
  357. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
  358. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
  359. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
  360. package/frontend/lib/services/preview/index.ts +23 -0
  361. package/frontend/lib/services/project/index.ts +8 -0
  362. package/frontend/lib/services/project/status.service.ts +159 -0
  363. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
  364. package/frontend/lib/services/terminal/background/index.ts +130 -0
  365. package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
  366. package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
  367. package/frontend/lib/services/terminal/index.ts +14 -0
  368. package/frontend/lib/services/terminal/persistence.service.ts +260 -0
  369. package/frontend/lib/services/terminal/project.service.ts +953 -0
  370. package/frontend/lib/services/terminal/session.service.ts +364 -0
  371. package/frontend/lib/services/terminal/terminal.service.ts +369 -0
  372. package/frontend/lib/stores/core/app.svelte.ts +117 -0
  373. package/frontend/lib/stores/core/files.svelte.ts +73 -0
  374. package/frontend/lib/stores/core/presence.svelte.ts +48 -0
  375. package/frontend/lib/stores/core/projects.svelte.ts +317 -0
  376. package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
  377. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
  378. package/frontend/lib/stores/features/models.svelte.ts +89 -0
  379. package/frontend/lib/stores/features/settings.svelte.ts +87 -0
  380. package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
  381. package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
  382. package/frontend/lib/stores/features/user.svelte.ts +96 -0
  383. package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
  384. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
  385. package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
  386. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
  387. package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
  388. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
  389. package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
  390. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
  391. package/frontend/lib/types/native-ui.ts +73 -0
  392. package/frontend/lib/utils/chat/date-separator.ts +39 -0
  393. package/frontend/lib/utils/chat/message-grouper.ts +219 -0
  394. package/frontend/lib/utils/chat/message-processor.ts +135 -0
  395. package/frontend/lib/utils/chat/tool-handler.ts +161 -0
  396. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
  397. package/frontend/lib/utils/click-outside.ts +20 -0
  398. package/frontend/lib/utils/context-manager.ts +257 -0
  399. package/frontend/lib/utils/file-icon-mappings.ts +769 -0
  400. package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
  401. package/frontend/lib/utils/git-status.ts +68 -0
  402. package/frontend/lib/utils/platform.ts +113 -0
  403. package/frontend/lib/utils/port-check.ts +65 -0
  404. package/frontend/lib/utils/terminalFormatter.ts +207 -0
  405. package/frontend/lib/utils/theme.ts +6 -0
  406. package/frontend/lib/utils/tree-visualizer.ts +320 -0
  407. package/frontend/lib/utils/ws.ts +44 -0
  408. package/frontend/main.ts +13 -0
  409. package/index.html +70 -0
  410. package/package.json +111 -0
  411. package/scripts/generate-icons.ts +87 -0
  412. package/scripts/pre-publish-check.sh +142 -0
  413. package/scripts/setup-hooks.sh +134 -0
  414. package/scripts/validate-branch-name.sh +47 -0
  415. package/scripts/validate-commit-msg.sh +42 -0
  416. package/shared/constants/engines.ts +134 -0
  417. package/shared/types/database/connection.ts +16 -0
  418. package/shared/types/database/index.ts +6 -0
  419. package/shared/types/database/schema.ts +141 -0
  420. package/shared/types/engine/index.ts +45 -0
  421. package/shared/types/filesystem/index.ts +22 -0
  422. package/shared/types/git.ts +171 -0
  423. package/shared/types/messaging/index.ts +239 -0
  424. package/shared/types/messaging/tool.ts +526 -0
  425. package/shared/types/network/api.ts +18 -0
  426. package/shared/types/network/index.ts +5 -0
  427. package/shared/types/stores/app.ts +23 -0
  428. package/shared/types/stores/dialog.ts +21 -0
  429. package/shared/types/stores/index.ts +3 -0
  430. package/shared/types/stores/settings.ts +15 -0
  431. package/shared/types/terminal/index.ts +44 -0
  432. package/shared/types/ui/components.ts +61 -0
  433. package/shared/types/ui/icons.ts +23 -0
  434. package/shared/types/ui/index.ts +22 -0
  435. package/shared/types/ui/notifications.ts +14 -0
  436. package/shared/types/ui/theme.ts +12 -0
  437. package/shared/types/websocket/index.ts +43 -0
  438. package/shared/types/window.d.ts +13 -0
  439. package/shared/utils/anonymous-user.ts +168 -0
  440. package/shared/utils/async.ts +10 -0
  441. package/shared/utils/diff-calculator.ts +184 -0
  442. package/shared/utils/file-type-detection.ts +166 -0
  443. package/shared/utils/logger.ts +158 -0
  444. package/shared/utils/message-formatter.ts +79 -0
  445. package/shared/utils/path.ts +47 -0
  446. package/shared/utils/ws-client.ts +768 -0
  447. package/shared/utils/ws-server.ts +660 -0
  448. package/static/audio/notification.ogg +0 -0
  449. package/static/favicon.svg +8 -0
  450. package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
  451. package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
  452. package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
  453. package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
  454. package/static/fonts/dm-sans.css +96 -0
  455. package/svelte.config.js +20 -0
  456. package/vite.config.ts +33 -0
@@ -0,0 +1,458 @@
1
+ <script lang="ts">
2
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
3
+ import Modal from '$frontend/lib/components/common/Modal.svelte';
4
+ import Dialog from '$frontend/lib/components/common/Dialog.svelte';
5
+ import type { GitBranchInfo, GitRemote } from '$shared/types/git';
6
+ import { projectState } from '$frontend/lib/stores/core/projects.svelte';
7
+ import { showError, showInfo } from '$frontend/lib/stores/ui/notification.svelte';
8
+ import { debug } from '$shared/utils/logger';
9
+ import ws from '$frontend/lib/utils/ws';
10
+
11
+ interface Props {
12
+ isOpen: boolean;
13
+ branchInfo: GitBranchInfo | null;
14
+ onClose: () => void;
15
+ onSwitch: (name: string) => void;
16
+ onCreate: (name: string) => void;
17
+ onDelete: (name: string, force?: boolean) => void;
18
+ onRename: (oldName: string, newName: string) => void;
19
+ onMerge: (name: string) => void;
20
+ onRemotesChanged?: () => void;
21
+ }
22
+
23
+ const { isOpen, branchInfo, onClose, onSwitch, onCreate, onDelete, onRename, onMerge, onRemotesChanged }: Props = $props();
24
+
25
+ const projectId = $derived(projectState.currentProject?.id || '');
26
+
27
+ let searchQuery = $state('');
28
+ let showCreateForm = $state(false);
29
+ let newBranchName = $state('');
30
+ let activeTab = $state<'local' | 'remote'>('local');
31
+
32
+ // Remote management
33
+ let remotes = $state<GitRemote[]>([]);
34
+ let isLoadingRemotes = $state(false);
35
+ let showAddRemoteForm = $state(false);
36
+ let newRemoteName = $state('origin');
37
+ let newRemoteUrl = $state('');
38
+ let showRemoteSection = $state(false);
39
+
40
+ // Confirm dialog
41
+ let showConfirmDialog = $state(false);
42
+ let confirmConfig = $state({
43
+ title: '',
44
+ message: '',
45
+ type: 'warning' as 'info' | 'warning' | 'error' | 'success',
46
+ confirmText: 'Confirm',
47
+ onConfirm: () => {}
48
+ });
49
+
50
+ const filteredLocal = $derived(
51
+ branchInfo?.local.filter(b =>
52
+ b.name.toLowerCase().includes(searchQuery.toLowerCase())
53
+ ) ?? []
54
+ );
55
+
56
+ const filteredRemote = $derived(
57
+ branchInfo?.remote.filter(b =>
58
+ b.name.toLowerCase().includes(searchQuery.toLowerCase())
59
+ ) ?? []
60
+ );
61
+
62
+ function handleCreate() {
63
+ if (!newBranchName.trim()) return;
64
+ onCreate(newBranchName.trim());
65
+ newBranchName = '';
66
+ showCreateForm = false;
67
+ }
68
+
69
+ function handleSwitchRemote(remoteBranch: string) {
70
+ const parts = remoteBranch.split('/');
71
+ const localName = parts.slice(1).join('/');
72
+ onSwitch(localName);
73
+ }
74
+
75
+ function handleClose() {
76
+ searchQuery = '';
77
+ newBranchName = '';
78
+ showCreateForm = false;
79
+ showAddRemoteForm = false;
80
+ newRemoteName = 'origin';
81
+ newRemoteUrl = '';
82
+ onClose();
83
+ }
84
+
85
+ async function loadRemotes() {
86
+ if (!projectId) return;
87
+ isLoadingRemotes = true;
88
+ try {
89
+ remotes = await ws.http('git:remotes', { projectId });
90
+ } catch (err) {
91
+ debug.error('git', 'Failed to load remotes:', err);
92
+ } finally {
93
+ isLoadingRemotes = false;
94
+ }
95
+ }
96
+
97
+ async function handleAddRemote() {
98
+ if (!newRemoteName.trim() || !newRemoteUrl.trim() || !projectId) return;
99
+ try {
100
+ await ws.http('git:add-remote', { projectId, name: newRemoteName.trim(), url: newRemoteUrl.trim() });
101
+ showInfo('Remote Added', `Remote "${newRemoteName.trim()}" connected.`);
102
+ newRemoteName = 'origin';
103
+ newRemoteUrl = '';
104
+ showAddRemoteForm = false;
105
+ await loadRemotes();
106
+ onRemotesChanged?.();
107
+ } catch (err) {
108
+ debug.error('git', 'Failed to add remote:', err);
109
+ showError('Failed to Add Remote', err instanceof Error ? err.message : 'Unknown error');
110
+ }
111
+ }
112
+
113
+ function handleRemoveRemote(name: string) {
114
+ confirmConfig = {
115
+ title: 'Remove Remote',
116
+ message: `Disconnect remote "${name}"? This will not delete the remote repository itself.`,
117
+ type: 'warning',
118
+ confirmText: 'Remove',
119
+ onConfirm: async () => {
120
+ if (!projectId) return;
121
+ try {
122
+ await ws.http('git:remove-remote', { projectId, name });
123
+ showInfo('Remote Removed', `Remote "${name}" disconnected.`);
124
+ await loadRemotes();
125
+ onRemotesChanged?.();
126
+ } catch (err) {
127
+ debug.error('git', 'Failed to remove remote:', err);
128
+ showError('Failed to Remove Remote', err instanceof Error ? err.message : 'Unknown error');
129
+ }
130
+ }
131
+ };
132
+ showConfirmDialog = true;
133
+ }
134
+
135
+ // Load remotes when modal opens
136
+ $effect(() => {
137
+ if (isOpen) {
138
+ loadRemotes();
139
+ }
140
+ });
141
+ </script>
142
+
143
+ <Modal isOpen={isOpen} onClose={handleClose} size="md">
144
+ {#snippet header()}
145
+ <div class="flex items-center justify-between px-4 py-3 md:px-6 md:py-4">
146
+ <div class="flex items-center gap-2.5">
147
+ <Icon name="lucide:git-branch" class="w-5 h-5 text-violet-600" />
148
+ <h2 class="text-base md:text-lg font-bold text-slate-900 dark:text-slate-100">Branches</h2>
149
+ </div>
150
+ <div class="flex items-center gap-2">
151
+ <button
152
+ type="button"
153
+ class="p-1.5 md:p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-violet-500/10 transition-colors"
154
+ onclick={handleClose}
155
+ aria-label="Close modal"
156
+ >
157
+ <svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
158
+ <path
159
+ stroke-linecap="round"
160
+ stroke-linejoin="round"
161
+ stroke-width="2"
162
+ d="M6 18L18 6M6 6l12 12"
163
+ />
164
+ </svg>
165
+ </button>
166
+ </div>
167
+ </div>
168
+ {/snippet}
169
+
170
+ {#snippet children()}
171
+ <!-- Create branch form -->
172
+ {#if showCreateForm}
173
+ <div class="mb-4 p-3 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg space-y-2">
174
+ <input
175
+ type="text"
176
+ bind:value={newBranchName}
177
+ placeholder="New branch name..."
178
+ class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40"
179
+ onkeydown={(e) => e.key === 'Enter' && handleCreate()}
180
+ autofocus
181
+ />
182
+ <div class="flex gap-2">
183
+ <button
184
+ type="button"
185
+ class="flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
186
+ {newBranchName.trim()
187
+ ? 'bg-violet-600 text-white hover:bg-violet-700'
188
+ : 'bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500 cursor-not-allowed'}"
189
+ onclick={handleCreate}
190
+ disabled={!newBranchName.trim()}
191
+ >
192
+ Create Branch
193
+ </button>
194
+ <button
195
+ type="button"
196
+ class="px-3 py-2 text-sm font-medium bg-transparent border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors cursor-pointer"
197
+ onclick={() => { showCreateForm = false; newBranchName = ''; }}
198
+ >
199
+ Cancel
200
+ </button>
201
+ </div>
202
+ </div>
203
+ {:else}
204
+ <button
205
+ type="button"
206
+ class="flex items-center justify-center gap-2 w-full mb-4 py-2.5 px-3 border border-dashed border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 hover:text-violet-600 hover:border-violet-400 transition-colors cursor-pointer bg-transparent"
207
+ onclick={() => showCreateForm = true}
208
+ >
209
+ <Icon name="lucide:plus" class="w-4 h-4" />
210
+ <span>Create New Branch</span>
211
+ </button>
212
+ {/if}
213
+
214
+ <!-- Search -->
215
+ <div class="mb-4">
216
+ <div class="flex items-center gap-2 py-2.5 px-3.5 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-lg">
217
+ <Icon name="lucide:search" class="w-4 h-4 text-slate-500 dark:text-slate-400 shrink-0" />
218
+ <input
219
+ type="text"
220
+ bind:value={searchQuery}
221
+ placeholder="Search branches..."
222
+ class="flex-1 bg-transparent border-none outline-none text-slate-900 dark:text-slate-100 text-sm placeholder:text-slate-500 dark:placeholder:text-slate-400"
223
+ />
224
+ {#if searchQuery}
225
+ <button
226
+ type="button"
227
+ class="flex items-center justify-center w-5 h-5 bg-transparent border-none rounded text-slate-400 cursor-pointer transition-all duration-150 hover:text-slate-600 dark:hover:text-slate-300"
228
+ onclick={() => (searchQuery = '')}
229
+ aria-label="Clear search"
230
+ >
231
+ <Icon name="lucide:x" class="w-3.5 h-3.5" />
232
+ </button>
233
+ {/if}
234
+ </div>
235
+ </div>
236
+
237
+ <!-- Tabs -->
238
+ <div class="flex gap-1 mb-3">
239
+ <button
240
+ type="button"
241
+ class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
242
+ {activeTab === 'local'
243
+ ? 'bg-violet-500/10 text-violet-600'
244
+ : 'bg-transparent text-slate-500 hover:text-slate-700 dark:hover:text-slate-300'}"
245
+ onclick={() => activeTab = 'local'}
246
+ >
247
+ Local ({filteredLocal.length})
248
+ </button>
249
+ <button
250
+ type="button"
251
+ class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
252
+ {activeTab === 'remote'
253
+ ? 'bg-violet-500/10 text-violet-600'
254
+ : 'bg-transparent text-slate-500 hover:text-slate-700 dark:hover:text-slate-300'}"
255
+ onclick={() => activeTab = 'remote'}
256
+ >
257
+ Remote ({filteredRemote.length})
258
+ </button>
259
+ </div>
260
+
261
+ <!-- Branch list -->
262
+ <div class="space-y-1.5 max-h-80 overflow-y-auto">
263
+ {#if activeTab === 'local'}
264
+ {#each filteredLocal as branch (branch.name)}
265
+ <div
266
+ class="group flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors
267
+ {branch.isCurrent
268
+ ? 'bg-violet-50 dark:bg-violet-900/10 border border-violet-200 dark:border-violet-800'
269
+ : 'hover:bg-slate-100 dark:hover:bg-slate-800/50 border border-slate-200 dark:border-slate-700'}"
270
+ >
271
+ <div class="flex items-center gap-2.5 flex-1 min-w-0">
272
+ {#if branch.isCurrent}
273
+ <Icon name="lucide:check" class="w-4 h-4 text-violet-600 shrink-0" />
274
+ {:else}
275
+ <div class="w-4 h-4 shrink-0"></div>
276
+ {/if}
277
+ <span class="text-sm text-slate-900 dark:text-slate-100 truncate {branch.isCurrent ? 'font-semibold' : ''}">
278
+ {branch.name}
279
+ </span>
280
+ {#if branch.isCurrent}
281
+ <span class="inline-flex items-center px-2 py-0.5 bg-violet-100 dark:bg-violet-900/20 text-violet-600 dark:text-violet-400 text-xs font-medium rounded-full">
282
+ Current
283
+ </span>
284
+ {/if}
285
+ </div>
286
+
287
+ {#if !branch.isCurrent}
288
+ <div class="flex items-center gap-1 -my-2">
289
+ <button
290
+ type="button"
291
+ class="flex items-center justify-center w-8 h-8 rounded-lg text-slate-500 hover:bg-violet-500/10 hover:text-violet-600 transition-colors cursor-pointer bg-transparent border-none"
292
+ onclick={() => onSwitch(branch.name)}
293
+ title="Switch to this branch"
294
+ >
295
+ <Icon name="lucide:arrow-right" class="w-4 h-4" />
296
+ </button>
297
+ <button
298
+ type="button"
299
+ class="flex items-center justify-center w-8 h-8 rounded-lg text-slate-500 hover:bg-blue-500/10 hover:text-blue-500 transition-colors cursor-pointer bg-transparent border-none"
300
+ onclick={() => onMerge(branch.name)}
301
+ title="Merge into current branch"
302
+ >
303
+ <Icon name="lucide:git-merge" class="w-4 h-4" />
304
+ </button>
305
+ <button
306
+ type="button"
307
+ class="flex items-center justify-center w-8 h-8 rounded-lg text-slate-500 hover:bg-red-500/10 hover:text-red-500 transition-colors cursor-pointer bg-transparent border-none"
308
+ onclick={() => onDelete(branch.name)}
309
+ title="Delete branch"
310
+ >
311
+ <Icon name="lucide:trash-2" class="w-4 h-4" />
312
+ </button>
313
+ </div>
314
+ {/if}
315
+ </div>
316
+ {:else}
317
+ <div class="flex flex-col items-center gap-2 py-8 text-slate-500 dark:text-slate-400 text-sm">
318
+ <Icon name="lucide:search-x" class="w-10 h-10 opacity-40" />
319
+ <p class="font-medium">No local branches found</p>
320
+ </div>
321
+ {/each}
322
+ {:else if activeTab === 'remote'}
323
+ {#each filteredRemote as branch (branch.name)}
324
+ <div
325
+ class="group flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800/50 transition-colors border border-slate-200 dark:border-slate-700"
326
+ >
327
+ <div class="flex items-center gap-2.5 flex-1 min-w-0">
328
+ <Icon name="lucide:cloud" class="w-4 h-4 text-slate-400 shrink-0" />
329
+ <span class="text-sm text-slate-900 dark:text-slate-100 truncate">{branch.name}</span>
330
+ </div>
331
+
332
+ <div class="items-center gap-1 shrink-0 hidden group-hover:flex">
333
+ <button
334
+ type="button"
335
+ class="flex items-center justify-center w-8 h-8 rounded-lg text-slate-500 hover:bg-violet-500/10 hover:text-violet-600 transition-colors cursor-pointer bg-transparent border-none"
336
+ onclick={() => handleSwitchRemote(branch.name)}
337
+ title="Checkout remote branch"
338
+ >
339
+ <Icon name="lucide:arrow-right" class="w-4 h-4" />
340
+ </button>
341
+ </div>
342
+ </div>
343
+ {:else}
344
+ <div class="flex flex-col items-center gap-2 py-8 text-slate-500 dark:text-slate-400 text-sm">
345
+ <Icon name="lucide:search-x" class="w-10 h-10 opacity-40" />
346
+ <p class="font-medium">No remote branches found</p>
347
+ </div>
348
+ {/each}
349
+ {/if}
350
+ </div>
351
+
352
+ <!-- Remote Connections Section -->
353
+ <div class="mt-4 pt-4 border-t border-slate-200 dark:border-slate-700">
354
+ <button
355
+ type="button"
356
+ class="flex items-center gap-2 w-full text-left bg-transparent border-none cursor-pointer px-0 py-1"
357
+ onclick={() => showRemoteSection = !showRemoteSection}
358
+ >
359
+ <Icon name={showRemoteSection ? 'lucide:chevron-down' : 'lucide:chevron-right'} class="w-3.5 h-3.5 text-slate-400" />
360
+ <Icon name="lucide:server" class="w-3.5 h-3.5 text-slate-500" />
361
+ <span class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide">
362
+ Remote Servers ({remotes.length})
363
+ </span>
364
+ </button>
365
+
366
+ {#if showRemoteSection}
367
+ <div class="mt-2 space-y-2">
368
+ {#if isLoadingRemotes}
369
+ <div class="flex items-center justify-center py-4">
370
+ <div class="w-4 h-4 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
371
+ </div>
372
+ {:else}
373
+ <!-- Existing remotes -->
374
+ {#each remotes as remote (remote.name)}
375
+ <div class="group flex items-center gap-2.5 px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/30">
376
+ <div class="flex items-center justify-center w-6 h-6 rounded-md bg-slate-100 dark:bg-slate-800">
377
+ <Icon name="lucide:globe" class="w-3.5 h-3.5 text-slate-500" />
378
+ </div>
379
+ <div class="flex-1 min-w-0">
380
+ <div class="text-sm font-medium text-slate-900 dark:text-slate-100">{remote.name}</div>
381
+ <div class="text-xs text-slate-500 dark:text-slate-400 truncate font-mono">{remote.fetchUrl}</div>
382
+ </div>
383
+ <button
384
+ type="button"
385
+ class="flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors cursor-pointer bg-transparent border-none shrink-0 opacity-0 group-hover:opacity-100"
386
+ onclick={() => handleRemoveRemote(remote.name)}
387
+ title="Disconnect remote"
388
+ >
389
+ <Icon name="lucide:unlink" class="w-3.5 h-3.5" />
390
+ </button>
391
+ </div>
392
+ {/each}
393
+
394
+ <!-- Add remote form -->
395
+ {#if showAddRemoteForm}
396
+ <div class="p-3 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg space-y-2">
397
+ <input
398
+ type="text"
399
+ bind:value={newRemoteName}
400
+ placeholder="Name (e.g. origin)"
401
+ class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40"
402
+ />
403
+ <input
404
+ type="text"
405
+ bind:value={newRemoteUrl}
406
+ placeholder="URL (e.g. https://github.com/user/repo.git)"
407
+ class="w-full px-3 py-2 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 outline-none focus:border-violet-500/40 font-mono"
408
+ onkeydown={(e) => e.key === 'Enter' && handleAddRemote()}
409
+ autofocus
410
+ />
411
+ <div class="flex gap-2">
412
+ <button
413
+ type="button"
414
+ class="flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer border-none
415
+ {newRemoteName.trim() && newRemoteUrl.trim()
416
+ ? 'bg-violet-600 text-white hover:bg-violet-700'
417
+ : 'bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-500 cursor-not-allowed'}"
418
+ onclick={handleAddRemote}
419
+ disabled={!newRemoteName.trim() || !newRemoteUrl.trim()}
420
+ >
421
+ Connect
422
+ </button>
423
+ <button
424
+ type="button"
425
+ class="px-3 py-2 text-sm font-medium bg-transparent border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors cursor-pointer"
426
+ onclick={() => { showAddRemoteForm = false; newRemoteName = 'origin'; newRemoteUrl = ''; }}
427
+ >
428
+ Cancel
429
+ </button>
430
+ </div>
431
+ </div>
432
+ {:else}
433
+ <button
434
+ type="button"
435
+ class="flex items-center gap-2 w-full px-3 py-2 text-xs text-slate-500 hover:text-violet-600 hover:bg-violet-500/5 rounded-lg transition-colors cursor-pointer bg-transparent border-none"
436
+ onclick={() => { showAddRemoteForm = true; showRemoteSection = true; }}
437
+ >
438
+ <Icon name="lucide:plus" class="w-3.5 h-3.5" />
439
+ <span>Add Remote Server</span>
440
+ </button>
441
+ {/if}
442
+ {/if}
443
+ </div>
444
+ {/if}
445
+ </div>
446
+ {/snippet}
447
+ </Modal>
448
+
449
+ <!-- Confirm Dialog -->
450
+ <Dialog
451
+ bind:isOpen={showConfirmDialog}
452
+ onClose={() => showConfirmDialog = false}
453
+ type={confirmConfig.type}
454
+ title={confirmConfig.title}
455
+ message={confirmConfig.message}
456
+ confirmText={confirmConfig.confirmText}
457
+ onConfirm={confirmConfig.onConfirm}
458
+ />
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
3
+ import FileChangeItem from './FileChangeItem.svelte';
4
+ import type { GitFileChange } from '$shared/types/git';
5
+ import type { IconName } from '$shared/types/ui/icons';
6
+
7
+ interface Props {
8
+ title: string;
9
+ icon: IconName;
10
+ files: GitFileChange[];
11
+ section: 'staged' | 'unstaged' | 'untracked' | 'conflicted';
12
+ collapsed?: boolean;
13
+ onStage?: (path: string) => void;
14
+ onUnstage?: (path: string) => void;
15
+ onDiscard?: (path: string) => void;
16
+ onStageAll?: () => void;
17
+ onUnstageAll?: () => void;
18
+ onDiscardAll?: () => void;
19
+ onViewDiff?: (file: GitFileChange, section: string) => void;
20
+ onResolve?: (path: string) => void;
21
+ }
22
+
23
+ const {
24
+ title, icon, files, section,
25
+ collapsed = false,
26
+ onStage, onUnstage, onDiscard,
27
+ onStageAll, onUnstageAll, onDiscardAll,
28
+ onViewDiff, onResolve
29
+ }: Props = $props();
30
+
31
+ let isCollapsed = $state(collapsed);
32
+ </script>
33
+
34
+ {#if files.length > 0}
35
+ <div class="mb-1">
36
+ <!-- Section header -->
37
+ <div
38
+ onclick={() => isCollapsed = !isCollapsed}
39
+ class="group flex items-center gap-2 py-3 px-2 cursor-pointer select-none hover:bg-slate-100 dark:hover:bg-slate-800/40 rounded-md transition-colors">
40
+ <div
41
+ class="flex items-center gap-2 flex-1 min-w-0 bg-transparent border-none text-left cursor-pointer p-0"
42
+ >
43
+ <Icon
44
+ name={isCollapsed ? 'lucide:chevron-right' : 'lucide:chevron-down'}
45
+ class="w-4 h-4 text-slate-500 shrink-0"
46
+ />
47
+ <!-- <Icon name={icon} class="w-4 h-4 text-slate-500 shrink-0" /> -->
48
+ <span class="text-xs font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wide">
49
+ {title}
50
+ </span>
51
+ <span class="text-xs font-medium text-slate-400 dark:text-slate-600 ml-0.5">
52
+ {files.length}
53
+ </span>
54
+ </div>
55
+
56
+ <!-- Bulk actions (hidden until hover) -->
57
+ <div class="flex items-center gap-0.5 shrink-0 -my-2">
58
+ {#if section === 'staged' && onUnstageAll}
59
+ <button
60
+ type="button"
61
+ class="flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors bg-transparent border-none cursor-pointer"
62
+ onclick={(e) => { e.stopPropagation(); onUnstageAll?.(); }}
63
+ title="Unstage All"
64
+ >
65
+ <Icon name="lucide:minus" class="w-4 h-4" />
66
+ </button>
67
+ {:else if (section === 'unstaged' || section === 'untracked') && onStageAll}
68
+ {#if onDiscardAll}
69
+ <button
70
+ type="button"
71
+ class="flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors bg-transparent border-none cursor-pointer"
72
+ onclick={(e) => { e.stopPropagation(); onDiscardAll?.(); }}
73
+ title="Discard All"
74
+ >
75
+ <Icon name="lucide:undo-2" class="w-4 h-4" />
76
+ </button>
77
+ {/if}
78
+ <button
79
+ type="button"
80
+ class="flex items-center justify-center w-7 h-7 rounded-md text-slate-400 hover:bg-emerald-500/10 hover:text-emerald-500 transition-colors bg-transparent border-none cursor-pointer"
81
+ onclick={(e) => { e.stopPropagation(); onStageAll?.(); }}
82
+ title="Stage All"
83
+ >
84
+ <Icon name="lucide:plus" class="w-4 h-4" />
85
+ </button>
86
+ {/if}
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Files list -->
91
+ {#if !isCollapsed}
92
+ <div class="ml-2">
93
+ {#each files as file (file.path)}
94
+ <FileChangeItem
95
+ {file}
96
+ {section}
97
+ {onStage}
98
+ {onUnstage}
99
+ {onDiscard}
100
+ {onViewDiff}
101
+ {onResolve}
102
+ />
103
+ {/each}
104
+ </div>
105
+ {/if}
106
+ </div>
107
+ {/if}
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
3
+
4
+ interface Props {
5
+ stagedCount: number;
6
+ isCommitting: boolean;
7
+ onCommit: (message: string) => void;
8
+ }
9
+
10
+ const { stagedCount, isCommitting, onCommit }: Props = $props();
11
+
12
+ let commitMessage = $state('');
13
+ let textareaEl = $state<HTMLTextAreaElement | null>(null);
14
+
15
+ function handleCommit() {
16
+ if (!commitMessage.trim() || stagedCount === 0) return;
17
+ onCommit(commitMessage.trim());
18
+ commitMessage = '';
19
+ autoResize();
20
+ }
21
+
22
+ function handleKeydown(e: KeyboardEvent) {
23
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
24
+ e.preventDefault();
25
+ handleCommit();
26
+ }
27
+ }
28
+
29
+ function autoResize() {
30
+ if (!textareaEl) return;
31
+ // Reset to single line to measure content
32
+ textareaEl.style.height = 'auto';
33
+ // Line height is ~20px for text-xs, so 5 lines max = 100px
34
+ const lineHeight = 20;
35
+ const maxHeight = lineHeight * 5;
36
+ const scrollHeight = textareaEl.scrollHeight;
37
+ textareaEl.style.height = Math.min(scrollHeight, maxHeight) + 'px';
38
+ }
39
+
40
+ function handleInput() {
41
+ autoResize();
42
+ }
43
+ </script>
44
+
45
+ <div class="px-2 pb-2">
46
+ <div class="flex flex-col gap-1.5">
47
+ <textarea
48
+ bind:this={textareaEl}
49
+ bind:value={commitMessage}
50
+ placeholder="Commit message..."
51
+ class="w-full px-2.5 py-2 text-sm bg-white dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 rounded-md text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-600 resize-none outline-none focus:border-violet-500/40 focus:ring-1 focus:ring-violet-500/20 transition-colors overflow-hidden"
52
+ rows="1"
53
+ style="height: 27px"
54
+ onkeydown={handleKeydown}
55
+ oninput={handleInput}
56
+ disabled={isCommitting}
57
+ ></textarea>
58
+ <button
59
+ type="button"
60
+ class="flex items-center justify-center gap-1.5 w-full py-1.5 px-3 rounded-md text-xs font-medium transition-all duration-150
61
+ {stagedCount > 0 && commitMessage.trim() && !isCommitting
62
+ ? 'bg-violet-600 text-white hover:bg-violet-700 cursor-pointer'
63
+ : 'bg-slate-100 dark:bg-slate-800 text-slate-400 dark:text-slate-600 cursor-not-allowed'}"
64
+ onclick={handleCommit}
65
+ disabled={stagedCount === 0 || !commitMessage.trim() || isCommitting}
66
+ >
67
+ {#if isCommitting}
68
+ <div class="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
69
+ <span>Committing...</span>
70
+ {:else}
71
+ <Icon name="lucide:check" class="w-3.5 h-3.5" />
72
+ <span>Commit{stagedCount > 0 ? ` (${stagedCount})` : ''}</span>
73
+ {/if}
74
+ </button>
75
+ </div>
76
+ </div>