@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,161 @@
1
+ /**
2
+ * Tunnel Store
3
+ * Manages cloudflared tunnel state for projects
4
+ */
5
+
6
+ import { debug } from '$shared/utils/logger';
7
+ import ws from '$frontend/lib/utils/ws';
8
+
9
+ type TunnelProgress =
10
+ | { stage: 'idle' }
11
+ | { stage: 'checking-binary' }
12
+ | { stage: 'downloading-binary'; progress?: number }
13
+ | { stage: 'binary-ready' }
14
+ | { stage: 'starting-tunnel' }
15
+ | { stage: 'generating-url' }
16
+ | { stage: 'connected' }
17
+ | { stage: 'failed'; error: string };
18
+
19
+ interface TunnelInfo {
20
+ port: number;
21
+ publicUrl: string;
22
+ startedAt: string;
23
+ }
24
+
25
+ interface PortState {
26
+ isLoading: boolean;
27
+ error: string | null;
28
+ progress: TunnelProgress;
29
+ }
30
+
31
+ interface TunnelState {
32
+ tunnels: TunnelInfo[];
33
+ portStates: Record<number, PortState>;
34
+ }
35
+
36
+ // Tunnel store state
37
+ const tunnelState = $state<TunnelState>({
38
+ tunnels: [],
39
+ portStates: {}
40
+ });
41
+
42
+ export const tunnelStore = {
43
+ get tunnels() {
44
+ return tunnelState.tunnels;
45
+ },
46
+ isLoading(port: number) {
47
+ return tunnelState.portStates[port]?.isLoading ?? false;
48
+ },
49
+ getError(port: number) {
50
+ return tunnelState.portStates[port]?.error ?? null;
51
+ },
52
+ getProgress(port: number) {
53
+ return tunnelState.portStates[port]?.progress ?? { stage: 'idle' };
54
+ },
55
+ getTunnel(port: number) {
56
+ return tunnelState.tunnels.find((t) => t.port === port) || null;
57
+ },
58
+
59
+ /**
60
+ * Start tunnel globally
61
+ * Note: First time may take 30-90 seconds (downloading binary + starting tunnel)
62
+ */
63
+ async startTunnel(port: number, autoStopMinutes?: number) {
64
+ // Initialize port state
65
+ tunnelState.portStates[port] = {
66
+ isLoading: true,
67
+ error: null,
68
+ progress: { stage: 'starting-tunnel' }
69
+ };
70
+
71
+ debug.log('tunnel', `[Frontend] Starting tunnel for port ${port}...`);
72
+
73
+ try {
74
+ // Use HTTP pattern directly
75
+ const result = await ws.http('tunnel:start', { port, autoStopMinutes });
76
+
77
+ if (!result || !result.publicUrl) {
78
+ throw new Error('No result received from server');
79
+ }
80
+
81
+ // Add tunnel to the list
82
+ tunnelState.tunnels.push({
83
+ port,
84
+ publicUrl: result.publicUrl,
85
+ startedAt: new Date().toISOString()
86
+ });
87
+
88
+ tunnelState.portStates[port].progress = { stage: 'connected' };
89
+ tunnelState.portStates[port].isLoading = false;
90
+
91
+ debug.log('tunnel', `[Frontend] ✅ Tunnel started successfully on port ${port}:`, result.publicUrl);
92
+ if (result.timings) {
93
+ debug.log('tunnel', '[Frontend] Timings:', result.timings);
94
+ }
95
+
96
+ // Reset progress after a short delay
97
+ setTimeout(() => {
98
+ if (tunnelState.portStates[port]) {
99
+ tunnelState.portStates[port].progress = { stage: 'idle' };
100
+ }
101
+ }, 1500);
102
+ } catch (error) {
103
+ if (error instanceof Error) {
104
+ tunnelState.portStates[port].error = error.message;
105
+ tunnelState.portStates[port].progress = { stage: 'failed', error: error.message };
106
+ debug.error('tunnel', '[Frontend] Error:', error.message);
107
+ } else {
108
+ const errorMsg = 'Unknown error';
109
+ tunnelState.portStates[port].error = errorMsg;
110
+ tunnelState.portStates[port].progress = { stage: 'failed', error: errorMsg };
111
+ debug.error('tunnel', '[Frontend] Unknown error:', error);
112
+ }
113
+ tunnelState.portStates[port].isLoading = false;
114
+ throw error;
115
+ }
116
+ },
117
+
118
+ /**
119
+ * Stop tunnel for a port
120
+ */
121
+ async stopTunnel(port: number) {
122
+ try {
123
+ const response = await ws.http('tunnel:stop', { port });
124
+
125
+ if (!response.stopped) {
126
+ throw new Error('Failed to stop tunnel');
127
+ }
128
+
129
+ // Remove tunnel from the list
130
+ tunnelState.tunnels = tunnelState.tunnels.filter((t) => t.port !== port);
131
+ delete tunnelState.portStates[port];
132
+
133
+ debug.log('tunnel', `Tunnel stopped on port ${port}`);
134
+ } catch (error) {
135
+ debug.error('tunnel', 'Failed to stop tunnel:', error);
136
+ throw error;
137
+ }
138
+ },
139
+
140
+ /**
141
+ * Check tunnel status globally
142
+ */
143
+ async checkStatus() {
144
+ try {
145
+ const response = await ws.http('tunnel:status', {});
146
+
147
+ // Update tunnels list from server
148
+ tunnelState.tunnels = response.tunnels || [];
149
+ } catch (error) {
150
+ debug.error('tunnel', 'Failed to check tunnel status:', error);
151
+ }
152
+ },
153
+
154
+ /**
155
+ * Reset tunnel state
156
+ */
157
+ reset() {
158
+ tunnelState.tunnels = [];
159
+ tunnelState.portStates = {};
160
+ }
161
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * User Store - Svelte 5 Runes
3
+ * Manages anonymous user state and provides reactive updates
4
+ */
5
+
6
+ import { getOrCreateAnonymousUser, updateAnonymousUserName, getCurrentAnonymousUser, type AnonymousUser } from '$shared/utils/anonymous-user';
7
+ import { debug } from '$shared/utils/logger';
8
+ import ws from '$frontend/lib/utils/ws';
9
+
10
+ // User state - initialize with null, will be properly set after async load
11
+ let currentUser = $state<AnonymousUser | null>(null);
12
+ let isInitializing = $state<boolean>(false);
13
+
14
+ // User store
15
+ export const userStore = {
16
+ get currentUser() {
17
+ return currentUser;
18
+ },
19
+
20
+ get isInitializing() {
21
+ return isInitializing;
22
+ },
23
+
24
+ // Initialize user (called on app start)
25
+ async initialize() {
26
+ if (typeof window === 'undefined') {
27
+ debug.warn('user', 'Cannot initialize user on server side');
28
+ return;
29
+ }
30
+
31
+ if (isInitializing) {
32
+ debug.warn('user', 'User initialization already in progress');
33
+ return;
34
+ }
35
+
36
+ isInitializing = true;
37
+
38
+ try {
39
+ // First check if user already exists in localStorage (fast path)
40
+ const existingUser = getCurrentAnonymousUser();
41
+ if (existingUser) {
42
+ currentUser = existingUser;
43
+ // Sync user context with WebSocket (for user-targeted broadcasting)
44
+ // IMPORTANT: Must await to ensure server has context before other operations
45
+ await ws.setUser(existingUser.id);
46
+ debug.log('user', '✅ Loaded existing user from localStorage:', existingUser.name);
47
+ } else {
48
+ // Generate new user from server
49
+ debug.log('user', 'No existing user, generating from server...');
50
+ const newUser = await getOrCreateAnonymousUser();
51
+ currentUser = newUser;
52
+ // Sync user context with WebSocket
53
+ // IMPORTANT: Must await to ensure server has context before other operations
54
+ if (newUser) {
55
+ await ws.setUser(newUser.id);
56
+ }
57
+ }
58
+ } catch (error) {
59
+ debug.error('user', 'Failed to initialize user:', error);
60
+ } finally {
61
+ isInitializing = false;
62
+ }
63
+ },
64
+
65
+ // Update user name
66
+ async updateName(newName: string): Promise<boolean> {
67
+ if (typeof window === 'undefined') {
68
+ return false;
69
+ }
70
+
71
+ try {
72
+ const updatedUser = await updateAnonymousUserName(newName);
73
+
74
+ if (updatedUser) {
75
+ currentUser = updatedUser;
76
+ return true;
77
+ }
78
+
79
+ return false;
80
+ } catch (error) {
81
+ debug.error('user', 'Failed to update user name:', error);
82
+ return false;
83
+ }
84
+ },
85
+
86
+ // Refresh user from localStorage
87
+ refresh() {
88
+ if (typeof window !== 'undefined') {
89
+ const user = getCurrentAnonymousUser();
90
+ if (user) {
91
+ currentUser = user;
92
+ debug.log('user', '✅ Refreshed user from localStorage:', user.name);
93
+ }
94
+ }
95
+ }
96
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Chat Input State Store
3
+ * Manages the chat input text and focus state
4
+ */
5
+
6
+ interface ChatInputState {
7
+ text: string;
8
+ shouldFocus: boolean;
9
+ }
10
+
11
+ // Create reactive state
12
+ const state = $state<ChatInputState>({
13
+ text: '',
14
+ shouldFocus: false
15
+ });
16
+
17
+ /**
18
+ * Set input text and optionally focus
19
+ */
20
+ export function setInputText(text: string, focus: boolean = true) {
21
+ state.text = text;
22
+ state.shouldFocus = focus;
23
+ }
24
+
25
+ /**
26
+ * Clear input text
27
+ */
28
+ export function clearInput() {
29
+ state.text = '';
30
+ state.shouldFocus = false;
31
+ }
32
+
33
+ /**
34
+ * Reset focus flag after focusing
35
+ */
36
+ export function resetFocus() {
37
+ state.shouldFocus = false;
38
+ }
39
+
40
+ // Flag to skip next server restore (prevents stale input restoration
41
+ // when ChatInput is remounted during welcome→chat transition)
42
+ let _skipNextRestore = false;
43
+
44
+ export function setSkipNextRestore(skip: boolean) {
45
+ _skipNextRestore = skip;
46
+ }
47
+
48
+ export function shouldSkipRestore(): boolean {
49
+ if (_skipNextRestore) {
50
+ _skipNextRestore = false;
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+
56
+ // Export reactive state
57
+ export const chatInputState = state;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Chat Model State Store
3
+ *
4
+ * Holds the local engine/model selection for the chat input.
5
+ * Isolated from Settings — Settings only provides the initial default.
6
+ * Changes here do NOT persist to Settings, and Settings changes do NOT
7
+ * affect the current session's selection after the first message is sent.
8
+ */
9
+
10
+ import { DEFAULT_ENGINE, DEFAULT_MODEL } from '$shared/constants/engines';
11
+ import type { EngineType } from '$shared/types/engine';
12
+
13
+ interface ChatModelState {
14
+ engine: EngineType;
15
+ model: string;
16
+ engineModelMemory: Record<string, string>;
17
+ claudeAccountId: number | null;
18
+ }
19
+
20
+ // Local reactive state — starts from compile-time defaults.
21
+ // Initialized from Settings on each new session via initChatModel().
22
+ export const chatModelState = $state<ChatModelState>({
23
+ engine: DEFAULT_ENGINE,
24
+ model: DEFAULT_MODEL,
25
+ engineModelMemory: { 'claude-code': DEFAULT_MODEL },
26
+ claudeAccountId: null
27
+ });
28
+
29
+ /**
30
+ * Initialize the local chat model state from Settings defaults.
31
+ * Called when a new session starts (no messages yet).
32
+ */
33
+ export function initChatModel(
34
+ engine: EngineType,
35
+ model: string,
36
+ memory: Record<string, string>
37
+ ): void {
38
+ chatModelState.engine = engine;
39
+ chatModelState.model = model;
40
+ chatModelState.engineModelMemory = { ...memory };
41
+ // claudeAccountId will be set by EngineModelPicker after fetching accounts
42
+ chatModelState.claudeAccountId = null;
43
+ }
44
+
45
+ /**
46
+ * Restore the local chat model state from a session's persisted engine/model.
47
+ * Called when continuing an existing session (has messages).
48
+ * IMPORTANT: Must NOT read from chatModelState to avoid circular tracking in $effect.
49
+ */
50
+ export function restoreChatModelFromSession(
51
+ engine: EngineType,
52
+ model: string,
53
+ claudeAccountId?: number | null
54
+ ): void {
55
+ chatModelState.engine = engine;
56
+ chatModelState.model = model;
57
+ // Only set the current engine's model — avoids reading chatModelState.engineModelMemory
58
+ // which would cause UpdatedAtError in Svelte 5 $effect tracking
59
+ chatModelState.engineModelMemory = { [engine]: model };
60
+ chatModelState.claudeAccountId = claudeAccountId ?? null;
61
+ }
@@ -0,0 +1,59 @@
1
+ import type { AlertOptions, ConfirmOptions, DialogState } from '$shared/types/stores/dialog';
2
+
3
+ const dialogState = $state<DialogState>({
4
+ alert: {
5
+ isOpen: false,
6
+ options: {
7
+ message: ''
8
+ }
9
+ },
10
+ confirm: {
11
+ isOpen: false,
12
+ options: {
13
+ message: ''
14
+ }
15
+ }
16
+ });
17
+
18
+ export const showAlert = (options: AlertOptions): Promise<void> => {
19
+ return new Promise((resolve) => {
20
+ dialogState.alert = {
21
+ isOpen: true,
22
+ options,
23
+ resolve
24
+ };
25
+ });
26
+ };
27
+
28
+ export const showConfirm = (options: ConfirmOptions): Promise<boolean> => {
29
+ return new Promise((resolve) => {
30
+ dialogState.confirm = {
31
+ isOpen: true,
32
+ options,
33
+ resolve
34
+ };
35
+ });
36
+ };
37
+
38
+ export const closeAlert = () => {
39
+ if (dialogState.alert.resolve) {
40
+ dialogState.alert.resolve();
41
+ }
42
+ dialogState.alert.isOpen = false;
43
+ };
44
+
45
+ export const closeConfirm = (confirmed: boolean) => {
46
+ if (dialogState.confirm.resolve) {
47
+ dialogState.confirm.resolve(confirmed);
48
+ }
49
+ dialogState.confirm.isOpen = false;
50
+ };
51
+
52
+ export const dialogStore = {
53
+ get alert() {
54
+ return dialogState.alert;
55
+ },
56
+ get confirm() {
57
+ return dialogState.confirm;
58
+ }
59
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Edit Mode State Store
3
+ * Manages message editing state with collaborative sync
4
+ * Server is single source of truth - no client storage
5
+ */
6
+
7
+ import ws from '$frontend/lib/utils/ws';
8
+ import { userStore } from '$frontend/lib/stores/features/user.svelte';
9
+ import { sessionState } from '$frontend/lib/stores/core/sessions.svelte';
10
+ import { debug } from '$shared/utils/logger';
11
+
12
+ export interface EditAttachment {
13
+ type: 'image' | 'document';
14
+ data: string; // base64
15
+ mediaType: string;
16
+ fileName: string;
17
+ }
18
+
19
+ interface EditModeState {
20
+ isEditing: boolean;
21
+ messageId: string | null;
22
+ messageText: string;
23
+ messageTimestamp: string | null;
24
+ attachments: EditAttachment[];
25
+ parentMessageId: string | null;
26
+ }
27
+
28
+ // Create reactive state
29
+ const state = $state<EditModeState>({
30
+ isEditing: false,
31
+ messageId: null,
32
+ messageText: '',
33
+ messageTimestamp: null,
34
+ attachments: [],
35
+ parentMessageId: null
36
+ });
37
+
38
+ // ============================================================================
39
+ // BROADCAST
40
+ // ============================================================================
41
+
42
+ function broadcastEditMode(isEditing: boolean, messageId: string | null, messageTimestamp: string | null) {
43
+ const currentUserId = userStore.currentUser?.id;
44
+ const chatSessionId = sessionState.currentSession?.id;
45
+ if (!currentUserId || !chatSessionId) return;
46
+
47
+ ws.emit('chat:edit-mode', {
48
+ senderId: currentUserId,
49
+ chatSessionId,
50
+ isEditing,
51
+ messageId,
52
+ messageTimestamp
53
+ });
54
+ }
55
+
56
+ // ============================================================================
57
+ // PUBLIC API
58
+ // ============================================================================
59
+
60
+ /**
61
+ * Enter edit mode for a message
62
+ */
63
+ export function startEdit(
64
+ messageId: string,
65
+ messageText: string,
66
+ messageTimestamp: string,
67
+ attachments: EditAttachment[] = [],
68
+ parentMessageId: string | null = null
69
+ ) {
70
+ state.isEditing = true;
71
+ state.messageId = messageId;
72
+ state.messageText = messageText;
73
+ state.messageTimestamp = messageTimestamp;
74
+ state.attachments = attachments;
75
+ state.parentMessageId = parentMessageId;
76
+
77
+ broadcastEditMode(true, messageId, messageTimestamp);
78
+ }
79
+
80
+ /**
81
+ * Cancel edit mode
82
+ */
83
+ export function cancelEdit() {
84
+ state.isEditing = false;
85
+ state.messageId = null;
86
+ state.messageText = '';
87
+ state.messageTimestamp = null;
88
+ state.attachments = [];
89
+ state.parentMessageId = null;
90
+
91
+ broadcastEditMode(false, null, null);
92
+ }
93
+
94
+ /**
95
+ * Check if a message is after the edit point
96
+ */
97
+ export function isMessageAfterEditPoint(messageTimestamp: string): boolean {
98
+ if (!state.isEditing || !state.messageTimestamp) {
99
+ return false;
100
+ }
101
+ return messageTimestamp > state.messageTimestamp;
102
+ }
103
+
104
+ /**
105
+ * Check if a message is the one being edited
106
+ */
107
+ export function isMessageBeingEdited(messageId: string | undefined): boolean {
108
+ if (!state.isEditing || !state.messageId || !messageId) {
109
+ return false;
110
+ }
111
+ return messageId === state.messageId;
112
+ }
113
+
114
+ /**
115
+ * Check if a message should be dimmed (not the one being edited)
116
+ */
117
+ export function shouldDimMessage(messageId: string | undefined): boolean {
118
+ if (!state.isEditing) {
119
+ return false;
120
+ }
121
+ return !isMessageBeingEdited(messageId);
122
+ }
123
+
124
+ // ============================================================================
125
+ // SERVER STATE MANAGEMENT
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Clear state without broadcasting (for project switching)
130
+ */
131
+ function clearStateQuietly() {
132
+ state.isEditing = false;
133
+ state.messageId = null;
134
+ state.messageText = '';
135
+ state.messageTimestamp = null;
136
+ state.attachments = [];
137
+ state.parentMessageId = null;
138
+ }
139
+
140
+ /**
141
+ * Fetch edit mode state from server for the current project.
142
+ * Server is the single source of truth.
143
+ */
144
+ export async function restoreEditMode() {
145
+ try {
146
+ const chatSessionId = sessionState.currentSession?.id || '';
147
+ const serverState = await ws.http('chat:get-edit-mode', { chatSessionId });
148
+ if (serverState && serverState.isEditing && serverState.messageId) {
149
+ state.isEditing = true;
150
+ state.messageId = serverState.messageId;
151
+ state.messageText = '';
152
+ state.messageTimestamp = serverState.messageTimestamp;
153
+ state.attachments = [];
154
+ state.parentMessageId = null;
155
+ } else {
156
+ clearStateQuietly();
157
+ }
158
+ } catch {
159
+ clearStateQuietly();
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Called when switching away from a project.
165
+ * Clears in-memory state without broadcasting (server retains per-project state).
166
+ */
167
+ export function onProjectLeave() {
168
+ clearStateQuietly();
169
+ }
170
+
171
+ /**
172
+ * Called when entering a new project.
173
+ * Fetches edit mode state from server for the new project.
174
+ * MUST be called AFTER ws.setProject() has completed.
175
+ */
176
+ export async function onProjectEnter() {
177
+ await restoreEditMode();
178
+ }
179
+
180
+ // ============================================================================
181
+ // COLLABORATIVE LISTENER
182
+ // ============================================================================
183
+
184
+ /**
185
+ * Setup WebSocket listener for edit mode events from other users.
186
+ * Call once during app initialization.
187
+ */
188
+ export function setupEditModeListener() {
189
+ ws.on('chat:edit-mode', (data: {
190
+ senderId: string;
191
+ isEditing: boolean;
192
+ messageId: string | null;
193
+ messageTimestamp: string | null;
194
+ }) => {
195
+ const currentUserId = userStore.currentUser?.id;
196
+ if (!currentUserId || data.senderId === currentUserId) return;
197
+
198
+ if (data.isEditing && data.messageId) {
199
+ debug.log('chat', `User ${data.senderId} started editing message ${data.messageId}`);
200
+ state.isEditing = true;
201
+ state.messageId = data.messageId;
202
+ state.messageText = '';
203
+ state.messageTimestamp = data.messageTimestamp;
204
+ state.attachments = [];
205
+ state.parentMessageId = null;
206
+ } else {
207
+ debug.log('chat', `User ${data.senderId} cancelled edit mode`);
208
+ clearStateQuietly();
209
+ }
210
+ });
211
+ }
212
+
213
+ // Export reactive state
214
+ export const editModeState = state;