@myrialabs/clopen 0.2.1 → 0.2.3

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 (422) hide show
  1. package/.dockerignore +5 -0
  2. package/.env.example +2 -5
  3. package/CONTRIBUTING.md +4 -0
  4. package/README.md +4 -2
  5. package/backend/{lib/auth → auth}/auth-service.ts +2 -2
  6. package/backend/{lib/chat → chat}/stream-manager.ts +1 -1
  7. package/backend/{lib/database → database}/queries/message-queries.ts +42 -0
  8. package/backend/{lib/database → database}/utils/connection.ts +5 -5
  9. package/backend/{lib/engine → engine}/adapters/claude/environment.ts +3 -4
  10. package/backend/{lib/engine → engine}/adapters/opencode/message-converter.ts +1 -1
  11. package/backend/{lib/engine → engine}/adapters/opencode/server.ts +7 -1
  12. package/backend/{lib/files → files}/file-watcher.ts +1 -1
  13. package/backend/{lib/git → git}/git-executor.ts +3 -2
  14. package/backend/index.ts +16 -16
  15. package/backend/{lib/mcp → mcp}/README.md +68 -79
  16. package/backend/{lib/mcp → mcp}/config.ts +1 -1
  17. package/backend/{lib/mcp → mcp}/servers/browser-automation/actions.ts +4 -4
  18. package/backend/{lib/mcp → mcp}/servers/browser-automation/browser.ts +3 -3
  19. package/backend/{lib/mcp → mcp}/servers/browser-automation/inspection.ts +3 -3
  20. package/backend/middleware/cors.ts +1 -1
  21. package/backend/middleware/error-handler.ts +1 -1
  22. package/backend/{lib/preview → preview}/browser/browser-preview-service.ts +1 -1
  23. package/backend/{lib/preview → preview}/browser/browser-tab-manager.ts +1 -1
  24. package/backend/{lib/preview → preview}/browser/types.ts +1 -1
  25. package/backend/{lib/project → project}/status-manager.ts +223 -223
  26. package/backend/{lib/snapshot → snapshot}/blob-store.ts +2 -2
  27. package/backend/{lib/terminal → terminal}/shell-utils.ts +1 -1
  28. package/backend/{lib/shared → utils}/env.ts +13 -15
  29. package/backend/{lib/shared → utils}/index.ts +4 -1
  30. package/backend/utils/paths.ts +11 -0
  31. package/backend/{lib/shared → utils}/port-utils.ts +19 -6
  32. package/backend/{lib/utils → utils}/ws.ts +2 -2
  33. package/backend/ws/README.md +9 -9
  34. package/backend/ws/auth/invites.ts +2 -2
  35. package/backend/ws/auth/login.ts +9 -9
  36. package/backend/ws/auth/status.ts +2 -2
  37. package/backend/ws/auth/users.ts +1 -1
  38. package/backend/ws/chat/background.ts +2 -2
  39. package/backend/ws/chat/stream.ts +3 -3
  40. package/backend/ws/engine/claude/accounts.ts +4 -4
  41. package/backend/ws/engine/claude/status.ts +1 -1
  42. package/backend/ws/files/read.ts +2 -2
  43. package/backend/ws/files/watch.ts +2 -2
  44. package/backend/ws/files/write.ts +1 -1
  45. package/backend/ws/git/branch.ts +2 -2
  46. package/backend/ws/git/commit.ts +2 -2
  47. package/backend/ws/git/conflict.ts +2 -2
  48. package/backend/ws/git/diff.ts +2 -2
  49. package/backend/ws/git/log.ts +2 -2
  50. package/backend/ws/git/remote.ts +2 -2
  51. package/backend/ws/git/staging.ts +2 -2
  52. package/backend/ws/git/status.ts +3 -3
  53. package/backend/ws/messages/crud.ts +53 -1
  54. package/backend/ws/preview/browser/cleanup.ts +2 -2
  55. package/backend/ws/preview/browser/console.ts +2 -2
  56. package/backend/ws/preview/browser/interact.ts +2 -2
  57. package/backend/ws/preview/browser/mcp.ts +1 -1
  58. package/backend/ws/preview/browser/native-ui.ts +2 -2
  59. package/backend/ws/preview/browser/stats.ts +2 -2
  60. package/backend/ws/preview/browser/tab-info.ts +2 -2
  61. package/backend/ws/preview/browser/tab.ts +2 -2
  62. package/backend/ws/preview/browser/webcodecs.ts +2 -2
  63. package/backend/ws/projects/crud.ts +3 -3
  64. package/backend/ws/projects/presence.ts +3 -3
  65. package/backend/ws/projects/status.ts +3 -3
  66. package/backend/ws/sessions/crud.ts +2 -2
  67. package/backend/ws/settings/crud.ts +2 -2
  68. package/backend/ws/snapshot/restore.ts +5 -5
  69. package/backend/ws/snapshot/timeline.ts +3 -3
  70. package/backend/ws/system/operations.ts +1 -1
  71. package/backend/ws/terminal/persistence.ts +3 -3
  72. package/backend/ws/terminal/session.ts +6 -6
  73. package/backend/ws/terminal/stream.ts +2 -2
  74. package/backend/ws/tunnel/operations.ts +1 -1
  75. package/backend/ws/user/crud.ts +4 -4
  76. package/bin/clopen.ts +15 -15
  77. package/docker-compose.yml +31 -0
  78. package/frontend/App.svelte +13 -13
  79. package/frontend/{lib/components → components}/auth/InvitePage.svelte +2 -2
  80. package/frontend/{lib/components → components}/auth/LoginPage.svelte +1 -1
  81. package/frontend/{lib/components → components}/auth/SetupPage.svelte +49 -17
  82. package/frontend/{lib/components → components}/chat/ChatInterface.svelte +14 -14
  83. package/frontend/{lib/components → components}/chat/formatters/ErrorMessage.svelte +1 -1
  84. package/frontend/{lib/components → components}/chat/formatters/MessageFormatter.svelte +4 -4
  85. package/frontend/{lib/components → components}/chat/formatters/TextMessage.svelte +1 -1
  86. package/frontend/{lib/components → components}/chat/formatters/Tools.svelte +1 -1
  87. package/frontend/{lib/components → components}/chat/input/ChatInput.svelte +12 -12
  88. package/frontend/{lib/components → components}/chat/input/components/ChatInputActions.svelte +1 -1
  89. package/frontend/{lib/components → components}/chat/input/components/EditModeIndicator.svelte +2 -2
  90. package/frontend/{lib/components → components}/chat/input/components/EngineModelPicker.svelte +9 -9
  91. package/frontend/{lib/components → components}/chat/input/components/FileAttachmentPreview.svelte +1 -1
  92. package/frontend/{lib/components → components}/chat/input/components/LoadingIndicator.svelte +2 -2
  93. package/frontend/{lib/components → components}/chat/input/composables/use-chat-actions.svelte.ts +8 -8
  94. package/frontend/{lib/components → components}/chat/input/composables/use-file-handling.svelte.ts +1 -1
  95. package/frontend/{lib/components → components}/chat/input/composables/use-input-state.svelte.ts +6 -6
  96. package/frontend/{lib/components → components}/chat/message/ChatMessage.svelte +8 -8
  97. package/frontend/{lib/components → components}/chat/message/ChatMessages.svelte +7 -7
  98. package/frontend/{lib/components → components}/chat/message/MessageBubble.svelte +1 -1
  99. package/frontend/{lib/components → components}/chat/message/MessageHeader.svelte +2 -2
  100. package/frontend/{lib/components → components}/chat/modal/DebugModal.svelte +2 -2
  101. package/frontend/{lib/components → components}/chat/modal/TokenUsageModal.svelte +2 -2
  102. package/frontend/{lib/components → components}/chat/tools/AskUserQuestionTool.svelte +4 -4
  103. package/frontend/{lib/components → components}/chat/tools/CustomMcpTool.svelte +1 -1
  104. package/frontend/{lib/components → components}/chat/tools/EditTool.svelte +1 -1
  105. package/frontend/{lib/components → components}/chat/tools/TodoWriteTool.svelte +1 -1
  106. package/frontend/{lib/components → components}/chat/tools/components/CodeBlock.svelte +2 -2
  107. package/frontend/{lib/components → components}/chat/tools/components/DiffBlock.svelte +1 -1
  108. package/frontend/{lib/components → components}/chat/tools/components/FileHeader.svelte +2 -2
  109. package/frontend/{lib/components → components}/chat/tools/components/InfoLine.svelte +1 -1
  110. package/frontend/{lib/components → components}/chat/widgets/FloatingTodoList.svelte +127 -13
  111. package/frontend/{lib/components → components}/chat/widgets/TokenUsage.svelte +4 -4
  112. package/frontend/{lib/components → components}/checkpoint/TimelineModal.svelte +10 -10
  113. package/frontend/{lib/components → components}/checkpoint/timeline/TimelineNode.svelte +1 -1
  114. package/frontend/{lib/components/common → components/common/display}/Icon.svelte +2 -2
  115. package/frontend/{lib/components/common → components/common/display}/PageTemplate.svelte +1 -1
  116. package/frontend/{lib/components/common → components/common/display}/ProjectUserAvatars.svelte +1 -1
  117. package/frontend/{lib/components/common → components/common/display}/ThemeToggle.svelte +1 -1
  118. package/frontend/{lib/components/common → components/common/editor}/MonacoEditor.svelte +2 -2
  119. package/frontend/{lib/components/common → components/common/feedback}/Alert.svelte +1 -1
  120. package/frontend/{lib/components/common → components/common/feedback}/ConnectionBanner.svelte +3 -3
  121. package/frontend/{lib/components/common → components/common/feedback}/LoadingScreen.svelte +1 -1
  122. package/frontend/{lib/components/common → components/common/feedback}/NotificationToast.svelte +2 -2
  123. package/frontend/{lib/components/common → components/common/feedback}/UpdateBanner.svelte +5 -5
  124. package/frontend/{lib/components/common → components/common/form}/Checkbox.svelte +1 -1
  125. package/frontend/{lib/components/common → components/common/form}/FolderBrowser.svelte +6 -6
  126. package/frontend/{lib/components/common → components/common/form}/Input.svelte +1 -1
  127. package/frontend/{lib/components/common → components/common/form}/ModelSelector.svelte +3 -3
  128. package/frontend/{lib/components/common → components/common/form}/Select.svelte +1 -1
  129. package/frontend/{lib/components/common → components/common/form}/Textarea.svelte +1 -1
  130. package/frontend/{lib/components/common → components/common/overlay}/Dialog.svelte +1 -1
  131. package/frontend/{lib/components/common → components/common/overlay}/Lightbox.svelte +3 -3
  132. package/frontend/{lib/components/common → components/common/overlay}/ModalProvider.svelte +2 -2
  133. package/frontend/{lib/components → components}/common/xterm/XTerm.svelte +5 -5
  134. package/frontend/{lib/components → components}/common/xterm/xterm-service.ts +2 -2
  135. package/frontend/{lib/components → components}/files/FileNode.svelte +3 -3
  136. package/frontend/{lib/components → components}/files/FileTree.svelte +5 -5
  137. package/frontend/{lib/components → components}/files/FileViewer.svelte +7 -7
  138. package/frontend/{lib/components → components}/files/SearchResults.svelte +3 -3
  139. package/frontend/{lib/components → components}/git/BranchManager.svelte +6 -6
  140. package/frontend/{lib/components → components}/git/ChangesSection.svelte +1 -1
  141. package/frontend/{lib/components → components}/git/CommitForm.svelte +1 -1
  142. package/frontend/{lib/components → components}/git/ConflictResolver.svelte +1 -1
  143. package/frontend/{lib/components → components}/git/DiffViewer.svelte +7 -7
  144. package/frontend/{lib/components → components}/git/FileChangeItem.svelte +3 -3
  145. package/frontend/{lib/components → components}/git/GitButton.svelte +1 -1
  146. package/frontend/{lib/components → components}/git/GitLog.svelte +2 -2
  147. package/frontend/{lib/components → components}/git/GitModal.svelte +3 -3
  148. package/frontend/{lib/components → components}/history/HistoryModal.svelte +41 -89
  149. package/frontend/{lib/components → components}/history/HistoryView.svelte +57 -104
  150. package/frontend/{lib/components → components}/index.ts +9 -9
  151. package/frontend/{lib/components → components}/preview/browser/BrowserPreview.svelte +3 -3
  152. package/frontend/{lib/components → components}/preview/browser/components/Canvas.svelte +2 -2
  153. package/frontend/{lib/components → components}/preview/browser/components/ConsolePanel.svelte +1 -1
  154. package/frontend/{lib/components → components}/preview/browser/components/Container.svelte +2 -2
  155. package/frontend/{lib/components → components}/preview/browser/components/ContextMenu.svelte +1 -1
  156. package/frontend/{lib/components → components}/preview/browser/components/SelectDropdown.svelte +1 -1
  157. package/frontend/{lib/components → components}/preview/browser/components/Toolbar.svelte +2 -2
  158. package/frontend/{lib/components → components}/preview/browser/core/cleanup.svelte.ts +1 -1
  159. package/frontend/{lib/components → components}/preview/browser/core/coordinator.svelte.ts +4 -4
  160. package/frontend/{lib/components → components}/preview/browser/core/interactions.svelte.ts +3 -3
  161. package/frontend/{lib/components → components}/preview/browser/core/mcp-handlers.svelte.ts +2 -2
  162. package/frontend/{lib/components → components}/preview/browser/core/native-ui-handlers.svelte.ts +2 -2
  163. package/frontend/{lib/components → components}/preview/browser/core/tab-manager.svelte.ts +1 -1
  164. package/frontend/{lib/components → components}/preview/browser/core/tab-operations.svelte.ts +3 -3
  165. package/frontend/{lib/components → components}/settings/SettingsModal.svelte +4 -4
  166. package/frontend/{lib/components → components}/settings/SettingsView.svelte +2 -2
  167. package/frontend/{lib/components → components}/settings/admin/InviteManagement.svelte +5 -5
  168. package/frontend/{lib/components → components}/settings/admin/UserManagement.svelte +5 -5
  169. package/frontend/{lib/components → components}/settings/appearance/AppearanceSettings.svelte +5 -5
  170. package/frontend/{lib/components → components}/settings/appearance/LayoutPresetSettings.svelte +3 -3
  171. package/frontend/{lib/components → components}/settings/appearance/LayoutPreview.svelte +1 -1
  172. package/frontend/{lib/components → components}/settings/engines/AIEnginesSettings.svelte +6 -6
  173. package/frontend/{lib/components → components}/settings/general/AdvancedSettings.svelte +5 -5
  174. package/frontend/{lib/components → components}/settings/general/AuthModeSettings.svelte +5 -5
  175. package/frontend/{lib/components → components}/settings/general/DataManagementSettings.svelte +9 -9
  176. package/frontend/{lib/components → components}/settings/general/UpdateSettings.svelte +3 -3
  177. package/frontend/{lib/components → components}/settings/model/ModelSettings.svelte +2 -2
  178. package/frontend/{lib/components → components}/settings/notifications/NotificationSettings.svelte +4 -4
  179. package/frontend/{lib/components → components}/settings/user/UserSettings.svelte +5 -5
  180. package/frontend/{lib/components → components}/terminal/Terminal.svelte +9 -9
  181. package/frontend/{lib/components → components}/terminal/TerminalTabs.svelte +1 -1
  182. package/frontend/{lib/components → components}/terminal/TerminalView.svelte +5 -5
  183. package/frontend/{lib/components → components}/tunnel/TunnelActive.svelte +3 -3
  184. package/frontend/{lib/components → components}/tunnel/TunnelButton.svelte +2 -2
  185. package/frontend/{lib/components → components}/tunnel/TunnelInactive.svelte +4 -4
  186. package/frontend/{lib/components → components}/tunnel/TunnelModal.svelte +2 -2
  187. package/frontend/{lib/components → components}/workspace/DesktopNavigator.svelte +372 -372
  188. package/frontend/{lib/components → components}/workspace/MobileNavigator.svelte +380 -380
  189. package/frontend/{lib/components → components}/workspace/PanelContainer.svelte +3 -3
  190. package/frontend/{lib/components → components}/workspace/PanelHeader.svelte +10 -10
  191. package/frontend/{lib/components → components}/workspace/ViewMenu.svelte +4 -4
  192. package/frontend/{lib/components → components}/workspace/WorkspaceLayout.svelte +17 -17
  193. package/frontend/{lib/components → components}/workspace/layout/DesktopLayout.svelte +1 -1
  194. package/frontend/{lib/components → components}/workspace/layout/MobileLayout.svelte +1 -1
  195. package/frontend/{lib/components → components}/workspace/layout/split-pane/Container.svelte +1 -1
  196. package/frontend/{lib/components → components}/workspace/layout/split-pane/Handle.svelte +2 -2
  197. package/frontend/{lib/components → components}/workspace/layout/split-pane/Layout.svelte +2 -2
  198. package/frontend/{lib/components → components}/workspace/panels/ChatPanel.svelte +14 -14
  199. package/frontend/{lib/components → components}/workspace/panels/FilesPanel.svelte +51 -13
  200. package/frontend/{lib/components → components}/workspace/panels/GitPanel.svelte +55 -17
  201. package/frontend/{lib/components → components}/workspace/panels/PreviewPanel.svelte +5 -5
  202. package/frontend/{lib/components → components}/workspace/panels/TerminalPanel.svelte +6 -6
  203. package/frontend/{lib/services → services}/chat/chat.service.ts +8 -8
  204. package/frontend/{lib/services → services}/notification/global-stream-monitor.ts +117 -117
  205. package/frontend/{lib/services → services}/notification/push.service.ts +1 -1
  206. package/frontend/{lib/services → services}/notification/sound.service.ts +1 -1
  207. package/frontend/{lib/services → services}/preview/browser/browser-console.service.ts +1 -1
  208. package/frontend/{lib/services → services}/preview/browser/browser-webcodecs.service.ts +1 -1
  209. package/frontend/{lib/services → services}/preview/browser/mcp-integration.svelte.ts +2 -2
  210. package/frontend/{lib/services → services}/project/status.service.ts +160 -160
  211. package/frontend/{lib/services → services}/snapshot/snapshot.service.ts +2 -2
  212. package/frontend/{lib/services → services}/terminal/background/index.ts +1 -1
  213. package/frontend/{lib/services → services}/terminal/background/session-restore.ts +1 -1
  214. package/frontend/{lib/services → services}/terminal/background/stream-manager.ts +1 -1
  215. package/frontend/{lib/services → services}/terminal/project.service.ts +2 -2
  216. package/frontend/{lib/services → services}/terminal/terminal.service.ts +2 -2
  217. package/frontend/{lib/stores → stores}/core/app.svelte.ts +1 -1
  218. package/frontend/{lib/stores → stores}/core/presence.svelte.ts +4 -4
  219. package/frontend/{lib/stores → stores}/core/projects.svelte.ts +5 -5
  220. package/frontend/{lib/stores → stores}/core/sessions.svelte.ts +3 -3
  221. package/frontend/{lib/stores → stores}/features/auth.svelte.ts +30 -2
  222. package/frontend/{lib/stores → stores}/features/claude-accounts.svelte.ts +1 -1
  223. package/frontend/{lib/stores → stores}/features/models.svelte.ts +1 -1
  224. package/frontend/{lib/stores → stores}/features/settings.svelte.ts +2 -2
  225. package/frontend/{lib/stores → stores}/features/terminal.svelte.ts +4 -4
  226. package/frontend/{lib/stores → stores}/features/tunnel.svelte.ts +1 -1
  227. package/frontend/{lib/stores → stores}/features/user.svelte.ts +1 -1
  228. package/frontend/{lib/stores → stores}/ui/edit-mode.svelte.ts +3 -3
  229. package/frontend/{lib/stores → stores}/ui/update.svelte.ts +8 -2
  230. package/frontend/{lib/utils → utils}/theme.ts +1 -1
  231. package/frontend/{lib/utils → utils}/ws.ts +1 -1
  232. package/package.json +2 -2
  233. package/scripts/dev.ts +4 -3
  234. package/scripts/generate-icons.ts +2 -2
  235. package/scripts/start.ts +24 -0
  236. package/shared/types/ui/icons.ts +4 -4
  237. package/shared/utils/anonymous-user.ts +1 -1
  238. package/shared/utils/ws-server.ts +3 -3
  239. package/vite.config.ts +2 -2
  240. /package/backend/{lib/auth → auth}/index.ts +0 -0
  241. /package/backend/{lib/auth → auth}/permissions.ts +0 -0
  242. /package/backend/{lib/auth → auth}/rate-limiter.ts +0 -0
  243. /package/backend/{lib/auth → auth}/tokens.ts +0 -0
  244. /package/backend/{lib/chat → chat}/helpers.ts +0 -0
  245. /package/backend/{lib/chat → chat}/index.ts +0 -0
  246. /package/backend/{lib/database → database}/README.md +0 -0
  247. /package/backend/{lib/database → database}/index.ts +0 -0
  248. /package/backend/{lib/database → database}/migrations/001_create_projects_table.ts +0 -0
  249. /package/backend/{lib/database → database}/migrations/002_create_chat_sessions_table.ts +0 -0
  250. /package/backend/{lib/database → database}/migrations/003_create_messages_table.ts +0 -0
  251. /package/backend/{lib/database → database}/migrations/004_create_prompt_templates_table.ts +0 -0
  252. /package/backend/{lib/database → database}/migrations/005_create_settings_table.ts +0 -0
  253. /package/backend/{lib/database → database}/migrations/006_add_user_to_messages.ts +0 -0
  254. /package/backend/{lib/database → database}/migrations/007_create_stream_states_table.ts +0 -0
  255. /package/backend/{lib/database → database}/migrations/008_create_message_snapshots_table.ts +0 -0
  256. /package/backend/{lib/database → database}/migrations/009_add_delta_snapshot_fields.ts +0 -0
  257. /package/backend/{lib/database → database}/migrations/010_add_soft_delete_and_branch_support.ts +0 -0
  258. /package/backend/{lib/database → database}/migrations/011_git_like_commit_graph.ts +0 -0
  259. /package/backend/{lib/database → database}/migrations/012_add_file_change_statistics.ts +0 -0
  260. /package/backend/{lib/database → database}/migrations/013_checkpoint_tree_state.ts +0 -0
  261. /package/backend/{lib/database → database}/migrations/014_add_engine_to_sessions.ts +0 -0
  262. /package/backend/{lib/database → database}/migrations/015_add_model_to_sessions.ts +0 -0
  263. /package/backend/{lib/database → database}/migrations/016_create_user_projects_table.ts +0 -0
  264. /package/backend/{lib/database → database}/migrations/017_add_current_session_to_user_projects.ts +0 -0
  265. /package/backend/{lib/database → database}/migrations/018_create_claude_accounts_table.ts +0 -0
  266. /package/backend/{lib/database → database}/migrations/019_add_claude_account_to_sessions.ts +0 -0
  267. /package/backend/{lib/database → database}/migrations/020_add_snapshot_tree_hash.ts +0 -0
  268. /package/backend/{lib/database → database}/migrations/021_drop_prompt_templates_table.ts +0 -0
  269. /package/backend/{lib/database → database}/migrations/022_add_snapshot_changes_column.ts +0 -0
  270. /package/backend/{lib/database → database}/migrations/023_create_user_unread_sessions_table.ts +0 -0
  271. /package/backend/{lib/database → database}/migrations/024_create_users_table.ts +0 -0
  272. /package/backend/{lib/database → database}/migrations/025_create_auth_sessions_table.ts +0 -0
  273. /package/backend/{lib/database → database}/migrations/026_create_invite_tokens_table.ts +0 -0
  274. /package/backend/{lib/database → database}/migrations/index.ts +0 -0
  275. /package/backend/{lib/database → database}/queries/auth-queries.ts +0 -0
  276. /package/backend/{lib/database → database}/queries/checkpoint-queries.ts +0 -0
  277. /package/backend/{lib/database → database}/queries/engine-queries.ts +0 -0
  278. /package/backend/{lib/database → database}/queries/index.ts +0 -0
  279. /package/backend/{lib/database → database}/queries/project-queries.ts +0 -0
  280. /package/backend/{lib/database → database}/queries/session-queries.ts +0 -0
  281. /package/backend/{lib/database → database}/queries/settings-queries.ts +0 -0
  282. /package/backend/{lib/database → database}/queries/snapshot-queries.ts +0 -0
  283. /package/backend/{lib/database → database}/queries/utils-queries.ts +0 -0
  284. /package/backend/{lib/database → database}/seeders/index.ts +0 -0
  285. /package/backend/{lib/database → database}/seeders/settings_seeder.ts +0 -0
  286. /package/backend/{lib/database → database}/utils/index.ts +0 -0
  287. /package/backend/{lib/database → database}/utils/migration-runner.ts +0 -0
  288. /package/backend/{lib/database → database}/utils/seeder-runner.ts +0 -0
  289. /package/backend/{lib/engine → engine}/adapters/claude/error-handler.ts +0 -0
  290. /package/backend/{lib/engine → engine}/adapters/claude/index.ts +0 -0
  291. /package/backend/{lib/engine → engine}/adapters/claude/path-utils.ts +0 -0
  292. /package/backend/{lib/engine → engine}/adapters/claude/stream.ts +0 -0
  293. /package/backend/{lib/engine → engine}/adapters/opencode/index.ts +0 -0
  294. /package/backend/{lib/engine → engine}/adapters/opencode/stream.ts +0 -0
  295. /package/backend/{lib/engine → engine}/index.ts +0 -0
  296. /package/backend/{lib/engine → engine}/types.ts +0 -0
  297. /package/backend/{lib/files → files}/file-operations.ts +0 -0
  298. /package/backend/{lib/files → files}/file-reading.ts +0 -0
  299. /package/backend/{lib/files → files}/path-browsing.ts +0 -0
  300. /package/backend/{lib/git → git}/git-parser.ts +0 -0
  301. /package/backend/{lib/git → git}/git-service.ts +0 -0
  302. /package/backend/{lib/mcp → mcp}/index.ts +0 -0
  303. /package/backend/{lib/mcp → mcp}/project-context.ts +0 -0
  304. /package/backend/{lib/mcp → mcp}/remote-server.ts +0 -0
  305. /package/backend/{lib/mcp → mcp}/servers/browser-automation/index.ts +0 -0
  306. /package/backend/{lib/mcp → mcp}/servers/helper.ts +0 -0
  307. /package/backend/{lib/mcp → mcp}/servers/index.ts +0 -0
  308. /package/backend/{lib/mcp → mcp}/servers/weather/get-temperature.ts +0 -0
  309. /package/backend/{lib/mcp → mcp}/servers/weather/index.ts +0 -0
  310. /package/backend/{lib/mcp → mcp}/types.ts +0 -0
  311. /package/backend/{lib/preview → preview}/browser/browser-audio-capture.ts +0 -0
  312. /package/backend/{lib/preview → preview}/browser/browser-console-manager.ts +0 -0
  313. /package/backend/{lib/preview → preview}/browser/browser-dialog-handler.ts +0 -0
  314. /package/backend/{lib/preview → preview}/browser/browser-interaction-handler.ts +0 -0
  315. /package/backend/{lib/preview → preview}/browser/browser-mcp-control.ts +0 -0
  316. /package/backend/{lib/preview → preview}/browser/browser-native-ui-handler.ts +0 -0
  317. /package/backend/{lib/preview → preview}/browser/browser-navigation-tracker.ts +0 -0
  318. /package/backend/{lib/preview → preview}/browser/browser-pool.ts +0 -0
  319. /package/backend/{lib/preview → preview}/browser/browser-video-capture.ts +0 -0
  320. /package/backend/{lib/preview → preview}/browser/scripts/audio-stream.ts +0 -0
  321. /package/backend/{lib/preview → preview}/browser/scripts/cursor-tracking.ts +0 -0
  322. /package/backend/{lib/preview → preview}/browser/scripts/video-stream.ts +0 -0
  323. /package/backend/{lib/preview → preview}/index.ts +0 -0
  324. /package/backend/{lib/project → project}/index.ts +0 -0
  325. /package/backend/{lib/snapshot → snapshot}/gitignore.ts +0 -0
  326. /package/backend/{lib/snapshot → snapshot}/helpers.ts +0 -0
  327. /package/backend/{lib/snapshot → snapshot}/snapshot-service.ts +0 -0
  328. /package/backend/{lib/terminal → terminal}/helpers.ts +0 -0
  329. /package/backend/{lib/terminal → terminal}/index.ts +0 -0
  330. /package/backend/{lib/terminal → terminal}/pty-manager.ts +0 -0
  331. /package/backend/{lib/terminal → terminal}/pty-session-manager.ts +0 -0
  332. /package/backend/{lib/terminal → terminal}/stream-manager.ts +0 -0
  333. /package/backend/{lib/tunnel → tunnel}/global-tunnel-manager.ts +0 -0
  334. /package/backend/{lib/tunnel → tunnel}/project-tunnel-manager.ts +0 -0
  335. /package/backend/{lib/shared → utils}/process-manager.ts +0 -0
  336. /package/backend/{lib/user/helpers.ts → utils/user-helpers.ts} +0 -0
  337. /package/frontend/{lib/app-environment.ts → app-environment.ts} +0 -0
  338. /package/frontend/{lib/components → components}/chat/formatters/index.ts +0 -0
  339. /package/frontend/{lib/components → components}/chat/input/components/DragDropOverlay.svelte +0 -0
  340. /package/frontend/{lib/components → components}/chat/input/composables/use-animations.svelte.ts +0 -0
  341. /package/frontend/{lib/components → components}/chat/input/composables/use-textarea-resize.svelte.ts +0 -0
  342. /package/frontend/{lib/components → components}/chat/message/DateSeparator.svelte +0 -0
  343. /package/frontend/{lib/components → components}/chat/shared/index.ts +0 -0
  344. /package/frontend/{lib/components → components}/chat/shared/utils.ts +0 -0
  345. /package/frontend/{lib/components → components}/chat/tools/AgentTool.svelte +0 -0
  346. /package/frontend/{lib/components → components}/chat/tools/BashOutputTool.svelte +0 -0
  347. /package/frontend/{lib/components → components}/chat/tools/BashTool.svelte +0 -0
  348. /package/frontend/{lib/components → components}/chat/tools/EnterPlanModeTool.svelte +0 -0
  349. /package/frontend/{lib/components → components}/chat/tools/ExitPlanModeTool.svelte +0 -0
  350. /package/frontend/{lib/components → components}/chat/tools/GlobTool.svelte +0 -0
  351. /package/frontend/{lib/components → components}/chat/tools/GrepTool.svelte +0 -0
  352. /package/frontend/{lib/components → components}/chat/tools/ListMcpResourcesTool.svelte +0 -0
  353. /package/frontend/{lib/components → components}/chat/tools/NotebookEditTool.svelte +0 -0
  354. /package/frontend/{lib/components → components}/chat/tools/ReadMcpResourceTool.svelte +0 -0
  355. /package/frontend/{lib/components → components}/chat/tools/ReadTool.svelte +0 -0
  356. /package/frontend/{lib/components → components}/chat/tools/TaskStopTool.svelte +0 -0
  357. /package/frontend/{lib/components → components}/chat/tools/TaskTool.svelte +0 -0
  358. /package/frontend/{lib/components → components}/chat/tools/WebFetchTool.svelte +0 -0
  359. /package/frontend/{lib/components → components}/chat/tools/WebSearchTool.svelte +0 -0
  360. /package/frontend/{lib/components → components}/chat/tools/WriteTool.svelte +0 -0
  361. /package/frontend/{lib/components → components}/chat/tools/components/StatsBadges.svelte +0 -0
  362. /package/frontend/{lib/components → components}/chat/tools/components/TerminalCommand.svelte +0 -0
  363. /package/frontend/{lib/components → components}/chat/tools/components/index.ts +0 -0
  364. /package/frontend/{lib/components → components}/chat/tools/index.ts +0 -0
  365. /package/frontend/{lib/components → components}/checkpoint/timeline/TimelineEdge.svelte +0 -0
  366. /package/frontend/{lib/components → components}/checkpoint/timeline/TimelineGraph.svelte +0 -0
  367. /package/frontend/{lib/components → components}/checkpoint/timeline/TimelineVersionGroup.svelte +0 -0
  368. /package/frontend/{lib/components → components}/checkpoint/timeline/animation.ts +0 -0
  369. /package/frontend/{lib/components → components}/checkpoint/timeline/config.ts +0 -0
  370. /package/frontend/{lib/components → components}/checkpoint/timeline/graph-builder.ts +0 -0
  371. /package/frontend/{lib/components → components}/checkpoint/timeline/types.ts +0 -0
  372. /package/frontend/{lib/components → components}/checkpoint/timeline/utils.ts +0 -0
  373. /package/frontend/{lib/components/common → components/common/display}/AvatarBubble.svelte +0 -0
  374. /package/frontend/{lib/components/common → components/common/display}/Button.svelte +0 -0
  375. /package/frontend/{lib/components/common → components/common/display}/Card.svelte +0 -0
  376. /package/frontend/{lib/components/common → components/common/feedback}/LoadingSpinner.svelte +0 -0
  377. /package/frontend/{lib/components → components}/common/lucide-icons.ts +0 -0
  378. /package/frontend/{lib/components → components}/common/material-icons.ts +0 -0
  379. /package/frontend/{lib/components/common → components/common/overlay}/Modal.svelte +0 -0
  380. /package/frontend/{lib/components → components}/common/xterm/index.ts +0 -0
  381. /package/frontend/{lib/components → components}/common/xterm/terminal-config.ts +0 -0
  382. /package/frontend/{lib/components → components}/common/xterm/types.ts +0 -0
  383. /package/frontend/{lib/components → components}/preview/browser/components/VirtualCursor.svelte +0 -0
  384. /package/frontend/{lib/components → components}/preview/browser/core/stream-handler.svelte.ts +0 -0
  385. /package/frontend/{lib/components → components}/preview/index.ts +0 -0
  386. /package/frontend/{lib/components → components}/settings/account/AccountSettings.svelte +0 -0
  387. /package/frontend/{lib/components → components}/settings/general/GeneralSettings.svelte +0 -0
  388. /package/frontend/{lib/components → components}/settings/security/SecuritySettings.svelte +0 -0
  389. /package/frontend/{lib/components → components}/settings/system/SystemSettings.svelte +0 -0
  390. /package/frontend/{lib/components → components}/tunnel/TunnelQRCode.svelte +0 -0
  391. /package/frontend/{lib/services → services}/chat/index.ts +0 -0
  392. /package/frontend/{lib/services → services}/notification/index.ts +0 -0
  393. /package/frontend/{lib/services → services}/preview/index.ts +0 -0
  394. /package/frontend/{lib/services → services}/project/index.ts +0 -0
  395. /package/frontend/{lib/services → services}/terminal/index.ts +0 -0
  396. /package/frontend/{lib/services → services}/terminal/persistence.service.ts +0 -0
  397. /package/frontend/{lib/services → services}/terminal/session.service.ts +0 -0
  398. /package/frontend/{lib/stores → stores}/core/files.svelte.ts +0 -0
  399. /package/frontend/{lib/stores → stores}/ui/chat-input.svelte.ts +0 -0
  400. /package/frontend/{lib/stores → stores}/ui/chat-model.svelte.ts +0 -0
  401. /package/frontend/{lib/stores → stores}/ui/connection.svelte.ts +0 -0
  402. /package/frontend/{lib/stores → stores}/ui/dialog.svelte.ts +0 -0
  403. /package/frontend/{lib/stores → stores}/ui/notification.svelte.ts +0 -0
  404. /package/frontend/{lib/stores → stores}/ui/settings-modal.svelte.ts +0 -0
  405. /package/frontend/{lib/stores → stores}/ui/theme.svelte.ts +0 -0
  406. /package/frontend/{lib/stores → stores}/ui/workspace.svelte.ts +0 -0
  407. /package/frontend/{lib/utils → utils}/chat/date-separator.ts +0 -0
  408. /package/frontend/{lib/utils → utils}/chat/message-grouper.ts +0 -0
  409. /package/frontend/{lib/utils → utils}/chat/message-processor.ts +0 -0
  410. /package/frontend/{lib/utils → utils}/chat/tool-handler.ts +0 -0
  411. /package/frontend/{lib/utils → utils}/chat/virtual-scroll.svelte.ts +0 -0
  412. /package/frontend/{lib/utils → utils}/click-outside.ts +0 -0
  413. /package/frontend/{lib/utils → utils}/context-manager.ts +0 -0
  414. /package/frontend/{lib/utils → utils}/file-icon-mappings.ts +0 -0
  415. /package/frontend/{lib/utils → utils}/folder-icon-mappings.ts +0 -0
  416. /package/frontend/{lib/utils → utils}/git-status.ts +0 -0
  417. /package/frontend/{lib/types → utils}/native-ui.ts +0 -0
  418. /package/frontend/{lib/utils → utils}/platform.ts +0 -0
  419. /package/frontend/{lib/utils → utils}/port-check.ts +0 -0
  420. /package/frontend/{lib/constants/preview.ts → utils/preview-constants.ts} +0 -0
  421. /package/frontend/{lib/utils/terminalFormatter.ts → utils/terminal-formatter.ts} +0 -0
  422. /package/frontend/{lib/utils → utils}/tree-visualizer.ts +0 -0
