@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,450 @@
1
+ <script lang="ts">
2
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
3
+ import Canvas from './Canvas.svelte';
4
+ import { scale } from 'svelte/transition';
5
+ import { cubicOut } from 'svelte/easing';
6
+ import { onDestroy } from 'svelte';
7
+ import { getViewportDimensions, type DeviceSize, type Rotation } from '$frontend/lib/constants/preview';
8
+ import { debug } from '$shared/utils/logger';
9
+ import { sendScaleUpdate } from '../core/interactions.svelte';
10
+
11
+ let {
12
+ projectId = '', // REQUIRED for project isolation (read-only from parent)
13
+ url = $bindable(''),
14
+ isLoading = $bindable(false),
15
+ isLaunchingBrowser = $bindable(false),
16
+ isNavigating = $bindable(false),
17
+ isReconnecting = $bindable(false), // True during fast reconnect after navigation
18
+ deviceSize = $bindable<DeviceSize>('laptop'),
19
+ rotation = $bindable<Rotation>('portrait'),
20
+ sessionId = $bindable<string | null>(null),
21
+ sessionInfo = $bindable<any>(null),
22
+ isConnected = $bindable(false),
23
+ isStreamReady = $bindable(false), // True when first frame received from WebCodecs
24
+ errorMessage = $bindable<string | null>(null),
25
+ virtualCursor = $bindable<{ x: number; y: number; visible: boolean; clicking?: boolean }>({
26
+ x: 0,
27
+ y: 0,
28
+ visible: false
29
+ }),
30
+ lastFrameData = $bindable<any>(null), // Add lastFrameData prop
31
+
32
+ // MCP Control State
33
+ isMcpControlled = $bindable(false),
34
+
35
+ // Canvas API
36
+ canvasAPI = $bindable<any>(null),
37
+
38
+ // Preview dimensions (bindable to parent)
39
+ previewDimensions = $bindable<any>({ scale: 1 }),
40
+
41
+ // Callbacks
42
+ onInteraction = $bindable<(action: any) => void>(() => {}),
43
+ onRetry = $bindable<() => void>(() => {})
44
+ } = $props();
45
+
46
+ let previewContainer = $state<HTMLDivElement | undefined>();
47
+
48
+ // Solid loading overlay: shown during initial load states
49
+ const showSolidOverlay = $derived(
50
+ isLaunchingBrowser || !sessionInfo || (!isStreamReady && !isNavigating && !isReconnecting)
51
+ );
52
+
53
+ // Navigation overlay state with debounce to prevent flickering during state transitions
54
+ let showNavigationOverlay = $state(false);
55
+ let overlayHideTimeout: ReturnType<typeof setTimeout> | null = null;
56
+
57
+ // Debounced navigation overlay - similar to progress bar logic
58
+ // Shows immediately when navigating/reconnecting, hides with delay to prevent flicker
59
+ $effect(() => {
60
+ const shouldShowOverlay = (isNavigating || isReconnecting) && isStreamReady;
61
+
62
+ // Cancel any pending hide when overlay should show
63
+ if (shouldShowOverlay && overlayHideTimeout) {
64
+ clearTimeout(overlayHideTimeout);
65
+ overlayHideTimeout = null;
66
+ }
67
+
68
+ // Show immediately
69
+ if (shouldShowOverlay && !showNavigationOverlay) {
70
+ showNavigationOverlay = true;
71
+ }
72
+ // Hide with debounce to handle state transitions
73
+ else if (!shouldShowOverlay && showNavigationOverlay && !overlayHideTimeout) {
74
+ overlayHideTimeout = setTimeout(() => {
75
+ overlayHideTimeout = null;
76
+ // Re-check if we should still hide
77
+ const stillShouldHide = !(isNavigating || isReconnecting) || !isStreamReady;
78
+ if (stillShouldHide) {
79
+ showNavigationOverlay = false;
80
+ }
81
+ }, 100); // 100ms debounce
82
+ }
83
+ });
84
+
85
+ onDestroy(() => {
86
+ if (overlayHideTimeout) {
87
+ clearTimeout(overlayHideTimeout);
88
+ }
89
+ });
90
+
91
+ // Use imported device viewports from config
92
+
93
+ // Calculate and update preview dimensions
94
+ function calculatePreviewDimensions() {
95
+ if (!previewContainer) {
96
+ previewDimensions = {
97
+ width: '100%',
98
+ height: '100%',
99
+ scale: 1,
100
+ frameWidth: '120rem',
101
+ frameHeight: '67.5rem',
102
+ containerWidth: '100%',
103
+ containerHeight: '100%'
104
+ };
105
+ return;
106
+ }
107
+
108
+ const containerRect = previewContainer.getBoundingClientRect();
109
+ const availableWidth = containerRect.width * 1.2;
110
+ const availableHeight = containerRect.height * 1.2;
111
+
112
+ // Use getViewportDimensions for consistent viewport calculation
113
+ // This ensures portrait = height > width, landscape = width > height
114
+ const { width: deviceWidth, height: deviceHeight } = getViewportDimensions(deviceSize as DeviceSize, rotation as Rotation);
115
+
116
+ // Calculate scale to fit in container while maintaining aspect ratio
117
+ // Important: Never scale up beyond original size (scale max = 1)
118
+ const scaleX = Math.min(1, availableWidth / deviceWidth);
119
+ const scaleY = Math.min(1, availableHeight / deviceHeight);
120
+ const scale = Math.min(scaleX, scaleY);
121
+
122
+ // Calculate container dimensions (what user sees)
123
+ const containerWidth = deviceWidth * scale;
124
+ const containerHeight = deviceHeight * scale;
125
+
126
+ previewDimensions = {
127
+ width: `${containerWidth / 16}rem`,
128
+ height: `${containerHeight / 16}rem`,
129
+ scale: scale,
130
+ frameWidth: `${deviceWidth / 16}rem`,
131
+ frameHeight: `${deviceHeight / 16}rem`,
132
+ containerWidth: `${containerWidth / 16}rem`,
133
+ containerHeight: `${containerHeight / 16}rem`
134
+ };
135
+ }
136
+
137
+ // Handle canvas interactions
138
+ function handleCanvasInteraction(action: any) {
139
+ // Block user interactions when MCP is controlling
140
+ if (isMcpControlled) {
141
+ debug.log('preview', '🚫 User interaction blocked - MCP is controlling');
142
+ return;
143
+ }
144
+ onInteraction(action);
145
+ }
146
+
147
+ function handleCursorUpdate(cursor: string) {
148
+ // Handle cursor updates if needed
149
+ }
150
+
151
+ function handleFrameUpdate(data: any) {
152
+ // Handle frame updates if needed
153
+ }
154
+
155
+ // Handle retry button click
156
+ function handleRetryClick() {
157
+ // Clear error message immediately for instant UI feedback
158
+ errorMessage = null;
159
+ // Call parent retry handler
160
+ onRetry();
161
+ }
162
+
163
+ // Handle screencast refresh request from Canvas (when stream is stuck)
164
+ // This sends a scale-update which triggers CDP screencast restart on backend
165
+ function handleScreencastRefresh() {
166
+ if (previewDimensions?.scale) {
167
+ debug.log('preview', `📐 Requesting screencast refresh with scale: ${previewDimensions.scale}`);
168
+ sendScaleUpdate(previewDimensions.scale);
169
+ }
170
+ }
171
+
172
+ // Initial dimensions calculation
173
+ $effect(() => {
174
+ if (previewContainer) {
175
+ debug.log('preview', `📐 PreviewContainer: Initial calculation`);
176
+ calculatePreviewDimensions();
177
+ }
178
+ });
179
+
180
+ // Recalculate dimensions when device size or rotation changes
181
+ $effect(() => {
182
+ if (previewContainer) {
183
+ // Trigger reactive recalculation by accessing reactive values
184
+ void deviceSize;
185
+ void rotation;
186
+ debug.log(
187
+ 'preview',
188
+ `📐 PreviewContainer: Recalculating dimensions for ${deviceSize}/${rotation}`
189
+ );
190
+ // Force reflow for accurate container dimensions
191
+ setTimeout(() => {
192
+ if (previewContainer) {
193
+ calculatePreviewDimensions();
194
+ debug.log(
195
+ 'preview',
196
+ `📐 PreviewContainer: New dimensions calculated - scale: ${previewDimensions.scale}`
197
+ );
198
+ }
199
+ }, 50); // Small delay to ensure layout is updated
200
+ }
201
+ });
202
+
203
+ // Force recalculation when sessionId changes (tab switch)
204
+ $effect(() => {
205
+ if (previewContainer && sessionId) {
206
+ debug.log(
207
+ 'preview',
208
+ `📐 PreviewContainer: SessionId changed to ${sessionId}, forcing recalculation`
209
+ );
210
+ setTimeout(() => {
211
+ if (previewContainer) {
212
+ calculatePreviewDimensions();
213
+ debug.log(
214
+ 'preview',
215
+ `📐 PreviewContainer: Forced calculation done - scale: ${previewDimensions.scale}`
216
+ );
217
+ }
218
+ }, 100); // Slightly longer delay to ensure all state is synced
219
+ }
220
+ });
221
+
222
+ // Also force recalculation when URL changes (navigation within tab)
223
+ $effect(() => {
224
+ if (previewContainer && url) {
225
+ debug.log(
226
+ 'preview',
227
+ `📐 PreviewContainer: URL changed to ${url}, checking if recalculation needed`
228
+ );
229
+ // Only recalculate if this might affect dimensions (shouldn't usually, but just in case)
230
+ setTimeout(() => {
231
+ if (previewContainer) {
232
+ calculatePreviewDimensions();
233
+ debug.log(
234
+ 'preview',
235
+ `📐 PreviewContainer: URL-triggered calculation done - scale: ${previewDimensions.scale}`
236
+ );
237
+ }
238
+ }, 150);
239
+ }
240
+ });
241
+
242
+ // Track previous isNavigating state for navigation completion detection
243
+ let wasNavigating = $state(false);
244
+
245
+ // Detect navigation completion and notify Canvas for fast screencast refresh
246
+ $effect(() => {
247
+ // Navigation completed when isNavigating goes from true to false
248
+ if (wasNavigating && !isNavigating && sessionId && canvasAPI?.notifyNavigationComplete) {
249
+ debug.log('preview', `🧭 Navigation completed, notifying Canvas for fast refresh`);
250
+ canvasAPI.notifyNavigationComplete();
251
+ }
252
+ wasNavigating = isNavigating;
253
+ });
254
+
255
+ // Recalculate on window resize
256
+ $effect(() => {
257
+ if (typeof window !== 'undefined') {
258
+ const handleResize = () => {
259
+ if (previewContainer) {
260
+ calculatePreviewDimensions();
261
+ }
262
+ };
263
+ window.addEventListener('resize', handleResize);
264
+ return () => window.removeEventListener('resize', handleResize);
265
+ }
266
+ });
267
+
268
+ // Use ResizeObserver for precise container size tracking
269
+ $effect(() => {
270
+ if (previewContainer && typeof window !== 'undefined' && 'ResizeObserver' in window) {
271
+ let resizeTimeout: ReturnType<typeof setTimeout> | undefined;
272
+
273
+ const resizeObserver = new ResizeObserver((entries) => {
274
+ // Clear previous timeout to debounce rapid resize events
275
+ if (resizeTimeout) {
276
+ clearTimeout(resizeTimeout);
277
+ }
278
+
279
+ // Debounce resize events to prevent excessive canvas redraws
280
+ resizeTimeout = setTimeout(() => {
281
+ for (const entry of entries) {
282
+ const previousDimensions = { ...previewDimensions };
283
+ calculatePreviewDimensions();
284
+
285
+ // Only trigger canvas setup if dimensions actually changed
286
+ const dimensionsChanged =
287
+ JSON.stringify(previousDimensions) !== JSON.stringify(previewDimensions);
288
+
289
+ if (dimensionsChanged && canvasAPI && canvasAPI.setupCanvas) {
290
+ canvasAPI.setupCanvas();
291
+ }
292
+ }
293
+ }, 16); // ~60fps debouncing
294
+ });
295
+
296
+ resizeObserver.observe(previewContainer);
297
+
298
+ return () => {
299
+ if (resizeTimeout) {
300
+ clearTimeout(resizeTimeout);
301
+ }
302
+ resizeObserver.disconnect();
303
+ };
304
+ }
305
+ });
306
+ </script>
307
+
308
+ <!-- Preview Container with scaling -->
309
+ <div
310
+ bind:this={previewContainer}
311
+ class="flex-1 relative overflow-hidden p-0.5 flex items-center justify-center min-h-0"
312
+ >
313
+ {#if errorMessage && !isLaunchingBrowser && !isLoading}
314
+ <!-- Error State - Outside viewport container like empty state -->
315
+ <!-- Only show error if NOT in any loading state (loading states have priority) -->
316
+ <div
317
+ class="text-center text-slate-500 absolute"
318
+ in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
319
+ out:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
320
+ >
321
+ <Icon name="lucide:circle-alert" class="w-16 h-16 mx-auto mb-4 text-red-500 opacity-80" />
322
+ <p class="text-lg font-medium mb-2 text-slate-700 dark:text-slate-300">Failed to Load Page</p>
323
+ <p class="text-sm mb-4 text-slate-600 dark:text-slate-400 max-w-md">
324
+ {errorMessage}
325
+ </p>
326
+ <button
327
+ onclick={handleRetryClick}
328
+ class="px-5 py-2.5 bg-violet-600 hover:bg-violet-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 inline-flex items-center gap-2"
329
+ >
330
+ <Icon name="lucide:refresh-cw" class="w-4 h-4" />
331
+ <span>Try Again</span>
332
+ </button>
333
+ </div>
334
+ {:else if url}
335
+ <!-- Scaled container for proper viewport simulation -->
336
+ <div
337
+ class="relative rounded overflow-hidden flex-shrink-0 {isMcpControlled ? 'ring-2 ring-amber-500 ring-offset-2 ring-offset-slate-900' : ''}"
338
+ class:mcp-control-border={isMcpControlled}
339
+ style="width: {previewDimensions.width}; height: {previewDimensions.height};"
340
+ in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
341
+ out:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
342
+ >
343
+ <!-- Canvas container - always render when session exists so WebCodecs can start -->
344
+ {#if sessionInfo}
345
+ <div
346
+ class="w-full h-full flex items-center justify-center"
347
+ style="
348
+ width: {previewDimensions.frameWidth};
349
+ height: {previewDimensions.frameHeight};
350
+ transform: scale({previewDimensions.scale});
351
+ transform-origin: top left;
352
+ transition: none;
353
+ "
354
+ >
355
+ <Canvas
356
+ projectId={projectId}
357
+ bind:sessionId
358
+ bind:sessionInfo
359
+ bind:deviceSize
360
+ bind:rotation
361
+ bind:canvasAPI
362
+ bind:lastFrameData
363
+ bind:isConnected
364
+ bind:isStreamReady
365
+ bind:isNavigating
366
+ bind:isReconnecting
367
+ onInteraction={handleCanvasInteraction}
368
+ onCursorUpdate={handleCursorUpdate}
369
+ onFrameUpdate={handleFrameUpdate}
370
+ onRequestScreencastRefresh={handleScreencastRefresh}
371
+ />
372
+ </div>
373
+ {/if}
374
+
375
+ <!-- Solid Loading Overlay: Initial load states (launching, no session, waiting for first frame) -->
376
+ {#if showSolidOverlay}
377
+ <div
378
+ class="absolute inset-0 bg-white dark:bg-slate-800 flex items-center justify-center z-10"
379
+ >
380
+ <div class="flex flex-col items-center gap-2">
381
+ <Icon name="lucide:loader-circle" class="w-8 h-8 animate-spin text-violet-600" />
382
+ <div class="text-slate-500 text-center">
383
+ <div class="text-sm">Loading preview...</div>
384
+ </div>
385
+ </div>
386
+ </div>
387
+ {/if}
388
+
389
+ <!-- Navigation Overlay: Semi-transparent overlay during navigation/reconnect (shows last frame behind) -->
390
+ {#if showNavigationOverlay}
391
+ <div
392
+ class="absolute inset-0 bg-white/60 dark:bg-slate-800/60 backdrop-blur-[2px] flex items-center justify-center z-10"
393
+ >
394
+ <div class="flex flex-col items-center gap-2">
395
+ <Icon name="lucide:loader-circle" class="w-8 h-8 animate-spin text-violet-600" />
396
+ <div class="text-slate-600 dark:text-slate-300 text-center">
397
+ <div class="text-sm font-medium">Loading preview...</div>
398
+ </div>
399
+ </div>
400
+ </div>
401
+ {/if}
402
+
403
+ <!-- MCP Control Overlay - blocks user interaction -->
404
+ {#if isMcpControlled && isStreamReady}
405
+ <div
406
+ class="absolute inset-0 z-20 pointer-events-auto cursor-not-allowed"
407
+ role="presentation"
408
+ aria-hidden="true"
409
+ style="background: transparent;"
410
+ onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}
411
+ onmousedown={(e) => { e.preventDefault(); e.stopPropagation(); }}
412
+ onmousemove={(e) => { e.preventDefault(); e.stopPropagation(); }}
413
+ onkeydown={(e) => { e.preventDefault(); e.stopPropagation(); }}
414
+ >
415
+ <!-- MCP Control Badge at top -->
416
+ <!-- <div class="absolute top-2 left-1/2 -translate-x-1/2 bg-amber-500/90 text-black text-xs font-semibold px-3 py-1.5 rounded-full flex items-center gap-1.5 shadow-lg">
417
+ <Icon name="lucide:bot" class="w-3.5 h-3.5" />
418
+ <span>MCP Controlling</span>
419
+ </div> -->
420
+ </div>
421
+ {/if}
422
+ </div>
423
+ {:else}
424
+ <div
425
+ class="text-center text-slate-500 absolute"
426
+ in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
427
+ out:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
428
+ >
429
+ <Icon name="lucide:monitor" class="w-16 h-16 mx-auto mb-4 opacity-20" />
430
+ <p class="text-lg font-medium mb-2">Real Browser Preview</p>
431
+ <p class="text-sm">Enter a URL to preview your web application</p>
432
+ </div>
433
+ {/if}
434
+ </div>
435
+
436
+ <style>
437
+ /* MCP Control Border Animation */
438
+ .mcp-control-border {
439
+ animation: mcp-border-pulse 2s ease-in-out infinite;
440
+ }
441
+
442
+ @keyframes mcp-border-pulse {
443
+ 0%, 100% {
444
+ box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.5), 0 0 20px rgba(245, 158, 11, 0.3);
445
+ }
446
+ 50% {
447
+ box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.8), 0 0 30px rgba(245, 158, 11, 0.5);
448
+ }
449
+ }
450
+ </style>
@@ -0,0 +1,236 @@
1
+ <script lang="ts">
2
+ import type { BrowserContextMenuInfo, BrowserContextMenuItem } from '$frontend/lib/types/native-ui';
3
+ import { onMount } from 'svelte';
4
+
5
+ let {
6
+ menuInfo = $bindable<BrowserContextMenuInfo | null>(null),
7
+ onSelectItem = $bindable<(itemId: string) => void>(() => {}),
8
+ onClose = $bindable<() => void>(() => {})
9
+ } = $props();
10
+
11
+ let menuElement = $state<HTMLDivElement | undefined>(undefined);
12
+ let highlightedItemId = $state<string | null>(null);
13
+
14
+ // Position the context menu
15
+ function getMenuPosition() {
16
+ if (!menuInfo) return { top: '0px', left: '0px' };
17
+
18
+ let x = menuInfo.x;
19
+ let y = menuInfo.y;
20
+
21
+ // Adjust if menu would go off screen
22
+ if (menuElement) {
23
+ const menuRect = menuElement.getBoundingClientRect();
24
+ const windowWidth = window.innerWidth;
25
+ const windowHeight = window.innerHeight;
26
+
27
+ if (x + menuRect.width > windowWidth) {
28
+ x = windowWidth - menuRect.width - 10;
29
+ }
30
+
31
+ if (y + menuRect.height > windowHeight) {
32
+ y = windowHeight - menuRect.height - 10;
33
+ }
34
+ }
35
+
36
+ return {
37
+ top: `${y}px`,
38
+ left: `${x}px`
39
+ };
40
+ }
41
+
42
+ // Handle item click
43
+ function handleItemClick(item: BrowserContextMenuItem) {
44
+ if (!item.enabled || item.type === 'separator') return;
45
+ onSelectItem(item.id);
46
+ onClose();
47
+ }
48
+
49
+ // Handle keyboard navigation
50
+ function handleKeydown(event: KeyboardEvent) {
51
+ if (!menuInfo) return;
52
+
53
+ const enabledItems = menuInfo.items.filter(
54
+ (item: BrowserContextMenuItem) => item.enabled && item.type !== 'separator'
55
+ );
56
+
57
+ switch (event.key) {
58
+ case 'ArrowDown': {
59
+ event.preventDefault();
60
+ const currentIndex = enabledItems.findIndex((item: BrowserContextMenuItem) => item.id === highlightedItemId);
61
+ const nextIndex = (currentIndex + 1) % enabledItems.length;
62
+ highlightedItemId = enabledItems[nextIndex]?.id || null;
63
+ break;
64
+ }
65
+
66
+ case 'ArrowUp': {
67
+ event.preventDefault();
68
+ const currentIndex = enabledItems.findIndex((item: BrowserContextMenuItem) => item.id === highlightedItemId);
69
+ const prevIndex = currentIndex <= 0 ? enabledItems.length - 1 : currentIndex - 1;
70
+ highlightedItemId = enabledItems[prevIndex]?.id || null;
71
+ break;
72
+ }
73
+
74
+ case 'Enter': {
75
+ event.preventDefault();
76
+ const item = enabledItems.find((item: BrowserContextMenuItem) => item.id === highlightedItemId);
77
+ if (item) {
78
+ handleItemClick(item);
79
+ }
80
+ break;
81
+ }
82
+
83
+ case 'Escape':
84
+ event.preventDefault();
85
+ onClose();
86
+ break;
87
+ }
88
+ }
89
+
90
+ // Handle click outside to close
91
+ function handleClickOutside(event: MouseEvent) {
92
+ if (menuElement && !menuElement.contains(event.target as Node)) {
93
+ onClose();
94
+ }
95
+ }
96
+
97
+ // Initialize
98
+ $effect(() => {
99
+ if (menuInfo) {
100
+ // Focus menu for keyboard navigation
101
+ if (menuElement) {
102
+ menuElement.focus();
103
+ }
104
+
105
+ // Add click outside listener
106
+ setTimeout(() => {
107
+ document.addEventListener('mousedown', handleClickOutside);
108
+ }, 0);
109
+ }
110
+
111
+ return () => {
112
+ document.removeEventListener('mousedown', handleClickOutside);
113
+ };
114
+ });
115
+
116
+ const position = $derived(getMenuPosition());
117
+ </script>
118
+
119
+ {#if menuInfo}
120
+ <div
121
+ bind:this={menuElement}
122
+ class="browser-context-menu"
123
+ style="top: {position.top}; left: {position.left};"
124
+ tabindex="-1"
125
+ onkeydown={handleKeydown}
126
+ role="menu"
127
+ aria-label="Context menu"
128
+ >
129
+ {#each menuInfo.items as item (item.id)}
130
+ {#if item.type === 'separator'}
131
+ <div class="context-menu-separator" role="separator"></div>
132
+ {:else}
133
+ <div
134
+ class="context-menu-item"
135
+ class:disabled={!item.enabled}
136
+ class:highlighted={item.id === highlightedItemId}
137
+ role="menuitem"
138
+ tabindex={item.enabled ? 0 : -1}
139
+ aria-disabled={!item.enabled}
140
+ onclick={() => handleItemClick(item)}
141
+ onkeydown={(e) => {
142
+ if (e.key === 'Enter' || e.key === ' ') {
143
+ e.preventDefault();
144
+ handleItemClick(item);
145
+ }
146
+ }}
147
+ onmouseenter={() => {
148
+ if (item.enabled) highlightedItemId = item.id;
149
+ }}
150
+ >
151
+ <span class="menu-item-label">{item.label}</span>
152
+ </div>
153
+ {/if}
154
+ {/each}
155
+ </div>
156
+ {/if}
157
+
158
+ <style>
159
+ .browser-context-menu {
160
+ position: fixed;
161
+ z-index: 999999;
162
+ min-width: 200px;
163
+ background: white;
164
+ border: 1px solid #ccc;
165
+ border-radius: 4px;
166
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
167
+ padding: 4px 0;
168
+ outline: none;
169
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
170
+ }
171
+
172
+ .context-menu-item {
173
+ padding: 8px 24px 8px 16px;
174
+ cursor: pointer;
175
+ user-select: none;
176
+ font-size: 14px;
177
+ line-height: 1.5;
178
+ color: #333;
179
+ display: flex;
180
+ align-items: center;
181
+ transition: background-color 0.1s ease;
182
+ }
183
+
184
+ .context-menu-item:hover:not(.disabled) {
185
+ background: #f0f0f0;
186
+ }
187
+
188
+ .context-menu-item.highlighted:not(.disabled) {
189
+ background: #e6f7ff;
190
+ }
191
+
192
+ .context-menu-item.disabled {
193
+ color: #999;
194
+ cursor: not-allowed;
195
+ opacity: 0.5;
196
+ }
197
+
198
+ .context-menu-separator {
199
+ height: 1px;
200
+ background: #e0e0e0;
201
+ margin: 4px 0;
202
+ }
203
+
204
+ .menu-item-label {
205
+ flex: 1;
206
+ }
207
+
208
+ /* Dark mode support */
209
+ @media (prefers-color-scheme: dark) {
210
+ .browser-context-menu {
211
+ background: #2d2d2d;
212
+ border-color: #444;
213
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
214
+ }
215
+
216
+ .context-menu-item {
217
+ color: #e0e0e0;
218
+ }
219
+
220
+ .context-menu-item:hover:not(.disabled) {
221
+ background: #3a3a3a;
222
+ }
223
+
224
+ .context-menu-item.highlighted:not(.disabled) {
225
+ background: #003d66;
226
+ }
227
+
228
+ .context-menu-item.disabled {
229
+ color: #666;
230
+ }
231
+
232
+ .context-menu-separator {
233
+ background: #444;
234
+ }
235
+ }
236
+ </style>