@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,549 @@
1
+ <script lang="ts">
2
+ import Toolbar from './components/Toolbar.svelte';
3
+ import Container from './components/Container.svelte';
4
+ import VirtualCursor from './components/VirtualCursor.svelte';
5
+ import SelectDropdown from './components/SelectDropdown.svelte';
6
+ import ContextMenu from './components/ContextMenu.svelte';
7
+ import type { BrowserSelectInfo, BrowserContextMenuInfo } from '$frontend/lib/types/native-ui';
8
+ import { fly } from 'svelte/transition';
9
+ import { cubicOut } from 'svelte/easing';
10
+ import { onMount, onDestroy } from 'svelte';
11
+ import type { DeviceSize, Rotation } from '$frontend/lib/constants/preview';
12
+ import { debug } from '$shared/utils/logger';
13
+ import { createBrowserCoordinator } from './core/coordinator.svelte';
14
+ import { sendScaleUpdate } from './core/interactions.svelte';
15
+ import { projectState } from '$frontend/lib/stores/core/projects.svelte';
16
+
17
+ let {
18
+ url = $bindable(''),
19
+ isOpen = $bindable(false),
20
+ mode = $bindable<'split' | 'tab'>('split'),
21
+ deviceSize = $bindable<DeviceSize>('laptop'),
22
+ rotation = $bindable<Rotation>('portrait'),
23
+ previewDimensions = $bindable<any>({ scale: 1 })
24
+ } = $props();
25
+
26
+ // Get projectId from project state (REQUIRED for project isolation)
27
+ const projectId = $derived(projectState.currentProject?.id || '');
28
+
29
+ // URL input state - separate from actual preview URL
30
+ let urlInput = $state('');
31
+
32
+ // UI state
33
+ let isLoading = $state(false);
34
+ let isLaunchingBrowser = $state(false);
35
+ let isNavigating = $state(false);
36
+ let isReconnecting = $state(false); // True during fast reconnect after navigation
37
+ let sessionId = $state<string | null>(null);
38
+ let sessionInfo = $state<any>(null);
39
+ let isConnected = $state(false);
40
+ let isStreamReady = $state(false);
41
+ let errorMessage = $state<string | null>(null);
42
+ let isConsoleOpen = $state(false); // Console temporarily disabled
43
+ let consoleLogs = $state<any[]>([]);
44
+
45
+ // Virtual cursors
46
+ let virtualCursor = $state<{x: number, y: number, visible: boolean, clicking?: boolean}>({
47
+ x: 0, y: 0, visible: false, clicking: false
48
+ });
49
+ let mcpVirtualCursor = $state<{x: number, y: number, visible: boolean, clicking?: boolean}>({
50
+ x: 0, y: 0, visible: false, clicking: false
51
+ });
52
+
53
+ // Native UI state
54
+ let currentSelectInfo = $state<BrowserSelectInfo | null>(null);
55
+ let currentContextMenuInfo = $state<BrowserContextMenuInfo | null>(null);
56
+
57
+ // Canvas and preview state
58
+ let canvasAPI = $state<any>(null);
59
+ let currentTabLastFrameData = $state<any>(null);
60
+
61
+ // Flag to prevent URL watcher from double-launching during MCP session creation
62
+ const mcpLaunchInProgress = $state(false);
63
+
64
+ // Flag to track if sessions were recovered (prevents creating empty tab on mount)
65
+ let sessionsRecovered = $state(false);
66
+
67
+ // Create browser coordinator with projectId
68
+ const coordinator = createBrowserCoordinator({
69
+ projectId: () => projectId, // Pass projectId as getter function
70
+ onUrlChange: (newUrl) => {
71
+ url = newUrl;
72
+ },
73
+ onUrlInputChange: (newUrlInput) => {
74
+ urlInput = newUrlInput;
75
+ },
76
+ onSessionChange: (newSessionId) => {
77
+ sessionId = newSessionId;
78
+ },
79
+ onLoadingChange: (loading) => {
80
+ isLoading = loading;
81
+ },
82
+ onErrorChange: (error) => {
83
+ errorMessage = error;
84
+ },
85
+ onSessionsRecovered: (tabCount) => {
86
+ debug.log('preview', `📦 Sessions recovered callback: ${tabCount} tabs`);
87
+ sessionsRecovered = tabCount > 0;
88
+ },
89
+ onSelectOpen: (selectInfo) => {
90
+ currentSelectInfo = selectInfo;
91
+ },
92
+ onContextMenuOpen: (menuInfo) => {
93
+ currentContextMenuInfo = menuInfo;
94
+ },
95
+ onVirtualCursorUpdate: (x, y, clicking) => {
96
+ virtualCursor = { x, y, visible: true, clicking: clicking || false };
97
+ if (clicking) {
98
+ setTimeout(() => {
99
+ virtualCursor = { ...virtualCursor, clicking: false };
100
+ }, 300);
101
+ }
102
+ },
103
+ onVirtualCursorHide: () => {
104
+ virtualCursor = { ...virtualCursor, visible: false };
105
+ },
106
+ onMcpCursorUpdate: (x, y, clicking) => {
107
+ mcpVirtualCursor = { x, y, visible: true, clicking: clicking || false };
108
+ if (clicking) {
109
+ setTimeout(() => {
110
+ mcpVirtualCursor = { ...mcpVirtualCursor, clicking: false };
111
+ }, 300);
112
+ }
113
+ },
114
+ onMcpCursorHide: () => {
115
+ mcpVirtualCursor = { ...mcpVirtualCursor, visible: false };
116
+ },
117
+ transformBrowserToDisplayCoordinates: (browserX, browserY) => {
118
+ return transformBrowserToDisplayCoordinates(browserX, browserY);
119
+ }
120
+ });
121
+
122
+ const { tabManager, streamHandler, nativeUIHandler, mcpHandler } = coordinator;
123
+
124
+ // Derived states from tab manager
125
+ const tabs = $derived(tabManager.tabs);
126
+ const activeTabId = $derived(tabManager.activeTabId);
127
+ const activeTab = $derived(tabManager.activeTab);
128
+
129
+ // Initialize - session recovery is handled inside coordinator.initialize()
130
+ onMount(() => {
131
+ coordinator.initialize();
132
+ // Note: We don't create a new tab here anymore
133
+ // The coordinator will either:
134
+ // 1. Recover existing tabs from backend (after refresh)
135
+ // 2. Or we create a new tab via the $effect below when isOpen is true and tabs.length === 0
136
+ });
137
+
138
+ // Cleanup
139
+ onDestroy(async () => {
140
+ await coordinator.cleanup();
141
+ });
142
+
143
+ // Watch for project changes and reload sessions
144
+ let previousProjectId = '';
145
+ let initialRecoveryDone = false;
146
+ $effect(() => {
147
+ const currentProjectId = projectId;
148
+
149
+ // Initial recovery - trigger when projectId first becomes available after mount
150
+ if (!initialRecoveryDone && currentProjectId && previousProjectId === '') {
151
+ debug.log('preview', `🔄 Initial project loaded: ${currentProjectId}, triggering session recovery`);
152
+ previousProjectId = currentProjectId;
153
+ initialRecoveryDone = true;
154
+ // Trigger recovery for initial project
155
+ coordinator.switchProject();
156
+ return;
157
+ }
158
+
159
+ // Project changed - switch to new project
160
+ if (currentProjectId && currentProjectId !== previousProjectId && previousProjectId !== '') {
161
+ debug.log('preview', `🔄 Project changed: ${previousProjectId} → ${currentProjectId}`);
162
+ previousProjectId = currentProjectId;
163
+ coordinator.switchProject();
164
+ }
165
+ });
166
+
167
+ // Sync state from active tab
168
+ $effect(() => {
169
+ const tab = activeTab;
170
+ if (tab) {
171
+ url = tab.url;
172
+ urlInput = tab.url;
173
+ sessionId = tab.sessionId;
174
+ sessionInfo = tab.sessionInfo;
175
+ isConnected = tab.isConnected;
176
+ isStreamReady = tab.isStreamReady;
177
+ isLoading = tab.isLoading;
178
+ isLaunchingBrowser = tab.isLaunchingBrowser;
179
+ isNavigating = tab.isNavigating;
180
+ errorMessage = tab.errorMessage;
181
+ deviceSize = tab.deviceSize;
182
+ rotation = tab.rotation;
183
+ consoleLogs = tab.consoleLogs;
184
+ canvasAPI = tab.canvasAPI;
185
+ previewDimensions = tab.previewDimensions || { scale: 1 };
186
+ currentTabLastFrameData = tab.lastFrameData;
187
+
188
+ // Setup canvas after tab switch
189
+ if (canvasAPI && canvasAPI.setupCanvas) {
190
+ setTimeout(() => {
191
+ canvasAPI.setupCanvas();
192
+ }, 50);
193
+ }
194
+ }
195
+ });
196
+
197
+ // Create initial tab if opened and no tabs exist (and sessions weren't just recovered)
198
+ $effect(() => {
199
+ // Wait a bit for session recovery to complete before deciding to create empty tab
200
+ // The recovery happens async in initialize(), so we need to give it time
201
+ if (isOpen && tabs.length === 0 && !sessionsRecovered) {
202
+ // Longer delay to ensure session recovery has time to complete
203
+ // This prevents race condition where empty tab is created during restore
204
+ const timeout = setTimeout(() => {
205
+ // Double-check tabs.length after delay in case recovery happened
206
+ // Only create tab if still no tabs and not currently restoring
207
+ if (tabs.length === 0 && !sessionsRecovered) {
208
+ debug.log('preview', '📝 No recovered sessions after wait, creating empty tab');
209
+ coordinator.createNewTab('');
210
+ } else {
211
+ debug.log('preview', '📝 Tabs recovered during wait period, skipping empty tab creation');
212
+ }
213
+ }, 1000); // Increased from 500ms to 1000ms for safer delay
214
+ return () => clearTimeout(timeout);
215
+ }
216
+ });
217
+
218
+ // Watch for URL changes from parent (e.g., MCP integration)
219
+ let previousUrl = '';
220
+ $effect(() => {
221
+ if (!url || url === previousUrl) return;
222
+ if (mcpLaunchInProgress) {
223
+ previousUrl = url;
224
+ urlInput = url;
225
+ return;
226
+ }
227
+
228
+ debug.log('preview', `📝 URL changed from parent: ${previousUrl} → ${url}`);
229
+ previousUrl = url;
230
+ urlInput = url;
231
+
232
+ if (activeTabId) {
233
+ const tab = activeTab;
234
+ if (tab) {
235
+ // Prevent duplicate launches - check if already launching or has session for this URL
236
+ if (tab.isLaunchingBrowser) {
237
+ debug.log('preview', `⏭️ Skipping launch - already launching browser for ${activeTabId}`);
238
+ return;
239
+ }
240
+
241
+ // If URL hasn't changed from tab's URL, don't re-launch
242
+ if (tab.url === url && tab.sessionId) {
243
+ debug.log('preview', `⏭️ Skipping launch - URL unchanged and session exists`);
244
+ return;
245
+ }
246
+
247
+ tabManager.updateTab(activeTabId, {
248
+ url: url,
249
+ errorMessage: null
250
+ });
251
+
252
+ if (tab.sessionId) {
253
+ coordinator.navigateBrowserForTab(activeTabId, url);
254
+ } else {
255
+ coordinator.launchBrowserForTab(activeTabId, url);
256
+ }
257
+ }
258
+ } else {
259
+ coordinator.createNewTab(url);
260
+ }
261
+ });
262
+
263
+ // Store canvasAPI and previewDimensions in active tab
264
+ $effect(() => {
265
+ if (activeTabId && activeTab) {
266
+ const updates: any = {};
267
+ let needsUpdate = false;
268
+
269
+ if (canvasAPI && activeTab.canvasAPI !== canvasAPI) {
270
+ updates.canvasAPI = canvasAPI;
271
+ needsUpdate = true;
272
+ }
273
+
274
+ if (previewDimensions && JSON.stringify(activeTab.previewDimensions) !== JSON.stringify(previewDimensions)) {
275
+ updates.previewDimensions = previewDimensions;
276
+ needsUpdate = true;
277
+ }
278
+
279
+ if (needsUpdate) {
280
+ tabManager.updateTab(activeTabId, updates);
281
+ }
282
+ }
283
+ });
284
+
285
+ // Sync isStreamReady back to active tab
286
+ $effect(() => {
287
+ if (activeTabId && activeTab && activeTab.isStreamReady !== isStreamReady) {
288
+ tabManager.updateTab(activeTabId, { isStreamReady });
289
+ }
290
+ });
291
+
292
+ // Watch scale changes and send to backend
293
+ let lastSentScale = 1;
294
+ $effect(() => {
295
+ const currentScale = previewDimensions?.scale || 1;
296
+ if (currentScale !== lastSentScale && sessionId && isStreamReady) {
297
+ debug.log('preview', `📐 Scale changed to ${currentScale}, sending to backend`);
298
+ sendScaleUpdate(currentScale);
299
+ lastSentScale = currentScale;
300
+ }
301
+ });
302
+
303
+ // Initialize URL input
304
+ $effect(() => {
305
+ if (url && !url.startsWith('http://') && !url.startsWith('https://')) {
306
+ url = 'http://' + url;
307
+ }
308
+ if (url && !urlInput) {
309
+ urlInput = url;
310
+ }
311
+ });
312
+
313
+ // URL handling functions
314
+ function handleGoClick() {
315
+ if (!urlInput.trim()) return;
316
+
317
+ let processedUrl = urlInput.trim();
318
+ if (!processedUrl.startsWith('http://') && !processedUrl.startsWith('https://')) {
319
+ processedUrl = 'http://' + processedUrl;
320
+ }
321
+
322
+ if (!activeTabId) {
323
+ coordinator.createNewTab(processedUrl);
324
+ } else {
325
+ const tab = activeTab;
326
+ if (tab) {
327
+ tabManager.updateTab(activeTabId, {
328
+ url: processedUrl,
329
+ errorMessage: null
330
+ });
331
+
332
+ if (tab.sessionId) {
333
+ coordinator.navigateBrowserForTab(activeTabId, processedUrl);
334
+ } else {
335
+ coordinator.launchBrowserForTab(activeTabId, processedUrl);
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ function refreshPreview() {
342
+ if (!activeTabId) return;
343
+ const tab = activeTab;
344
+ if (tab && tab.sessionId && tab.url) {
345
+ coordinator.navigateBrowserForTab(activeTabId, tab.url);
346
+ }
347
+ }
348
+
349
+ async function closePreview() {
350
+ if (activeTabId && tabs.length > 0) {
351
+ coordinator.closeTab(activeTabId);
352
+ if (tabs.length === 0) {
353
+ isOpen = false;
354
+ }
355
+ } else {
356
+ isOpen = false;
357
+ }
358
+ }
359
+
360
+ function handleCanvasInteraction(action: any) {
361
+ const tab = activeTab;
362
+ if (tab && tab.sessionId) {
363
+ coordinator.sendInteraction(action);
364
+ }
365
+ }
366
+
367
+ function transformBrowserToDisplayCoordinates(browserX: number, browserY: number): { x: number, y: number } | null {
368
+ let canvasElement: HTMLCanvasElement | null = null;
369
+
370
+ // Try to get canvas element from canvasAPI first (preferred method)
371
+ if (canvasAPI && canvasAPI.getCanvasElement) {
372
+ canvasElement = canvasAPI.getCanvasElement();
373
+ }
374
+
375
+ // Fallback: Try to get canvas element directly from DOM if canvasAPI not ready yet
376
+ // This handles race condition where context menu event arrives before canvasAPI is set
377
+ if (!canvasElement && typeof document !== 'undefined') {
378
+ // Canvas is rendered inside Container component
379
+ const foundCanvas = document.querySelector('canvas[tabindex="0"]') as HTMLCanvasElement;
380
+ if (foundCanvas) {
381
+ canvasElement = foundCanvas;
382
+ debug.log('preview', `Using fallback DOM query to get canvas element`);
383
+ }
384
+ }
385
+
386
+ if (!canvasElement) {
387
+ debug.warn('preview', `Transform coordinates failed: canvasElement not found (browser: ${browserX}, ${browserY})`);
388
+ return null;
389
+ }
390
+
391
+ try {
392
+ const canvasRect = canvasElement.getBoundingClientRect();
393
+ const scaleX = canvasRect.width / canvasElement.width;
394
+ const scaleY = canvasRect.height / canvasElement.height;
395
+ const screenX = canvasRect.left + (browserX * scaleX);
396
+ const screenY = canvasRect.top + (browserY * scaleY);
397
+
398
+ return { x: screenX, y: screenY };
399
+ } catch (error) {
400
+ debug.error('preview', 'Error transforming coordinates:', error);
401
+ return null;
402
+ }
403
+ }
404
+
405
+ async function handleSelectOption(selectedIndex: number) {
406
+ if (!currentSelectInfo) return;
407
+ await nativeUIHandler.respondSelectOption(currentSelectInfo, selectedIndex);
408
+ currentSelectInfo = null;
409
+ }
410
+
411
+ function closeSelectDropdown() {
412
+ currentSelectInfo = null;
413
+ }
414
+
415
+ async function handleContextMenuItem(itemId: string) {
416
+ if (!currentContextMenuInfo) return;
417
+ const menuInfo = currentContextMenuInfo;
418
+ currentContextMenuInfo = null;
419
+ await nativeUIHandler.respondContextMenuItem(menuInfo, itemId);
420
+ }
421
+
422
+ function closeContextMenu() {
423
+ currentContextMenuInfo = null;
424
+ }
425
+
426
+ function isCurrentTabMcpControlled(): boolean {
427
+ return mcpHandler.isCurrentTabMcpControlled();
428
+ }
429
+
430
+ // Stream message handling
431
+ $effect(() => {
432
+ if (activeTabId && sessionId) {
433
+ // Message handling is done via coordinator
434
+ }
435
+ });
436
+
437
+ // Expose methods for parent (PreviewPanel)
438
+ export const browserActions = {
439
+ changeDeviceSize: (size: DeviceSize) => {
440
+ coordinator.changeDeviceSize(size, previewDimensions?.scale);
441
+ },
442
+ toggleRotation: () => {
443
+ coordinator.toggleRotation(previewDimensions?.scale);
444
+ },
445
+ getSessionInfo: () => sessionInfo,
446
+ getIsStreamReady: () => isStreamReady,
447
+ getErrorMessage: () => errorMessage
448
+ };
449
+ </script>
450
+
451
+ {#if isOpen && mode === 'split'}
452
+ <div
453
+ class="h-full flex flex-col theme-transition bg-slate-50 dark:bg-slate-900 dot-pattern"
454
+ in:fly={{ x: 300, duration: 300, easing: cubicOut }}
455
+ out:fly={{ x: 300, duration: 250, easing: cubicOut }}
456
+ >
457
+ <!-- Preview Toolbar -->
458
+ <Toolbar
459
+ bind:url
460
+ bind:urlInput
461
+ bind:isLoading
462
+ bind:isLaunchingBrowser
463
+ bind:isNavigating
464
+ bind:isReconnecting
465
+ bind:sessionId
466
+ bind:sessionInfo
467
+ bind:isConnected
468
+ bind:isStreamReady
469
+ bind:errorMessage
470
+ bind:isConsoleOpen
471
+ {tabs}
472
+ {activeTabId}
473
+ mcpControlledTabId={mcpHandler.mcpControlState.controlledTabId}
474
+ onGoClick={handleGoClick}
475
+ onRefresh={refreshPreview}
476
+ onOpenInExternalBrowser={() => {}}
477
+ onClosePreview={closePreview}
478
+ onToggleConsole={() => {}}
479
+ onUrlInput={() => {}}
480
+ onUrlKeydown={() => {}}
481
+ onSwitchTab={(tabId: string) => coordinator.switchToTab(tabId)}
482
+ onCloseTab={(tabId: string) => coordinator.closeTab(tabId)}
483
+ onNewTab={() => coordinator.createNewTab()}
484
+ />
485
+
486
+ <!-- Preview Container -->
487
+ <div class="flex-1 flex min-h-0">
488
+ <Container
489
+ projectId={projectId}
490
+ bind:url
491
+ bind:isLoading
492
+ bind:isLaunchingBrowser
493
+ bind:isNavigating
494
+ bind:isReconnecting
495
+ bind:deviceSize
496
+ bind:rotation
497
+ bind:sessionId
498
+ bind:sessionInfo
499
+ bind:isConnected
500
+ bind:isStreamReady
501
+ bind:errorMessage
502
+ bind:virtualCursor
503
+ bind:canvasAPI
504
+ bind:previewDimensions
505
+ bind:lastFrameData={currentTabLastFrameData}
506
+ isMcpControlled={isCurrentTabMcpControlled()}
507
+ onInteraction={handleCanvasInteraction}
508
+ onRetry={handleGoClick}
509
+ />
510
+ </div>
511
+
512
+ <!-- Virtual Cursor - User -->
513
+ {#if !isCurrentTabMcpControlled()}
514
+ <VirtualCursor cursor={virtualCursor} />
515
+ {/if}
516
+
517
+ <!-- MCP Virtual Cursor -->
518
+ {#if mcpHandler.mcpControlState.isControlled}
519
+ <VirtualCursor cursor={mcpVirtualCursor} />
520
+ {/if}
521
+
522
+ <!-- Native UI Overlays -->
523
+ <SelectDropdown
524
+ bind:selectInfo={currentSelectInfo}
525
+ onSelect={handleSelectOption}
526
+ onClose={closeSelectDropdown}
527
+ />
528
+
529
+ <ContextMenu
530
+ bind:menuInfo={currentContextMenuInfo}
531
+ onSelectItem={handleContextMenuItem}
532
+ onClose={closeContextMenu}
533
+ />
534
+ </div>
535
+ {/if}
536
+
537
+ <style>
538
+ /* Dot Pattern Background */
539
+ .dot-pattern {
540
+ background: center center / 1.5rem repeat transparent;
541
+ /* Light mode: darker dots */
542
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20width%3D'30'%20height%3D'30'%20fill%3D'none'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M1.227%200c.687%200%201.227.54%201.227%201.227s-.54%201.227-1.227%201.227S0%201.914%200%201.227.54%200%201.227%200z'%20fill%3D'rgba(0%2C0%2C0%2C0.25)'%2F%3E%3C%2Fsvg%3E");
543
+ }
544
+
545
+ /* Dark mode: lighter dots */
546
+ :global(.dark) .dot-pattern {
547
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20width%3D'30'%20height%3D'30'%20fill%3D'none'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M1.227%200c.687%200%201.227.54%201.227%201.227s-.54%201.227-1.227%201.227S0%201.914%200%201.227.54%200%201.227%200z'%20fill%3D'rgba(255%2C255%2C255%2C0.15)'%2F%3E%3C%2Fsvg%3E");
548
+ }
549
+ </style>