@@ -1,372 +1,372 @@
1
- <script lang="ts">
2
- import { onMount } from 'svelte';
3
- import { fade } from 'svelte/transition';
4
- import Icon from '$frontend/lib/components/common/Icon.svelte';
5
- import { projectState, setCurrentProject, removeProject } from '$frontend/lib/stores/core/projects.svelte';
6
- import { workspaceState, toggleNavigator } from '$frontend/lib/stores/ui/workspace.svelte';
7
- import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
8
- import { openSettingsModal } from '$frontend/lib/stores/ui/settings-modal.svelte';
9
- import { projectStatusService } from '$frontend/lib/services/project';
10
- import { presenceState, getProjectStatusColor } from '$frontend/lib/stores/core/presence.svelte';
11
- import type { Project } from '$shared/types/database/schema';
12
- import { debug } from '$shared/utils/logger';
13
- import { settings } from '$frontend/lib/stores/features/settings.svelte';
14
- import FolderBrowser from '$frontend/lib/components/common/FolderBrowser.svelte';
15
- import Dialog from '$frontend/lib/components/common/Dialog.svelte';
16
- import ViewMenu from '$frontend/lib/components/workspace/ViewMenu.svelte';
17
- import TunnelButton from '$frontend/lib/components/tunnel/TunnelButton.svelte';
18
- import TunnelModal from '$frontend/lib/components/tunnel/TunnelModal.svelte';
19
- import ProjectUserAvatars from '$frontend/lib/components/common/ProjectUserAvatars.svelte';
20
- import ws from '$frontend/lib/utils/ws';
21
-
22
- // State
23
- let existingProjects = $state<Project[]>([]);
24
- let showFolderBrowser = $state(false);
25
- let showDeleteDialog = $state(false);
26
- let projectToDelete = $state<Project | null>(null);
27
- let searchQuery = $state('');
28
- let showTunnelModal = $state(false);
29
-
30
- // Derived
31
- const isCollapsed = $derived(workspaceState.navigatorCollapsed);
32
- const currentProjectId = $derived(projectState.currentProject?.id);
33
- const navigatorWidth = $derived(
34
- workspaceState.navigatorCollapsed ? 48 : Math.round(workspaceState.navigatorWidth * (settings.fontSize / 13))
35
- );
36
-
37
- const filteredProjects = $derived(() => {
38
- if (!searchQuery.trim()) return existingProjects;
39
- const query = searchQuery.toLowerCase();
40
- return existingProjects.filter(
41
- (p) => p.name.toLowerCase().includes(query) || p.path.toLowerCase().includes(query)
42
- );
43
- });
44
-
45
- // Load projects
46
- async function loadProjects() {
47
- try {
48
- const projects = await ws.http('projects:list', {});
49
- if (Array.isArray(projects)) {
50
- existingProjects = projects;
51
- }
52
- } catch (error) {
53
- debug.error('workspace', 'Failed to load projects:', error);
54
- }
55
- }
56
-
57
- // Select project
58
- async function selectProject(project: Project) {
59
- await setCurrentProject(project);
60
- await projectStatusService.startTracking(project.id);
61
-
62
- // Update last opened (handled by projects:get in setCurrentProject)
63
- }
64
-
65
- // Create project from folder
66
- async function createProjectFromFolder(folderPath: string, folderName: string) {
67
- try {
68
- showFolderBrowser = false;
69
-
70
- // Check if already exists
71
- const existing = existingProjects.find((p) => p.path === folderPath);
72
- if (existing) {
73
- await selectProject(existing);
74
- return;
75
- }
76
-
77
- const newProject = await ws.http('projects:create', { name: folderName, path: folderPath });
78
-
79
- await setCurrentProject(newProject);
80
- await loadProjects();
81
- } catch (error) {
82
- debug.error('workspace', 'Failed to create project:', error);
83
- addNotification({
84
- type: 'error',
85
- title: 'Error',
86
- message: 'Failed to create project',
87
- duration: 5000
88
- });
89
- }
90
- }
91
-
92
- // Delete project
93
- async function confirmDeleteProject() {
94
- if (!projectToDelete) return;
95
- const deleteId = projectToDelete.id!;
96
-
97
- try {
98
- await ws.http('projects:delete', { id: deleteId });
99
- removeProject(deleteId);
100
- existingProjects = existingProjects.filter(p => p.id !== deleteId);
101
- showDeleteDialog = false;
102
- projectToDelete = null;
103
- } catch (error) {
104
- debug.error('workspace', 'Failed to delete project:', error);
105
- addNotification({
106
- type: 'error',
107
- title: 'Error',
108
- message: 'Failed to delete project',
109
- duration: 5000
110
- });
111
- }
112
- }
113
-
114
- // Status color for project indicator — uses shared helper from presence store
115
-
116
- // Close folder browser
117
- function closeFolderBrowser() {
118
- showFolderBrowser = false;
119
- }
120
-
121
- // Close delete dialog
122
- function closeDeleteDialog() {
123
- showDeleteDialog = false;
124
- projectToDelete = null;
125
- }
126
-
127
- // Handle delete button click
128
- function handleDeleteClick(project: Project, event: MouseEvent) {
129
- event.stopPropagation();
130
- projectToDelete = project;
131
- showDeleteDialog = true;
132
- }
133
-
134
- onMount(async () => {
135
- await loadProjects();
136
- });
137
-
138
- // Get project initials (max 2 characters)
139
- function getProjectInitials(name: string): string {
140
- const words = name.trim().split(/[\s-_]+/);
141
- if (words.length >= 2) {
142
- // Multiple words: take first letter of first 2 words
143
- return (words[0][0] + words[1][0]).toUpperCase();
144
- }
145
- // Single word: take first 2 letters
146
- return name.substring(0, 2).toUpperCase();
147
- }
148
- </script>
149
-
150
- <!-- Project Navigator Sidebar -->
151
- <aside
152
- class="shrink-0 h-full bg-white dark:bg-slate-900/95 border-r border-slate-200 dark:border-slate-800 transition-[width] duration-200 z-20"
153
- style="width: {navigatorWidth}px"
154
- aria-label="Project Navigator"
155
- >
156
- <nav
157
- class="flex flex-col h-full bg-slate-50 dark:bg-slate-900/95 transition-all duration-200 {isCollapsed
158
- ? 'items-center'
159
- : ''}"
160
- >
161
- <!-- Header -->
162
- <header
163
- class="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800 {isCollapsed
164
- ? 'justify-center px-2'
165
- : ''}"
166
- >
167
- {#if !isCollapsed}
168
- <div class="flex items-center gap-2.5" in:fade={{ duration: 150 }}>
169
- <img src="/favicon.svg" alt="Clopen" class="w-8 h-8 rounded-lg" />
170
- <span class="text-base font-semibold text-slate-900 dark:text-slate-100">Clopen</span>
171
- </div>
172
- {/if}
173
-
174
- <button
175
- type="button"
176
- class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
177
- onclick={toggleNavigator}
178
- aria-label={isCollapsed ? 'Expand navigator' : 'Collapse navigator'}
179
- title={isCollapsed ? 'Expand' : 'Collapse'}
180
- >
181
- <Icon
182
- name={isCollapsed ? 'lucide:panel-left-open' : 'lucide:panel-left-close'}
183
- class="w-5 h-5"
184
- />
185
- </button>
186
- </header>
187
-
188
- {#if !isCollapsed}
189
- <!-- Search -->
190
- <div
191
- class="flex items-center gap-2.5 mx-4 my-3 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"
192
- in:fade={{ duration: 150 }}
193
- >
194
- <Icon name="lucide:search" class="w-4 h-4 text-slate-600 dark:text-slate-500 shrink-0" />
195
- <input
196
- type="text"
197
- bind:value={searchQuery}
198
- placeholder="Search projects..."
199
- class="flex-1 bg-transparent border-none outline-none text-slate-900 dark:text-slate-100 text-sm placeholder:text-slate-600 dark:placeholder:text-slate-500"
200
- />
201
- </div>
202
-
203
- <!-- Projects List -->
204
- <div class="flex-1 flex flex-col min-h-0 px-3" in:fade={{ duration: 150 }}>
205
- <div
206
- class="flex items-center justify-between py-2 px-1 text-xs font-semibold text-slate-600 dark:text-slate-500 uppercase tracking-wider"
207
- >
208
- <span>Projects</span>
209
- <button
210
- type="button"
211
- class="flex items-center justify-center w-6 h-6 bg-transparent border-none rounded-md text-slate-600 dark:text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/20 hover:text-violet-600"
212
- onclick={() => (showFolderBrowser = true)}
213
- aria-label="Add project"
214
- title="Add project"
215
- >
216
- <Icon name="lucide:plus" class="w-4 h-4" />
217
- </button>
218
- </div>
219
-
220
- <div class="flex-1 overflow-y-auto flex flex-col">
221
- {#each filteredProjects() as project (project.id)}
222
- <div
223
- class="flex items-center gap-2.5 py-2.5 px-3 bg-transparent border-none rounded-lg text-slate-600 dark:text-slate-400 text-sm text-left cursor-pointer transition-all duration-150 relative group
224
- hover:bg-violet-500/10
225
- {currentProjectId === project.id
226
- ? 'bg-violet-500/10 dark:bg-violet-500/20 text-slate-900 dark:text-slate-100'
227
- : ''}"
228
- role="button"
229
- title={project.path}
230
- tabindex="0"
231
- onclick={() => selectProject(project)}
232
- onkeydown={(e) => e.key === 'Enter' && selectProject(project)}
233
- >
234
- <div class="relative shrink-0">
235
- <Icon name="lucide:folder" class="w-4 h-4" />
236
- <span
237
- class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-slate-50 dark:border-slate-900/95 {getProjectStatusColor(project.id ?? '')}"
238
- ></span>
239
- </div>
240
-
241
- <div class="flex-1 flex items-center justify-between gap-2 min-w-0">
242
- <div class="flex-1 min-w-0">
243
- <span class="block overflow-hidden text-ellipsis whitespace-nowrap">{project.name}</span>
244
- <span class="block text-3xs text-slate-400 dark:text-slate-500 overflow-hidden text-ellipsis whitespace-nowrap font-mono leading-tight">{project.path}</span>
245
- </div>
246
- <div class="flex items-center gap-1 shrink-0">
247
- <ProjectUserAvatars projectStatus={presenceState.statuses.get(project.id ?? '')} maxVisible={2} />
248
- <button
249
- type="button"
250
- class="flex items-center justify-center w-6 h-6 bg-transparent border-none rounded-md text-slate-400 dark:text-slate-600 cursor-pointer transition-all duration-150 hover:bg-red-500/20 hover:text-red-500 shrink-0"
251
- onclick={(e) => handleDeleteClick(project, e)}
252
- aria-label="Delete project"
253
- title="Delete"
254
- >
255
- <Icon name="lucide:trash-2" class="w-3.5 h-3.5" />
256
- </button>
257
- </div>
258
- </div>
259
- </div>
260
- {:else}
261
- <div
262
- class="flex flex-col items-center gap-3 py-8 px-4 text-slate-600 dark:text-slate-500 text-sm text-center"
263
- >
264
- <Icon name="lucide:folder-plus" class="w-8 h-8 opacity-40" />
265
- <span>No projects yet</span>
266
- <button
267
- type="button"
268
- class="py-2 px-4 bg-violet-500/10 dark:bg-violet-500/15 border border-violet-500/20 dark:border-violet-500/30 rounded-lg text-violet-600 text-xs font-medium cursor-pointer transition-all duration-150 hover:bg-violet-500/20 dark:hover:bg-violet-500/25"
269
- onclick={() => (showFolderBrowser = true)}
270
- >
271
- Add your first project
272
- </button>
273
- </div>
274
- {/each}
275
- </div>
276
- </div>
277
-
278
- <!-- Footer Actions -->
279
- <footer class="flex flex-col p-3 border-t border-slate-200 dark:border-slate-800" in:fade={{ duration: 150 }}>
280
- <ViewMenu />
281
- <TunnelButton onClick={() => (showTunnelModal = true)} />
282
-
283
- <button
284
- type="button"
285
- class="flex items-center gap-2.5 w-full py-2.5 px-3 bg-transparent border-none rounded-lg text-slate-500 text-sm cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
286
- onclick={() => openSettingsModal()}
287
- >
288
- <Icon name="lucide:settings" class="w-4 h-4" />
289
- <span>Settings</span>
290
- </button>
291
- </footer>
292
- {:else}
293
- <!-- Collapsed State: Icon Buttons -->
294
- <div class="flex flex-col items-center pt-4 px-2 shrink-0">
295
- <button
296
- type="button"
297
- class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 relative hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
298
- onclick={() => (showFolderBrowser = true)}
299
- title="Add Project"
300
- >
301
- <Icon name="lucide:folder-plus" class="w-5 h-5" />
302
- </button>
303
-
304
- <div class="w-6 h-px bg-violet-500/10 my-1"></div>
305
- </div>
306
-
307
- <div class="flex-1 flex flex-col items-center gap-2 px-2 pb-4 min-h-0 overflow-y-auto">
308
- {#each existingProjects as project (project.id)}
309
- {@const projectStatus = presenceState.statuses.get(project.id ?? '')}
310
- {@const activeUserCount = (projectStatus?.activeUsers || []).length}
311
- <button
312
- type="button"
313
- class="flex items-center justify-center w-9 h-9 shrink-0 border-none rounded-lg cursor-pointer transition-all duration-150 relative font-semibold text-sm
314
- {currentProjectId === project.id
315
- ? 'bg-violet-500/10 dark:bg-violet-500/20 text-violet-700 dark:text-violet-300'
316
- : 'bg-slate-200/50 dark:bg-slate-800/50 text-slate-600 dark:text-slate-400 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100'}"
317
- onclick={() => selectProject(project)}
318
- title={project.name}
319
- >
320
- <span>{getProjectInitials(project.name)}</span>
321
- <span
322
- class="absolute bottom-1 right-1 w-2.5 h-2.5 rounded-full border-2 border-slate-50 dark:border-slate-900/95 {getProjectStatusColor(project.id ?? '')}"
323
- ></span>
324
- {#if activeUserCount > 0}
325
- <span
326
- class="absolute -top-1 -right-1 min-w-4 h-4 px-0.5 rounded-full bg-violet-500 text-white text-3xs font-bold flex items-center justify-center border-2 border-slate-50 dark:border-slate-900/95"
327
- >
328
- {activeUserCount}
329
- </span>
330
- {/if}
331
- </button>
332
- {/each}
333
- </div>
334
-
335
- <footer class="flex flex-col gap-2 py-3 px-2 border-t border-slate-200 dark:border-slate-800">
336
- <ViewMenu collapsed={true} />
337
- <TunnelButton collapsed={true} onClick={() => (showTunnelModal = true)} />
338
-
339
- <button
340
- type="button"
341
- class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 relative hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
342
- onclick={() => openSettingsModal()}
343
- title="Settings"
344
- >
345
- <Icon name="lucide:settings" class="w-5 h-5" />
346
- </button>
347
- </footer>
348
- {/if}
349
- </nav>
350
- </aside>
351
-
352
- <!-- Folder Browser (includes its own Modal) -->
353
- <FolderBrowser
354
- bind:isOpen={showFolderBrowser}
355
- onClose={closeFolderBrowser}
356
- onSelect={createProjectFromFolder}
357
- />
358
-
359
- <!-- Delete Confirmation Dialog -->
360
- <Dialog
361
- bind:isOpen={showDeleteDialog}
362
- onClose={closeDeleteDialog}
363
- type="error"
364
- title="Delete Project"
365
- message='This will remove "{projectToDelete?.name}" from your project list. The actual project files on disk will not be deleted.'
366
- confirmText="Delete"
367
- cancelText="Cancel"
368
- onConfirm={confirmDeleteProject}
369
- />
370
-
371
- <!-- Tunnel Modal -->
372
- <TunnelModal bind:isOpen={showTunnelModal} onClose={() => (showTunnelModal = false)} />
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { fade } from 'svelte/transition';
4
+ import Icon from '$frontend/components/common/display/Icon.svelte';
5
+ import { projectState, setCurrentProject, removeProject } from '$frontend/stores/core/projects.svelte';
6
+ import { workspaceState, toggleNavigator } from '$frontend/stores/ui/workspace.svelte';
7
+ import { addNotification } from '$frontend/stores/ui/notification.svelte';
8
+ import { openSettingsModal } from '$frontend/stores/ui/settings-modal.svelte';
9
+ import { projectStatusService } from '$frontend/services/project';
10
+ import { presenceState, getProjectStatusColor } from '$frontend/stores/core/presence.svelte';
11
+ import type { Project } from '$shared/types/database/schema';
12
+ import { debug } from '$shared/utils/logger';
13
+ import { settings } from '$frontend/stores/features/settings.svelte';
14
+ import FolderBrowser from '$frontend/components/common/form/FolderBrowser.svelte';
15
+ import Dialog from '$frontend/components/common/overlay/Dialog.svelte';
16
+ import ViewMenu from '$frontend/components/workspace/ViewMenu.svelte';
17
+ import TunnelButton from '$frontend/components/tunnel/TunnelButton.svelte';
18
+ import TunnelModal from '$frontend/components/tunnel/TunnelModal.svelte';
19
+ import ProjectUserAvatars from '$frontend/components/common/display/ProjectUserAvatars.svelte';
20
+ import ws from '$frontend/utils/ws';
21
+
22
+ // State
23
+ let existingProjects = $state<Project[]>([]);
24
+ let showFolderBrowser = $state(false);
25
+ let showDeleteDialog = $state(false);
26
+ let projectToDelete = $state<Project | null>(null);
27
+ let searchQuery = $state('');
28
+ let showTunnelModal = $state(false);
29
+
30
+ // Derived
31
+ const isCollapsed = $derived(workspaceState.navigatorCollapsed);
32
+ const currentProjectId = $derived(projectState.currentProject?.id);
33
+ const navigatorWidth = $derived(
34
+ workspaceState.navigatorCollapsed ? 48 : Math.round(workspaceState.navigatorWidth * (settings.fontSize / 13))
35
+ );
36
+
37
+ const filteredProjects = $derived(() => {
38
+ if (!searchQuery.trim()) return existingProjects;
39
+ const query = searchQuery.toLowerCase();
40
+ return existingProjects.filter(
41
+ (p) => p.name.toLowerCase().includes(query) || p.path.toLowerCase().includes(query)
42
+ );
43
+ });
44
+
45
+ // Load projects
46
+ async function loadProjects() {
47
+ try {
48
+ const projects = await ws.http('projects:list', {});
49
+ if (Array.isArray(projects)) {
50
+ existingProjects = projects;
51
+ }
52
+ } catch (error) {
53
+ debug.error('workspace', 'Failed to load projects:', error);
54
+ }
55
+ }
56
+
57
+ // Select project
58
+ async function selectProject(project: Project) {
59
+ await setCurrentProject(project);
60
+ await projectStatusService.startTracking(project.id);
61
+
62
+ // Update last opened (handled by projects:get in setCurrentProject)
63
+ }
64
+
65
+ // Create project from folder
66
+ async function createProjectFromFolder(folderPath: string, folderName: string) {
67
+ try {
68
+ showFolderBrowser = false;
69
+
70
+ // Check if already exists
71
+ const existing = existingProjects.find((p) => p.path === folderPath);
72
+ if (existing) {
73
+ await selectProject(existing);
74
+ return;
75
+ }
76
+
77
+ const newProject = await ws.http('projects:create', { name: folderName, path: folderPath });
78
+
79
+ await setCurrentProject(newProject);
80
+ await loadProjects();
81
+ } catch (error) {
82
+ debug.error('workspace', 'Failed to create project:', error);
83
+ addNotification({
84
+ type: 'error',
85
+ title: 'Error',
86
+ message: 'Failed to create project',
87
+ duration: 5000
88
+ });
89
+ }
90
+ }
91
+
92
+ // Delete project
93
+ async function confirmDeleteProject() {
94
+ if (!projectToDelete) return;
95
+ const deleteId = projectToDelete.id!;
96
+
97
+ try {
98
+ await ws.http('projects:delete', { id: deleteId });
99
+ removeProject(deleteId);
100
+ existingProjects = existingProjects.filter(p => p.id !== deleteId);
101
+ showDeleteDialog = false;
102
+ projectToDelete = null;
103
+ } catch (error) {
104
+ debug.error('workspace', 'Failed to delete project:', error);
105
+ addNotification({
106
+ type: 'error',
107
+ title: 'Error',
108
+ message: 'Failed to delete project',
109
+ duration: 5000
110
+ });
111
+ }
112
+ }
113
+
114
+ // Status color for project indicator — uses shared helper from presence store
115
+
116
+ // Close folder browser
117
+ function closeFolderBrowser() {
118
+ showFolderBrowser = false;
119
+ }
120
+
121
+ // Close delete dialog
122
+ function closeDeleteDialog() {
123
+ showDeleteDialog = false;
124
+ projectToDelete = null;
125
+ }
126
+
127
+ // Handle delete button click
128
+ function handleDeleteClick(project: Project, event: MouseEvent) {
129
+ event.stopPropagation();
130
+ projectToDelete = project;
131
+ showDeleteDialog = true;
132
+ }
133
+
134
+ onMount(async () => {
135
+ await loadProjects();
136
+ });
137
+
138
+ // Get project initials (max 2 characters)
139
+ function getProjectInitials(name: string): string {
140
+ const words = name.trim().split(/[\s-_]+/);
141
+ if (words.length >= 2) {
142
+ // Multiple words: take first letter of first 2 words
143
+ return (words[0][0] + words[1][0]).toUpperCase();
144
+ }
145
+ // Single word: take first 2 letters
146
+ return name.substring(0, 2).toUpperCase();
147
+ }
148
+ </script>
149
+
150
+ <!-- Project Navigator Sidebar -->
151
+ <aside
152
+ class="shrink-0 h-full bg-white dark:bg-slate-900/95 border-r border-slate-200 dark:border-slate-800 transition-[width] duration-200 z-20"
153
+ style="width: {navigatorWidth}px"
154
+ aria-label="Project Navigator"
155
+ >
156
+ <nav
157
+ class="flex flex-col h-full bg-slate-50 dark:bg-slate-900/95 transition-all duration-200 {isCollapsed
158
+ ? 'items-center'
159
+ : ''}"
160
+ >
161
+ <!-- Header -->
162
+ <header
163
+ class="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800 {isCollapsed
164
+ ? 'justify-center px-2'
165
+ : ''}"
166
+ >
167
+ {#if !isCollapsed}
168
+ <div class="flex items-center gap-2.5" in:fade={{ duration: 150 }}>
169
+ <img src="/favicon.svg" alt="Clopen" class="w-8 h-8 rounded-lg" />
170
+ <span class="text-base font-semibold text-slate-900 dark:text-slate-100">Clopen</span>
171
+ </div>
172
+ {/if}
173
+
174
+ <button
175
+ type="button"
176
+ class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
177
+ onclick={toggleNavigator}
178
+ aria-label={isCollapsed ? 'Expand navigator' : 'Collapse navigator'}
179
+ title={isCollapsed ? 'Expand' : 'Collapse'}
180
+ >
181
+ <Icon
182
+ name={isCollapsed ? 'lucide:panel-left-open' : 'lucide:panel-left-close'}
183
+ class="w-5 h-5"
184
+ />
185
+ </button>
186
+ </header>
187
+
188
+ {#if !isCollapsed}
189
+ <!-- Search -->
190
+ <div
191
+ class="flex items-center gap-2.5 mx-4 my-3 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"
192
+ in:fade={{ duration: 150 }}
193
+ >
194
+ <Icon name="lucide:search" class="w-4 h-4 text-slate-600 dark:text-slate-500 shrink-0" />
195
+ <input
196
+ type="text"
197
+ bind:value={searchQuery}
198
+ placeholder="Search projects..."
199
+ class="flex-1 bg-transparent border-none outline-none text-slate-900 dark:text-slate-100 text-sm placeholder:text-slate-600 dark:placeholder:text-slate-500"
200
+ />
201
+ </div>
202
+
203
+ <!-- Projects List -->
204
+ <div class="flex-1 flex flex-col min-h-0 px-3" in:fade={{ duration: 150 }}>
205
+ <div
206
+ class="flex items-center justify-between py-2 px-1 text-xs font-semibold text-slate-600 dark:text-slate-500 uppercase tracking-wider"
207
+ >
208
+ <span>Projects</span>
209
+ <button
210
+ type="button"
211
+ class="flex items-center justify-center w-6 h-6 bg-transparent border-none rounded-md text-slate-600 dark:text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/20 hover:text-violet-600"
212
+ onclick={() => (showFolderBrowser = true)}
213
+ aria-label="Add project"
214
+ title="Add project"
215
+ >
216
+ <Icon name="lucide:plus" class="w-4 h-4" />
217
+ </button>
218
+ </div>
219
+
220
+ <div class="flex-1 overflow-y-auto flex flex-col">
221
+ {#each filteredProjects() as project (project.id)}
222
+ <div
223
+ class="flex items-center gap-2.5 py-2.5 px-3 bg-transparent border-none rounded-lg text-slate-600 dark:text-slate-400 text-sm text-left cursor-pointer transition-all duration-150 relative group
224
+ hover:bg-violet-500/10
225
+ {currentProjectId === project.id
226
+ ? 'bg-violet-500/10 dark:bg-violet-500/20 text-slate-900 dark:text-slate-100'
227
+ : ''}"
228
+ role="button"
229
+ title={project.path}
230
+ tabindex="0"
231
+ onclick={() => selectProject(project)}
232
+ onkeydown={(e) => e.key === 'Enter' && selectProject(project)}
233
+ >
234
+ <div class="relative shrink-0">
235
+ <Icon name="lucide:folder" class="w-4 h-4" />
236
+ <span
237
+ class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-slate-50 dark:border-slate-900/95 {getProjectStatusColor(project.id ?? '')}"
238
+ ></span>
239
+ </div>
240
+
241
+ <div class="flex-1 flex items-center justify-between gap-2 min-w-0">
242
+ <div class="flex-1 min-w-0">
243
+ <span class="block overflow-hidden text-ellipsis whitespace-nowrap">{project.name}</span>
244
+ <span class="block text-3xs text-slate-400 dark:text-slate-500 overflow-hidden text-ellipsis whitespace-nowrap font-mono leading-tight">{project.path}</span>
245
+ </div>
246
+ <div class="flex items-center gap-1 shrink-0">
247
+ <ProjectUserAvatars projectStatus={presenceState.statuses.get(project.id ?? '')} maxVisible={2} />
248
+ <button
249
+ type="button"
250
+ class="flex items-center justify-center w-6 h-6 bg-transparent border-none rounded-md text-slate-400 dark:text-slate-600 cursor-pointer transition-all duration-150 hover:bg-red-500/20 hover:text-red-500 shrink-0"
251
+ onclick={(e) => handleDeleteClick(project, e)}
252
+ aria-label="Delete project"
253
+ title="Delete"
254
+ >
255
+ <Icon name="lucide:trash-2" class="w-3.5 h-3.5" />
256
+ </button>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ {:else}
261
+ <div
262
+ class="flex flex-col items-center gap-3 py-8 px-4 text-slate-600 dark:text-slate-500 text-sm text-center"
263
+ >
264
+ <Icon name="lucide:folder-plus" class="w-8 h-8 opacity-40" />
265
+ <span>No projects yet</span>
266
+ <button
267
+ type="button"
268
+ class="py-2 px-4 bg-violet-500/10 dark:bg-violet-500/15 border border-violet-500/20 dark:border-violet-500/30 rounded-lg text-violet-600 text-xs font-medium cursor-pointer transition-all duration-150 hover:bg-violet-500/20 dark:hover:bg-violet-500/25"
269
+ onclick={() => (showFolderBrowser = true)}
270
+ >
271
+ Add your first project
272
+ </button>
273
+ </div>
274
+ {/each}
275
+ </div>
276
+ </div>
277
+
278
+ <!-- Footer Actions -->
279
+ <footer class="flex flex-col p-3 border-t border-slate-200 dark:border-slate-800" in:fade={{ duration: 150 }}>
280
+ <ViewMenu />
281
+ <TunnelButton onClick={() => (showTunnelModal = true)} />
282
+
283
+ <button
284
+ type="button"
285
+ class="flex items-center gap-2.5 w-full py-2.5 px-3 bg-transparent border-none rounded-lg text-slate-500 text-sm cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
286
+ onclick={() => openSettingsModal()}
287
+ >
288
+ <Icon name="lucide:settings" class="w-4 h-4" />
289
+ <span>Settings</span>
290
+ </button>
291
+ </footer>
292
+ {:else}
293
+ <!-- Collapsed State: Icon Buttons -->
294
+ <div class="flex flex-col items-center pt-4 px-2 shrink-0">
295
+ <button
296
+ type="button"
297
+ class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 relative hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
298
+ onclick={() => (showFolderBrowser = true)}
299
+ title="Add Project"
300
+ >
301
+ <Icon name="lucide:folder-plus" class="w-5 h-5" />
302
+ </button>
303
+
304
+ <div class="w-6 h-px bg-violet-500/10 my-1"></div>
305
+ </div>
306
+
307
+ <div class="flex-1 flex flex-col items-center gap-2 px-2 pb-4 min-h-0 overflow-y-auto">
308
+ {#each existingProjects as project (project.id)}
309
+ {@const projectStatus = presenceState.statuses.get(project.id ?? '')}
310
+ {@const activeUserCount = (projectStatus?.activeUsers || []).length}
311
+ <button
312
+ type="button"
313
+ class="flex items-center justify-center w-9 h-9 shrink-0 border-none rounded-lg cursor-pointer transition-all duration-150 relative font-semibold text-sm
314
+ {currentProjectId === project.id
315
+ ? 'bg-violet-500/10 dark:bg-violet-500/20 text-violet-700 dark:text-violet-300'
316
+ : 'bg-slate-200/50 dark:bg-slate-800/50 text-slate-600 dark:text-slate-400 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100'}"
317
+ onclick={() => selectProject(project)}
318
+ title={project.name}
319
+ >
320
+ <span>{getProjectInitials(project.name)}</span>
321
+ <span
322
+ class="absolute bottom-1 right-1 w-2.5 h-2.5 rounded-full border-2 border-slate-50 dark:border-slate-900/95 {getProjectStatusColor(project.id ?? '')}"
323
+ ></span>
324
+ {#if activeUserCount > 0}
325
+ <span
326
+ class="absolute -top-1 -right-1 min-w-4 h-4 px-0.5 rounded-full bg-violet-500 text-white text-3xs font-bold flex items-center justify-center border-2 border-slate-50 dark:border-slate-900/95"
327
+ >
328
+ {activeUserCount}
329
+ </span>
330
+ {/if}
331
+ </button>
332
+ {/each}
333
+ </div>
334
+
335
+ <footer class="flex flex-col gap-2 py-3 px-2 border-t border-slate-200 dark:border-slate-800">
336
+ <ViewMenu collapsed={true} />
337
+ <TunnelButton collapsed={true} onClick={() => (showTunnelModal = true)} />
338
+
339
+ <button
340
+ type="button"
341
+ class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 relative hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
342
+ onclick={() => openSettingsModal()}
343
+ title="Settings"
344
+ >
345
+ <Icon name="lucide:settings" class="w-5 h-5" />
346
+ </button>
347
+ </footer>
348
+ {/if}
349
+ </nav>
350
+ </aside>
351
+
352
+ <!-- Folder Browser (includes its own Modal) -->
353
+ <FolderBrowser
354
+ bind:isOpen={showFolderBrowser}
355
+ onClose={closeFolderBrowser}
356
+ onSelect={createProjectFromFolder}
357
+ />
358
+
359
+ <!-- Delete Confirmation Dialog -->
360
+ <Dialog
361
+ bind:isOpen={showDeleteDialog}
362
+ onClose={closeDeleteDialog}
363
+ type="error"
364
+ title="Delete Project"
365
+ message='This will remove "{projectToDelete?.name}" from your project list. The actual project files on disk will not be deleted.'
366
+ confirmText="Delete"
367
+ cancelText="Cancel"
368
+ onConfirm={confirmDeleteProject}
369
+ />
370
+
371
+ <!-- Tunnel Modal -->
372
+ <TunnelModal bind:isOpen={showTunnelModal} onClose={() => (showTunnelModal = false)} />