@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.
- package/.env.example +6 -0
- package/.github/workflows/release.yml +60 -0
- package/.github/workflows/test.yml +40 -0
- package/CONTRIBUTING.md +499 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/backend/index.ts +156 -0
- package/backend/lib/chat/helpers.ts +42 -0
- package/backend/lib/chat/index.ts +2 -0
- package/backend/lib/chat/stream-manager.ts +1126 -0
- package/backend/lib/database/README.md +77 -0
- package/backend/lib/database/index.ts +119 -0
- package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
- package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
- package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
- package/backend/lib/database/migrations/index.ts +154 -0
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
- package/backend/lib/database/queries/engine-queries.ts +75 -0
- package/backend/lib/database/queries/index.ts +9 -0
- package/backend/lib/database/queries/message-queries.ts +472 -0
- package/backend/lib/database/queries/project-queries.ts +117 -0
- package/backend/lib/database/queries/session-queries.ts +271 -0
- package/backend/lib/database/queries/settings-queries.ts +34 -0
- package/backend/lib/database/queries/snapshot-queries.ts +326 -0
- package/backend/lib/database/queries/utils-queries.ts +59 -0
- package/backend/lib/database/seeders/index.ts +13 -0
- package/backend/lib/database/seeders/settings_seeder.ts +84 -0
- package/backend/lib/database/utils/connection.ts +174 -0
- package/backend/lib/database/utils/index.ts +4 -0
- package/backend/lib/database/utils/migration-runner.ts +118 -0
- package/backend/lib/database/utils/seeder-runner.ts +121 -0
- package/backend/lib/engine/adapters/claude/environment.ts +164 -0
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
- package/backend/lib/engine/adapters/claude/index.ts +1 -0
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
- package/backend/lib/engine/adapters/claude/stream.ts +177 -0
- package/backend/lib/engine/adapters/opencode/index.ts +2 -0
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
- package/backend/lib/engine/adapters/opencode/server.ts +104 -0
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
- package/backend/lib/engine/index.ts +196 -0
- package/backend/lib/engine/types.ts +58 -0
- package/backend/lib/files/file-operations.ts +478 -0
- package/backend/lib/files/file-reading.ts +308 -0
- package/backend/lib/files/file-watcher.ts +383 -0
- package/backend/lib/files/path-browsing.ts +382 -0
- package/backend/lib/git/git-executor.ts +88 -0
- package/backend/lib/git/git-parser.ts +411 -0
- package/backend/lib/git/git-service.ts +505 -0
- package/backend/lib/mcp/README.md +1144 -0
- package/backend/lib/mcp/config.ts +316 -0
- package/backend/lib/mcp/index.ts +35 -0
- package/backend/lib/mcp/project-context.ts +236 -0
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
- package/backend/lib/mcp/servers/helper.ts +143 -0
- package/backend/lib/mcp/servers/index.ts +45 -0
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
- package/backend/lib/mcp/servers/weather/index.ts +31 -0
- package/backend/lib/mcp/stdio-server.ts +103 -0
- package/backend/lib/mcp/types.ts +65 -0
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
- package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
- package/backend/lib/preview/browser/browser-pool.ts +357 -0
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
- package/backend/lib/preview/browser/types.ts +359 -0
- package/backend/lib/preview/index.ts +24 -0
- package/backend/lib/project/index.ts +2 -0
- package/backend/lib/project/status-manager.ts +182 -0
- package/backend/lib/shared/index.ts +2 -0
- package/backend/lib/shared/port-utils.ts +25 -0
- package/backend/lib/shared/process-manager.ts +281 -0
- package/backend/lib/snapshot/blob-store.ts +227 -0
- package/backend/lib/snapshot/gitignore.ts +307 -0
- package/backend/lib/snapshot/helpers.ts +397 -0
- package/backend/lib/snapshot/snapshot-service.ts +483 -0
- package/backend/lib/terminal/helpers.ts +14 -0
- package/backend/lib/terminal/index.ts +8 -0
- package/backend/lib/terminal/pty-manager.ts +4 -0
- package/backend/lib/terminal/pty-session-manager.ts +387 -0
- package/backend/lib/terminal/shell-utils.ts +313 -0
- package/backend/lib/terminal/stream-manager.ts +293 -0
- package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
- package/backend/lib/user/helpers.ts +87 -0
- package/backend/lib/utils/ws.ts +944 -0
- package/backend/lib/vite-dev.ts +353 -0
- package/backend/middleware/cors.ts +15 -0
- package/backend/middleware/error-handler.ts +49 -0
- package/backend/middleware/logger.ts +9 -0
- package/backend/types/api.ts +24 -0
- package/backend/ws/README.md +1505 -0
- package/backend/ws/chat/background.ts +198 -0
- package/backend/ws/chat/index.ts +21 -0
- package/backend/ws/chat/stream.ts +707 -0
- package/backend/ws/engine/claude/accounts.ts +401 -0
- package/backend/ws/engine/claude/index.ts +13 -0
- package/backend/ws/engine/claude/status.ts +43 -0
- package/backend/ws/engine/index.ts +14 -0
- package/backend/ws/engine/opencode/index.ts +11 -0
- package/backend/ws/engine/opencode/status.ts +30 -0
- package/backend/ws/engine/utils.ts +36 -0
- package/backend/ws/files/index.ts +30 -0
- package/backend/ws/files/read.ts +189 -0
- package/backend/ws/files/search.ts +453 -0
- package/backend/ws/files/watch.ts +124 -0
- package/backend/ws/files/write.ts +143 -0
- package/backend/ws/git/branch.ts +106 -0
- package/backend/ws/git/commit.ts +39 -0
- package/backend/ws/git/conflict.ts +68 -0
- package/backend/ws/git/diff.ts +69 -0
- package/backend/ws/git/index.ts +24 -0
- package/backend/ws/git/log.ts +41 -0
- package/backend/ws/git/remote.ts +214 -0
- package/backend/ws/git/staging.ts +84 -0
- package/backend/ws/git/status.ts +90 -0
- package/backend/ws/index.ts +69 -0
- package/backend/ws/mcp/index.ts +61 -0
- package/backend/ws/messages/crud.ts +74 -0
- package/backend/ws/messages/index.ts +14 -0
- package/backend/ws/preview/browser/cleanup.ts +129 -0
- package/backend/ws/preview/browser/console.ts +114 -0
- package/backend/ws/preview/browser/interact.ts +513 -0
- package/backend/ws/preview/browser/mcp.ts +129 -0
- package/backend/ws/preview/browser/native-ui.ts +235 -0
- package/backend/ws/preview/browser/stats.ts +55 -0
- package/backend/ws/preview/browser/tab-info.ts +126 -0
- package/backend/ws/preview/browser/tab.ts +166 -0
- package/backend/ws/preview/browser/webcodecs.ts +293 -0
- package/backend/ws/preview/index.ts +146 -0
- package/backend/ws/projects/crud.ts +113 -0
- package/backend/ws/projects/index.ts +25 -0
- package/backend/ws/projects/presence.ts +46 -0
- package/backend/ws/projects/status.ts +116 -0
- package/backend/ws/sessions/crud.ts +327 -0
- package/backend/ws/sessions/index.ts +33 -0
- package/backend/ws/settings/crud.ts +112 -0
- package/backend/ws/settings/index.ts +14 -0
- package/backend/ws/snapshot/index.ts +17 -0
- package/backend/ws/snapshot/restore.ts +173 -0
- package/backend/ws/snapshot/timeline.ts +141 -0
- package/backend/ws/system/index.ts +14 -0
- package/backend/ws/system/operations.ts +49 -0
- package/backend/ws/terminal/index.ts +40 -0
- package/backend/ws/terminal/persistence.ts +153 -0
- package/backend/ws/terminal/session.ts +382 -0
- package/backend/ws/terminal/stream.ts +79 -0
- package/backend/ws/tunnel/index.ts +14 -0
- package/backend/ws/tunnel/operations.ts +91 -0
- package/backend/ws/types.ts +20 -0
- package/backend/ws/user/crud.ts +156 -0
- package/backend/ws/user/index.ts +14 -0
- package/bin/clopen.ts +307 -0
- package/bun.lock +1352 -0
- package/frontend/App.svelte +34 -0
- package/frontend/app.css +313 -0
- package/frontend/lib/app-environment.ts +10 -0
- package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
- package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
- package/frontend/lib/components/chat/formatters/index.ts +3 -0
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
- package/frontend/lib/components/chat/shared/index.ts +2 -0
- package/frontend/lib/components/chat/shared/utils.ts +116 -0
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
- package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
- package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
- package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
- package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
- package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
- package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
- package/frontend/lib/components/chat/tools/components/index.ts +7 -0
- package/frontend/lib/components/chat/tools/index.ts +26 -0
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
- package/frontend/lib/components/common/Alert.svelte +139 -0
- package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
- package/frontend/lib/components/common/Button.svelte +71 -0
- package/frontend/lib/components/common/Card.svelte +102 -0
- package/frontend/lib/components/common/Checkbox.svelte +48 -0
- package/frontend/lib/components/common/Dialog.svelte +249 -0
- package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
- package/frontend/lib/components/common/Icon.svelte +58 -0
- package/frontend/lib/components/common/Input.svelte +72 -0
- package/frontend/lib/components/common/Lightbox.svelte +233 -0
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
- package/frontend/lib/components/common/Modal.svelte +177 -0
- package/frontend/lib/components/common/ModalProvider.svelte +28 -0
- package/frontend/lib/components/common/ModelSelector.svelte +110 -0
- package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
- package/frontend/lib/components/common/NotificationToast.svelte +113 -0
- package/frontend/lib/components/common/PageTemplate.svelte +76 -0
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
- package/frontend/lib/components/common/Select.svelte +98 -0
- package/frontend/lib/components/common/Textarea.svelte +80 -0
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
- package/frontend/lib/components/common/lucide-icons.ts +1642 -0
- package/frontend/lib/components/common/material-icons.ts +1082 -0
- package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
- package/frontend/lib/components/common/xterm/index.ts +16 -0
- package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
- package/frontend/lib/components/common/xterm/types.ts +31 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
- package/frontend/lib/components/files/FileNode.svelte +384 -0
- package/frontend/lib/components/files/FileTree.svelte +681 -0
- package/frontend/lib/components/files/FileViewer.svelte +728 -0
- package/frontend/lib/components/files/SearchResults.svelte +303 -0
- package/frontend/lib/components/git/BranchManager.svelte +458 -0
- package/frontend/lib/components/git/ChangesSection.svelte +107 -0
- package/frontend/lib/components/git/CommitForm.svelte +76 -0
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
- package/frontend/lib/components/git/DiffViewer.svelte +364 -0
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
- package/frontend/lib/components/git/GitButton.svelte +33 -0
- package/frontend/lib/components/git/GitLog.svelte +361 -0
- package/frontend/lib/components/git/GitModal.svelte +80 -0
- package/frontend/lib/components/history/HistoryModal.svelte +563 -0
- package/frontend/lib/components/history/HistoryView.svelte +615 -0
- package/frontend/lib/components/index.ts +34 -0
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
- package/frontend/lib/components/preview/index.ts +2 -0
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
- package/frontend/lib/components/settings/SettingsView.svelte +36 -0
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
- package/frontend/lib/components/terminal/Terminal.svelte +368 -0
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
- package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
- package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
- package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
- package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
- package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
- package/frontend/lib/constants/preview.ts +45 -0
- package/frontend/lib/services/chat/chat.service.ts +704 -0
- package/frontend/lib/services/chat/index.ts +7 -0
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
- package/frontend/lib/services/notification/index.ts +8 -0
- package/frontend/lib/services/notification/push.service.ts +144 -0
- package/frontend/lib/services/notification/sound.service.ts +127 -0
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
- package/frontend/lib/services/preview/index.ts +23 -0
- package/frontend/lib/services/project/index.ts +8 -0
- package/frontend/lib/services/project/status.service.ts +159 -0
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
- package/frontend/lib/services/terminal/background/index.ts +130 -0
- package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
- package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
- package/frontend/lib/services/terminal/index.ts +14 -0
- package/frontend/lib/services/terminal/persistence.service.ts +260 -0
- package/frontend/lib/services/terminal/project.service.ts +953 -0
- package/frontend/lib/services/terminal/session.service.ts +364 -0
- package/frontend/lib/services/terminal/terminal.service.ts +369 -0
- package/frontend/lib/stores/core/app.svelte.ts +117 -0
- package/frontend/lib/stores/core/files.svelte.ts +73 -0
- package/frontend/lib/stores/core/presence.svelte.ts +48 -0
- package/frontend/lib/stores/core/projects.svelte.ts +317 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
- package/frontend/lib/stores/features/models.svelte.ts +89 -0
- package/frontend/lib/stores/features/settings.svelte.ts +87 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
- package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
- package/frontend/lib/stores/features/user.svelte.ts +96 -0
- package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
- package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
- package/frontend/lib/types/native-ui.ts +73 -0
- package/frontend/lib/utils/chat/date-separator.ts +39 -0
- package/frontend/lib/utils/chat/message-grouper.ts +219 -0
- package/frontend/lib/utils/chat/message-processor.ts +135 -0
- package/frontend/lib/utils/chat/tool-handler.ts +161 -0
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
- package/frontend/lib/utils/click-outside.ts +20 -0
- package/frontend/lib/utils/context-manager.ts +257 -0
- package/frontend/lib/utils/file-icon-mappings.ts +769 -0
- package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
- package/frontend/lib/utils/git-status.ts +68 -0
- package/frontend/lib/utils/platform.ts +113 -0
- package/frontend/lib/utils/port-check.ts +65 -0
- package/frontend/lib/utils/terminalFormatter.ts +207 -0
- package/frontend/lib/utils/theme.ts +6 -0
- package/frontend/lib/utils/tree-visualizer.ts +320 -0
- package/frontend/lib/utils/ws.ts +44 -0
- package/frontend/main.ts +13 -0
- package/index.html +70 -0
- package/package.json +111 -0
- package/scripts/generate-icons.ts +87 -0
- package/scripts/pre-publish-check.sh +142 -0
- package/scripts/setup-hooks.sh +134 -0
- package/scripts/validate-branch-name.sh +47 -0
- package/scripts/validate-commit-msg.sh +42 -0
- package/shared/constants/engines.ts +134 -0
- package/shared/types/database/connection.ts +16 -0
- package/shared/types/database/index.ts +6 -0
- package/shared/types/database/schema.ts +141 -0
- package/shared/types/engine/index.ts +45 -0
- package/shared/types/filesystem/index.ts +22 -0
- package/shared/types/git.ts +171 -0
- package/shared/types/messaging/index.ts +239 -0
- package/shared/types/messaging/tool.ts +526 -0
- package/shared/types/network/api.ts +18 -0
- package/shared/types/network/index.ts +5 -0
- package/shared/types/stores/app.ts +23 -0
- package/shared/types/stores/dialog.ts +21 -0
- package/shared/types/stores/index.ts +3 -0
- package/shared/types/stores/settings.ts +15 -0
- package/shared/types/terminal/index.ts +44 -0
- package/shared/types/ui/components.ts +61 -0
- package/shared/types/ui/icons.ts +23 -0
- package/shared/types/ui/index.ts +22 -0
- package/shared/types/ui/notifications.ts +14 -0
- package/shared/types/ui/theme.ts +12 -0
- package/shared/types/websocket/index.ts +43 -0
- package/shared/types/window.d.ts +13 -0
- package/shared/utils/anonymous-user.ts +168 -0
- package/shared/utils/async.ts +10 -0
- package/shared/utils/diff-calculator.ts +184 -0
- package/shared/utils/file-type-detection.ts +166 -0
- package/shared/utils/logger.ts +158 -0
- package/shared/utils/message-formatter.ts +79 -0
- package/shared/utils/path.ts +47 -0
- package/shared/utils/ws-client.ts +768 -0
- package/shared/utils/ws-server.ts +660 -0
- package/static/audio/notification.ogg +0 -0
- package/static/favicon.svg +8 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
- package/static/fonts/dm-sans.css +96 -0
- package/svelte.config.js +20 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Coordinator
|
|
3
|
+
* Coordinates all browser preview services and state management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { debug } from '$shared/utils/logger';
|
|
7
|
+
import type { DeviceSize, Rotation } from '$frontend/lib/constants/preview';
|
|
8
|
+
import { createTabManager, type TabManager, type PreviewTab, getTabTitle } from './tab-manager.svelte';
|
|
9
|
+
import { createStreamMessageHandler, type StreamMessageHandler } from './stream-handler.svelte';
|
|
10
|
+
import { createNativeUIHandler, type NativeUIHandler } from './native-ui-handlers.svelte';
|
|
11
|
+
import { createMcpHandler, type McpHandler } from './mcp-handlers.svelte';
|
|
12
|
+
import {
|
|
13
|
+
launchBrowser as launchBrowserOp,
|
|
14
|
+
navigateBrowser as navigateBrowserOp,
|
|
15
|
+
destroyBrowser,
|
|
16
|
+
destroyBrowserTab,
|
|
17
|
+
getExistingTabs,
|
|
18
|
+
switchToBackendTab,
|
|
19
|
+
type ExistingTabInfo
|
|
20
|
+
} from './tab-operations.svelte';
|
|
21
|
+
import { browserCleanup } from './cleanup.svelte';
|
|
22
|
+
import { sendInteraction, updateViewport, setInteractionProjectId } from './interactions.svelte';
|
|
23
|
+
import type { BrowserSelectInfo, BrowserContextMenuInfo } from '$frontend/lib/types/native-ui';
|
|
24
|
+
|
|
25
|
+
export interface BrowserCoordinatorConfig {
|
|
26
|
+
// Project ID getter (REQUIRED for project isolation)
|
|
27
|
+
projectId: () => string;
|
|
28
|
+
|
|
29
|
+
// Callbacks from parent component
|
|
30
|
+
onUrlChange?: (url: string) => void;
|
|
31
|
+
onUrlInputChange?: (urlInput: string) => void;
|
|
32
|
+
onSessionChange?: (sessionId: string | null) => void;
|
|
33
|
+
onLoadingChange?: (isLoading: boolean) => void;
|
|
34
|
+
onErrorChange?: (errorMessage: string | null) => void;
|
|
35
|
+
|
|
36
|
+
// Session recovery callback - called when sessions are recovered after page refresh
|
|
37
|
+
onSessionsRecovered?: (tabCount: number) => void;
|
|
38
|
+
|
|
39
|
+
// UI state callbacks
|
|
40
|
+
onSelectOpen?: (selectInfo: BrowserSelectInfo) => void;
|
|
41
|
+
onContextMenuOpen?: (menuInfo: BrowserContextMenuInfo) => void;
|
|
42
|
+
onVirtualCursorUpdate?: (x: number, y: number, clicking?: boolean) => void;
|
|
43
|
+
onVirtualCursorHide?: () => void;
|
|
44
|
+
onMcpCursorUpdate?: (x: number, y: number, clicking?: boolean) => void;
|
|
45
|
+
onMcpCursorHide?: () => void;
|
|
46
|
+
|
|
47
|
+
// Coordinate transformation
|
|
48
|
+
transformBrowserToDisplayCoordinates?: (x: number, y: number) => { x: number, y: number } | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create browser coordinator
|
|
53
|
+
*/
|
|
54
|
+
export function createBrowserCoordinator(config: BrowserCoordinatorConfig) {
|
|
55
|
+
const {
|
|
56
|
+
projectId: getProjectId,
|
|
57
|
+
onUrlChange,
|
|
58
|
+
onUrlInputChange,
|
|
59
|
+
onSessionChange,
|
|
60
|
+
onLoadingChange,
|
|
61
|
+
onErrorChange,
|
|
62
|
+
onSessionsRecovered,
|
|
63
|
+
onSelectOpen,
|
|
64
|
+
onContextMenuOpen,
|
|
65
|
+
onVirtualCursorUpdate,
|
|
66
|
+
onVirtualCursorHide,
|
|
67
|
+
onMcpCursorUpdate,
|
|
68
|
+
onMcpCursorHide,
|
|
69
|
+
transformBrowserToDisplayCoordinates
|
|
70
|
+
} = config;
|
|
71
|
+
|
|
72
|
+
// Restore lock to prevent race conditions
|
|
73
|
+
let isRestoring = $state(false);
|
|
74
|
+
let eventListenersReady = $state(false);
|
|
75
|
+
|
|
76
|
+
// Create managers
|
|
77
|
+
const tabManager = createTabManager();
|
|
78
|
+
|
|
79
|
+
// Create stream handler
|
|
80
|
+
const streamHandler = createStreamMessageHandler({
|
|
81
|
+
tabManager,
|
|
82
|
+
onNavigationUpdate: (tabId, url) => {
|
|
83
|
+
// Only notify parent if this is the active tab
|
|
84
|
+
if (tabId === tabManager.activeTabId) {
|
|
85
|
+
if (onUrlChange) onUrlChange(url);
|
|
86
|
+
if (onUrlInputChange) onUrlInputChange(url);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onCursorUpdate: (x, y, clicking) => {
|
|
90
|
+
if (onVirtualCursorUpdate) {
|
|
91
|
+
onVirtualCursorUpdate(x, y, clicking);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onTestCompleted: () => {
|
|
95
|
+
if (onVirtualCursorHide) {
|
|
96
|
+
onVirtualCursorHide();
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
transformBrowserToDisplayCoordinates
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Create native UI handler
|
|
103
|
+
const nativeUIHandler = createNativeUIHandler({
|
|
104
|
+
tabManager,
|
|
105
|
+
transformBrowserToDisplayCoordinates,
|
|
106
|
+
onSelectOpen,
|
|
107
|
+
onContextMenuOpen,
|
|
108
|
+
onOpenUrlNewTab: (url) => {
|
|
109
|
+
createNewTab(url);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Create MCP handler
|
|
114
|
+
const mcpHandler = createMcpHandler({
|
|
115
|
+
tabManager,
|
|
116
|
+
transformBrowserToDisplayCoordinates,
|
|
117
|
+
onCursorUpdate: (x, y, clicking) => {
|
|
118
|
+
if (onMcpCursorUpdate) {
|
|
119
|
+
onMcpCursorUpdate(x, y, clicking);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
onCursorHide: () => {
|
|
123
|
+
if (onMcpCursorHide) {
|
|
124
|
+
onMcpCursorHide();
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
onLaunchRequest: (url, deviceSize, rotation, sessionId) => {
|
|
128
|
+
handleMcpLaunchRequest(url, deviceSize, rotation, sessionId);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Browser session management
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Launch browser for a tab
|
|
136
|
+
*/
|
|
137
|
+
async function launchBrowserForTab(tabId: string, tabUrl: string, mcpSessionId?: string) {
|
|
138
|
+
debug.log('preview', `๐ launchBrowserForTab called - tabId: ${tabId}, url: ${tabUrl}${mcpSessionId ? `, mcpSessionId: ${mcpSessionId}` : ''}`);
|
|
139
|
+
|
|
140
|
+
const tab = tabManager.getTab(tabId);
|
|
141
|
+
if (!tab || !tabUrl) {
|
|
142
|
+
debug.error('preview', `โ Tab not found or no URL: ${tabId}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
tabManager.updateTab(tabId, { isLaunchingBrowser: true, errorMessage: null });
|
|
148
|
+
|
|
149
|
+
// Get current projectId
|
|
150
|
+
const projectId = getProjectId();
|
|
151
|
+
const result = await launchBrowserOp(tabUrl, tab.deviceSize, tab.rotation, projectId, mcpSessionId);
|
|
152
|
+
|
|
153
|
+
if (result.success && result.sessionId && result.sessionInfo) {
|
|
154
|
+
debug.log('preview', `โ
Browser launched successfully - sessionId: ${result.sessionId}`);
|
|
155
|
+
|
|
156
|
+
// Register session for cleanup tracking
|
|
157
|
+
browserCleanup.registerSession(result.sessionId);
|
|
158
|
+
|
|
159
|
+
// Use the actual URL from backend (which may be different due to redirects)
|
|
160
|
+
const actualUrl = result.sessionInfo.url || tabUrl;
|
|
161
|
+
|
|
162
|
+
tabManager.updateTab(tabId, {
|
|
163
|
+
sessionId: result.sessionId,
|
|
164
|
+
sessionInfo: result.sessionInfo,
|
|
165
|
+
url: actualUrl,
|
|
166
|
+
lastFrameData: null,
|
|
167
|
+
errorMessage: null
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Notify parent if active tab
|
|
171
|
+
if (tabId === tabManager.activeTabId) {
|
|
172
|
+
if (onSessionChange) onSessionChange(result.sessionId);
|
|
173
|
+
if (onUrlChange) onUrlChange(actualUrl);
|
|
174
|
+
if (onUrlInputChange) onUrlInputChange(actualUrl);
|
|
175
|
+
if (onErrorChange) onErrorChange(null);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
} else {
|
|
179
|
+
const errorMsg = result.error || 'Unknown error';
|
|
180
|
+
debug.error('preview', `โ Browser launch failed:`, errorMsg);
|
|
181
|
+
tabManager.updateTab(tabId, { errorMessage: errorMsg });
|
|
182
|
+
|
|
183
|
+
if (tabId === tabManager.activeTabId && onErrorChange) {
|
|
184
|
+
onErrorChange(errorMsg);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
189
|
+
debug.error('preview', `๐ฅ Exception in launchBrowserForTab:`, error);
|
|
190
|
+
tabManager.updateTab(tabId, { errorMessage: `Exception: ${errorMsg}` });
|
|
191
|
+
|
|
192
|
+
if (tabId === tabManager.activeTabId && onErrorChange) {
|
|
193
|
+
onErrorChange(`Exception: ${errorMsg}`);
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
tabManager.updateTab(tabId, { isLaunchingBrowser: false });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Navigate browser for a tab
|
|
202
|
+
*/
|
|
203
|
+
async function navigateBrowserForTab(tabId: string, newUrl: string) {
|
|
204
|
+
const tab = tabManager.getTab(tabId);
|
|
205
|
+
if (!tab || !tab.sessionId) return;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Set isNavigating since we have an existing session
|
|
209
|
+
tabManager.updateTab(tabId, { isLoading: true, isNavigating: true, errorMessage: null });
|
|
210
|
+
|
|
211
|
+
// Get current projectId
|
|
212
|
+
const projectId = getProjectId();
|
|
213
|
+
const result = await navigateBrowserOp(newUrl, projectId);
|
|
214
|
+
|
|
215
|
+
if (result.success) {
|
|
216
|
+
const finalUrl = result.finalUrl || newUrl;
|
|
217
|
+
tabManager.updateTab(tabId, {
|
|
218
|
+
url: finalUrl,
|
|
219
|
+
errorMessage: null
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Notify parent if active tab
|
|
223
|
+
if (tabId === tabManager.activeTabId) {
|
|
224
|
+
if (onUrlChange) onUrlChange(finalUrl);
|
|
225
|
+
if (onUrlInputChange) onUrlInputChange(finalUrl);
|
|
226
|
+
if (onErrorChange) onErrorChange(null);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const errorMsg = result.error || 'Navigation failed';
|
|
230
|
+
debug.error('preview', `โ Browser navigation failed:`, errorMsg);
|
|
231
|
+
tabManager.updateTab(tabId, { errorMessage: errorMsg, isNavigating: false });
|
|
232
|
+
|
|
233
|
+
if (tabId === tabManager.activeTabId && onErrorChange) {
|
|
234
|
+
onErrorChange(errorMsg);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
239
|
+
debug.error('preview', `๐ฅ Exception in navigateBrowserForTab:`, error);
|
|
240
|
+
tabManager.updateTab(tabId, { errorMessage: `Exception: ${errorMsg}`, isNavigating: false });
|
|
241
|
+
|
|
242
|
+
if (tabId === tabManager.activeTabId && onErrorChange) {
|
|
243
|
+
onErrorChange(`Exception: ${errorMsg}`);
|
|
244
|
+
}
|
|
245
|
+
} finally {
|
|
246
|
+
tabManager.updateTab(tabId, { isLoading: false });
|
|
247
|
+
// Note: isNavigating will be cleared when new frame arrives (in stream-handler)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create new tab
|
|
253
|
+
*/
|
|
254
|
+
function createNewTab(tabUrl: string = ''): string {
|
|
255
|
+
// Prevent creating tabs during restore to avoid race conditions
|
|
256
|
+
if (isRestoring) {
|
|
257
|
+
debug.warn('preview', 'โ ๏ธ Cannot create tab while restoring sessions');
|
|
258
|
+
return '';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const tabId = tabManager.createTab(tabUrl);
|
|
262
|
+
|
|
263
|
+
// Update parent state
|
|
264
|
+
if (onUrlChange) onUrlChange(tabUrl);
|
|
265
|
+
if (onUrlInputChange) onUrlInputChange(tabUrl);
|
|
266
|
+
|
|
267
|
+
// Launch browser if URL is provided
|
|
268
|
+
if (tabUrl) {
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
launchBrowserForTab(tabId, tabUrl);
|
|
271
|
+
}, 0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return tabId;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Switch to tab
|
|
279
|
+
*/
|
|
280
|
+
async function switchToTab(tabId: string) {
|
|
281
|
+
const tab = tabManager.switchTab(tabId);
|
|
282
|
+
if (!tab) return;
|
|
283
|
+
|
|
284
|
+
// If this tab has a backend session, switch backend to it
|
|
285
|
+
// This ensures streaming comes from the correct backend tab
|
|
286
|
+
if (tab.sessionId) {
|
|
287
|
+
const projectId = getProjectId();
|
|
288
|
+
await switchToBackendTab(tab.sessionId, projectId);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Notify parent of state changes
|
|
292
|
+
if (onUrlChange) onUrlChange(tab.url);
|
|
293
|
+
if (onUrlInputChange) onUrlInputChange(tab.url);
|
|
294
|
+
if (onSessionChange) onSessionChange(tab.sessionId);
|
|
295
|
+
if (onLoadingChange) onLoadingChange(tab.isLoading);
|
|
296
|
+
if (onErrorChange) onErrorChange(tab.errorMessage);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Close tab - explicitly destroys backend session
|
|
301
|
+
*/
|
|
302
|
+
function closeTab(tabId: string) {
|
|
303
|
+
const tab = tabManager.getTab(tabId);
|
|
304
|
+
if (!tab) return;
|
|
305
|
+
|
|
306
|
+
// Cleanup session - explicitly destroy the backend tab
|
|
307
|
+
if (tab.sessionId) {
|
|
308
|
+
browserCleanup.unregisterSession(tab.sessionId);
|
|
309
|
+
const projectId = getProjectId();
|
|
310
|
+
destroyBrowserTab(tab.sessionId, projectId); // Use specific tab ID, not active tab
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { newActiveTab } = tabManager.closeTab(tabId);
|
|
314
|
+
|
|
315
|
+
// Check if there are any tabs left
|
|
316
|
+
const tabsLeft = tabManager.getAllTabs().length;
|
|
317
|
+
|
|
318
|
+
// If no tabs left, create a new empty tab
|
|
319
|
+
if (tabsLeft === 0) {
|
|
320
|
+
debug.log('preview', '๐ No tabs left after close, creating new empty tab');
|
|
321
|
+
createNewTab('');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Update parent state if we switched to a new active tab
|
|
326
|
+
if (newActiveTab) {
|
|
327
|
+
if (onUrlChange) onUrlChange(newActiveTab.url);
|
|
328
|
+
if (onUrlInputChange) onUrlInputChange(newActiveTab.url);
|
|
329
|
+
if (onSessionChange) onSessionChange(newActiveTab.sessionId);
|
|
330
|
+
if (onLoadingChange) onLoadingChange(newActiveTab.isLoading);
|
|
331
|
+
if (onErrorChange) onErrorChange(newActiveTab.errorMessage);
|
|
332
|
+
}
|
|
333
|
+
// If newActiveTab is null but tabs still exist, it means we closed a non-active tab
|
|
334
|
+
// In this case, don't update parent state as the active tab hasn't changed
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Handle device size change
|
|
339
|
+
* When changing device, reset to that device's natural default rotation
|
|
340
|
+
*/
|
|
341
|
+
function changeDeviceSize(newSize: DeviceSize, scale?: number) {
|
|
342
|
+
if (!tabManager.activeTabId) return;
|
|
343
|
+
|
|
344
|
+
const tab = tabManager.activeTab;
|
|
345
|
+
if (!tab || tab.deviceSize === newSize) return;
|
|
346
|
+
|
|
347
|
+
// Reset to device's natural default rotation:
|
|
348
|
+
// - Desktop/laptop: landscape (natural default)
|
|
349
|
+
// - Tablet/mobile: portrait (natural default)
|
|
350
|
+
const finalRotation: Rotation = (newSize === 'desktop' || newSize === 'laptop') ? 'landscape' : 'portrait';
|
|
351
|
+
|
|
352
|
+
// Update tab state
|
|
353
|
+
tabManager.updateTab(tabManager.activeTabId, {
|
|
354
|
+
deviceSize: newSize,
|
|
355
|
+
rotation: finalRotation
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Hot-swap viewport if session is active
|
|
359
|
+
if (tab.sessionId && scale) {
|
|
360
|
+
setTimeout(() => {
|
|
361
|
+
updateViewport(newSize, finalRotation, scale);
|
|
362
|
+
}, 100);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Toggle rotation
|
|
368
|
+
*/
|
|
369
|
+
function toggleRotation(scale?: number) {
|
|
370
|
+
if (!tabManager.activeTabId) return;
|
|
371
|
+
|
|
372
|
+
const tab = tabManager.activeTab;
|
|
373
|
+
if (!tab) return;
|
|
374
|
+
|
|
375
|
+
const newRotation = tab.rotation === 'portrait' ? 'landscape' : 'portrait';
|
|
376
|
+
|
|
377
|
+
// Update tab state
|
|
378
|
+
tabManager.updateTab(tabManager.activeTabId, {
|
|
379
|
+
rotation: newRotation
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Hot-swap viewport if session is active
|
|
383
|
+
if (tab.sessionId && scale) {
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
updateViewport(tab.deviceSize, newRotation, scale);
|
|
386
|
+
}, 100);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Handle MCP launch request
|
|
392
|
+
*/
|
|
393
|
+
function handleMcpLaunchRequest(url: string, deviceSize: string, rotation: string, sessionId?: string) {
|
|
394
|
+
if (tabManager.activeTabId) {
|
|
395
|
+
const tab = tabManager.activeTab;
|
|
396
|
+
if (tab && !tab.sessionId) {
|
|
397
|
+
const finalDeviceSize = (deviceSize || 'laptop') as DeviceSize;
|
|
398
|
+
// Use device-appropriate default rotation if not specified
|
|
399
|
+
const finalRotation = rotation
|
|
400
|
+
? (rotation as Rotation)
|
|
401
|
+
: ((finalDeviceSize === 'desktop' || finalDeviceSize === 'laptop') ? 'landscape' : 'portrait');
|
|
402
|
+
|
|
403
|
+
// Use existing empty tab
|
|
404
|
+
tabManager.updateTab(tabManager.activeTabId, {
|
|
405
|
+
url: url,
|
|
406
|
+
deviceSize: finalDeviceSize,
|
|
407
|
+
rotation: finalRotation
|
|
408
|
+
});
|
|
409
|
+
launchBrowserForTab(tabManager.activeTabId, url, sessionId);
|
|
410
|
+
} else {
|
|
411
|
+
// Create new tab
|
|
412
|
+
createNewTabWithMcpSession(url, sessionId);
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
// Create new tab
|
|
416
|
+
createNewTabWithMcpSession(url, sessionId);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Create new tab with MCP session
|
|
422
|
+
*/
|
|
423
|
+
function createNewTabWithMcpSession(tabUrl: string, mcpSessionId?: string): string {
|
|
424
|
+
const tabId = tabManager.createTab(tabUrl);
|
|
425
|
+
|
|
426
|
+
// Update parent state
|
|
427
|
+
if (onUrlChange) onUrlChange(tabUrl);
|
|
428
|
+
if (onUrlInputChange) onUrlInputChange(tabUrl);
|
|
429
|
+
|
|
430
|
+
// Launch browser with MCP sessionId
|
|
431
|
+
if (tabUrl) {
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
launchBrowserForTab(tabId, tabUrl, mcpSessionId);
|
|
434
|
+
}, 0);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return tabId;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Initialize event listeners
|
|
442
|
+
* NOTE: Session recovery is now handled by the project change watcher in BrowserPreview.svelte
|
|
443
|
+
* to ensure projectId is available before attempting recovery
|
|
444
|
+
*/
|
|
445
|
+
function initialize() {
|
|
446
|
+
browserCleanup.initialize();
|
|
447
|
+
nativeUIHandler.setupEventListeners();
|
|
448
|
+
mcpHandler.setupEventListeners();
|
|
449
|
+
|
|
450
|
+
// CRITICAL: Setup event listeners FIRST before any recovery
|
|
451
|
+
// This ensures we don't miss events from backend
|
|
452
|
+
setupTabEventListeners();
|
|
453
|
+
setupNavigationListeners();
|
|
454
|
+
|
|
455
|
+
// Mark event listeners as ready
|
|
456
|
+
eventListenersReady = true;
|
|
457
|
+
|
|
458
|
+
// Note: setInteractionProjectId and recoverExistingSessions are called by switchProject()
|
|
459
|
+
// which is triggered by the project watcher effect after projectId becomes available
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Recover existing browser sessions from backend (after page refresh)
|
|
464
|
+
*/
|
|
465
|
+
async function recoverExistingSessions() {
|
|
466
|
+
// Wait for event listeners to be ready before recovering
|
|
467
|
+
// This prevents missing events from backend during recovery
|
|
468
|
+
if (!eventListenersReady) {
|
|
469
|
+
debug.warn('preview', 'โ ๏ธ Event listeners not ready, waiting...');
|
|
470
|
+
await new Promise(resolve => {
|
|
471
|
+
const checkInterval = setInterval(() => {
|
|
472
|
+
if (eventListenersReady) {
|
|
473
|
+
clearInterval(checkInterval);
|
|
474
|
+
resolve(true);
|
|
475
|
+
}
|
|
476
|
+
}, 50);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Set restore lock to prevent race conditions
|
|
481
|
+
isRestoring = true;
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
const projectId = getProjectId();
|
|
485
|
+
const existingTabs = await getExistingTabs(projectId);
|
|
486
|
+
|
|
487
|
+
if (!existingTabs || existingTabs.count === 0) {
|
|
488
|
+
debug.log('preview', '๐ญ No existing sessions to recover');
|
|
489
|
+
isRestoring = false;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Track which frontend tab corresponds to the active tab
|
|
494
|
+
let activeTabFrontendId: string | null = null;
|
|
495
|
+
let totalRestored = 0;
|
|
496
|
+
|
|
497
|
+
// Restore backend tabs
|
|
498
|
+
debug.log('preview', `๐ Recovering ${existingTabs.count} existing browser sessions...`);
|
|
499
|
+
|
|
500
|
+
for (const backendTab of existingTabs.tabs) {
|
|
501
|
+
const frontendId = restoreTabFromBackend(backendTab, backendTab.isActive);
|
|
502
|
+
|
|
503
|
+
// Register session for cleanup tracking
|
|
504
|
+
browserCleanup.registerSession(backendTab.tabId);
|
|
505
|
+
|
|
506
|
+
// Remember which frontend tab should be active
|
|
507
|
+
if (backendTab.isActive && frontendId) {
|
|
508
|
+
activeTabFrontendId = frontendId;
|
|
509
|
+
}
|
|
510
|
+
totalRestored++;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Now switch to the active tab (after all tabs are restored)
|
|
514
|
+
if (activeTabFrontendId) {
|
|
515
|
+
debug.log('preview', `๐ฏ Switching to active tab: ${activeTabFrontendId}`);
|
|
516
|
+
tabManager.switchTab(activeTabFrontendId);
|
|
517
|
+
|
|
518
|
+
// If active tab has a backend session, notify backend
|
|
519
|
+
const activeTab = tabManager.getTab(activeTabFrontendId);
|
|
520
|
+
if (activeTab?.sessionId) {
|
|
521
|
+
await switchToBackendTab(activeTab.sessionId, projectId);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Update parent state with active tab info
|
|
525
|
+
if (activeTab) {
|
|
526
|
+
if (onUrlChange) onUrlChange(activeTab.url);
|
|
527
|
+
if (onUrlInputChange) onUrlInputChange(activeTab.url);
|
|
528
|
+
if (onSessionChange) onSessionChange(activeTab.sessionId);
|
|
529
|
+
if (onLoadingChange) onLoadingChange(false);
|
|
530
|
+
if (onErrorChange) onErrorChange(null);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
debug.log('preview', `โ
Session recovery complete - restored ${totalRestored} tabs`);
|
|
535
|
+
|
|
536
|
+
// Notify parent that sessions were recovered
|
|
537
|
+
if (onSessionsRecovered) {
|
|
538
|
+
onSessionsRecovered(totalRestored);
|
|
539
|
+
}
|
|
540
|
+
} catch (error) {
|
|
541
|
+
debug.error('preview', 'โ Error recovering sessions:', error);
|
|
542
|
+
} finally {
|
|
543
|
+
// Always release restore lock
|
|
544
|
+
isRestoring = false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Restore a single tab from backend session
|
|
550
|
+
* Returns the frontend tab ID for this restored tab
|
|
551
|
+
*/
|
|
552
|
+
function restoreTabFromBackend(backendTab: ExistingTabInfo, isActive: boolean): string {
|
|
553
|
+
debug.log('preview', `๐ง Restoring tab: ${backendTab.tabId} (${backendTab.url})`);
|
|
554
|
+
|
|
555
|
+
// Create frontend tab matching backend tab
|
|
556
|
+
const frontendTabId = tabManager.createTab(backendTab.url);
|
|
557
|
+
|
|
558
|
+
// Update tab with backend session info
|
|
559
|
+
tabManager.updateTab(frontendTabId, {
|
|
560
|
+
sessionId: backendTab.tabId,
|
|
561
|
+
sessionInfo: {
|
|
562
|
+
quality: backendTab.quality,
|
|
563
|
+
url: backendTab.url,
|
|
564
|
+
deviceSize: backendTab.deviceSize as DeviceSize,
|
|
565
|
+
rotation: backendTab.rotation as Rotation
|
|
566
|
+
},
|
|
567
|
+
url: backendTab.url,
|
|
568
|
+
title: backendTab.title || getTabTitle(backendTab.url),
|
|
569
|
+
deviceSize: backendTab.deviceSize as DeviceSize,
|
|
570
|
+
rotation: backendTab.rotation as Rotation,
|
|
571
|
+
isConnected: true,
|
|
572
|
+
isStreamReady: false, // Will be set true when stream reconnects
|
|
573
|
+
isLoading: false,
|
|
574
|
+
isLaunchingBrowser: false,
|
|
575
|
+
isNavigating: false,
|
|
576
|
+
errorMessage: null
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
debug.log('preview', `โ
Tab restored: ${frontendTabId} โ ${backendTab.tabId}`);
|
|
580
|
+
|
|
581
|
+
return frontendTabId;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Setup tab event listeners
|
|
586
|
+
* CRITICAL: This must be synchronous to ensure listeners are ready before recovery
|
|
587
|
+
*/
|
|
588
|
+
function setupTabEventListeners() {
|
|
589
|
+
debug.log('preview', '๐ง Setting up tab event listeners...');
|
|
590
|
+
|
|
591
|
+
// Import ws synchronously (should already be loaded in app context)
|
|
592
|
+
// This is critical to ensure listeners are ready before any events arrive
|
|
593
|
+
import('$frontend/lib/utils/ws').then(({ default: ws }) => {
|
|
594
|
+
debug.log('preview', 'โ
WebSocket module loaded, registering tab event listeners');
|
|
595
|
+
|
|
596
|
+
// Listen for tab opened events (from MCP or backend)
|
|
597
|
+
ws.on('preview:browser-tab-opened', (data: any) => {
|
|
598
|
+
debug.log('preview', `๐ฅ Frontend received preview:browser-tab-opened:`, data);
|
|
599
|
+
|
|
600
|
+
// Skip if we're currently restoring to prevent conflicts
|
|
601
|
+
if (isRestoring) {
|
|
602
|
+
debug.log('preview', `โญ๏ธ Skipping tab-opened event during restore: ${data.tabId}`);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Check if this tab already exists in frontend (by backend sessionId)
|
|
607
|
+
const existingTab = tabManager.tabs.find(t => t.sessionId === data.tabId);
|
|
608
|
+
if (existingTab) {
|
|
609
|
+
debug.log('preview', `โ Backend tab ${data.tabId} already linked to frontend tab ${existingTab.id}, skipping`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Check if there's a tab currently launching without sessionId (race condition fix)
|
|
614
|
+
const launchingTab = tabManager.tabs.find(t => t.isLaunchingBrowser && !t.sessionId);
|
|
615
|
+
if (launchingTab) {
|
|
616
|
+
debug.log('preview', `๐ Found launching tab ${launchingTab.id}, linking to backend tab ${data.tabId}`);
|
|
617
|
+
|
|
618
|
+
// Update the launching tab with backend session info
|
|
619
|
+
tabManager.updateTab(launchingTab.id, {
|
|
620
|
+
sessionId: data.tabId,
|
|
621
|
+
sessionInfo: {
|
|
622
|
+
quality: 'good',
|
|
623
|
+
url: data.url,
|
|
624
|
+
deviceSize: data.deviceSize,
|
|
625
|
+
rotation: data.rotation
|
|
626
|
+
},
|
|
627
|
+
url: data.url,
|
|
628
|
+
title: data.title,
|
|
629
|
+
deviceSize: data.deviceSize || 'laptop',
|
|
630
|
+
rotation: data.rotation || 'landscape',
|
|
631
|
+
isConnected: true,
|
|
632
|
+
isStreamReady: false,
|
|
633
|
+
isLoading: false,
|
|
634
|
+
errorMessage: null
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Note: Session registration is handled by launchBrowserForTab
|
|
638
|
+
debug.log('preview', `โ
Launching tab updated: ${launchingTab.id} โ backend tab: ${data.tabId}`);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Create frontend tab to match backend tab (only if no launching tab found)
|
|
643
|
+
const frontendTabId = tabManager.createTab(data.url);
|
|
644
|
+
|
|
645
|
+
// Update tab with backend session info including device settings
|
|
646
|
+
tabManager.updateTab(frontendTabId, {
|
|
647
|
+
sessionId: data.tabId, // Backend tab ID is the session ID
|
|
648
|
+
sessionInfo: {
|
|
649
|
+
quality: 'good',
|
|
650
|
+
url: data.url,
|
|
651
|
+
deviceSize: data.deviceSize,
|
|
652
|
+
rotation: data.rotation
|
|
653
|
+
},
|
|
654
|
+
url: data.url,
|
|
655
|
+
title: data.title,
|
|
656
|
+
deviceSize: data.deviceSize || 'laptop',
|
|
657
|
+
rotation: data.rotation || 'landscape',
|
|
658
|
+
isConnected: true,
|
|
659
|
+
isStreamReady: false,
|
|
660
|
+
isLoading: false,
|
|
661
|
+
errorMessage: null
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// Register session for cleanup tracking
|
|
665
|
+
browserCleanup.registerSession(data.tabId);
|
|
666
|
+
|
|
667
|
+
// Switch to this tab if it's active
|
|
668
|
+
if (data.isActive) {
|
|
669
|
+
tabManager.switchTab(frontendTabId);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
debug.log('preview', `โ
Frontend tab created: ${frontendTabId} โ backend tab: ${data.tabId}`);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// Listen for tab closed events
|
|
676
|
+
ws.on('preview:browser-tab-closed', (data) => {
|
|
677
|
+
debug.log('preview', `๐ฅ Frontend received preview:browser-tab-closed:`, data);
|
|
678
|
+
|
|
679
|
+
// Find frontend tab by backend session ID
|
|
680
|
+
const tab = tabManager.tabs.find(t => t.sessionId === data.tabId);
|
|
681
|
+
if (tab) {
|
|
682
|
+
tabManager.closeTab(tab.id);
|
|
683
|
+
browserCleanup.unregisterSession(data.tabId);
|
|
684
|
+
debug.log('preview', `โ
Frontend tab closed: ${tab.id}`);
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Listen for tab switched events
|
|
689
|
+
ws.on('preview:browser-tab-switched', (data) => {
|
|
690
|
+
debug.log('preview', `๐ฅ Frontend received preview:browser-tab-switched:`, data);
|
|
691
|
+
|
|
692
|
+
// Find frontend tab by backend session ID
|
|
693
|
+
const tab = tabManager.tabs.find(t => t.sessionId === data.newTabId);
|
|
694
|
+
if (tab) {
|
|
695
|
+
tabManager.switchTab(tab.id);
|
|
696
|
+
debug.log('preview', `โ
Frontend switched to tab: ${tab.id}`);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Listen for viewport changed events
|
|
701
|
+
ws.on('preview:browser-viewport-changed' as any, (data: any) => {
|
|
702
|
+
debug.log('preview', `๐ฅ Frontend received preview:browser-viewport-changed:`, data);
|
|
703
|
+
|
|
704
|
+
// Find frontend tab by backend session ID
|
|
705
|
+
const tab = tabManager.tabs.find(t => t.sessionId === data.tabId);
|
|
706
|
+
if (tab) {
|
|
707
|
+
// Update tab with new device settings
|
|
708
|
+
tabManager.updateTab(tab.id, {
|
|
709
|
+
deviceSize: data.deviceSize,
|
|
710
|
+
rotation: data.rotation
|
|
711
|
+
});
|
|
712
|
+
debug.log('preview', `โ
Frontend viewport updated: ${tab.id} โ ${data.deviceSize} (${data.rotation})`);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
debug.log('preview', `๐ All tab event listeners registered and active`);
|
|
717
|
+
}).catch(error => {
|
|
718
|
+
debug.error('preview', 'โ Failed to setup tab event listeners:', error);
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Setup navigation event listeners
|
|
724
|
+
*/
|
|
725
|
+
function setupNavigationListeners() {
|
|
726
|
+
// Import ws at runtime to avoid circular dependencies
|
|
727
|
+
import('$frontend/lib/utils/ws').then(({ default: ws }) => {
|
|
728
|
+
// Listen for navigation loading events
|
|
729
|
+
ws.on('preview:browser-navigation-loading', (data: { sessionId: string; type: string; url: string; timestamp: number }) => {
|
|
730
|
+
debug.log('preview', `๐ Navigation loading event received: ${data.sessionId} โ ${data.url}`);
|
|
731
|
+
|
|
732
|
+
// Find tab by sessionId (backend sends backend tab ID as sessionId)
|
|
733
|
+
const tab = tabManager.tabs.find(t => t.sessionId === data.sessionId);
|
|
734
|
+
if (tab) {
|
|
735
|
+
streamHandler.handleStreamMessage({
|
|
736
|
+
type: 'navigation-loading',
|
|
737
|
+
data: { url: data.url }
|
|
738
|
+
}, tab.id);
|
|
739
|
+
} else {
|
|
740
|
+
debug.warn('preview', `Tab not found for sessionId: ${data.sessionId}`);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// Listen for navigation completed events
|
|
745
|
+
ws.on('preview:browser-navigation', (data: { sessionId: string; type: string; url: string; timestamp: number }) => {
|
|
746
|
+
debug.log('preview', `โ
Navigation completed event received: ${data.sessionId} โ ${data.url}`);
|
|
747
|
+
|
|
748
|
+
// Find tab by sessionId (backend sends backend tab ID as sessionId)
|
|
749
|
+
const tab = tabManager.tabs.find(t => t.sessionId === data.sessionId);
|
|
750
|
+
if (tab) {
|
|
751
|
+
streamHandler.handleStreamMessage({
|
|
752
|
+
type: 'navigation',
|
|
753
|
+
data: { url: data.url }
|
|
754
|
+
}, tab.id);
|
|
755
|
+
} else {
|
|
756
|
+
debug.warn('preview', `Tab not found for sessionId: ${data.sessionId}`);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Cleanup frontend state only (called on component destroy)
|
|
764
|
+
*
|
|
765
|
+
* NOTE: This does NOT destroy backend sessions.
|
|
766
|
+
* Backend sessions are preserved to allow reconnection after page refresh.
|
|
767
|
+
* To explicitly close tabs, use closeTab() which calls destroyBrowser().
|
|
768
|
+
*/
|
|
769
|
+
async function cleanup() {
|
|
770
|
+
// Just clear frontend tracking - don't destroy backend sessions
|
|
771
|
+
// Backend sessions will be recovered on next page load
|
|
772
|
+
browserCleanup.clearAll();
|
|
773
|
+
debug.log('preview', '๐งน Frontend cleanup complete (backend sessions preserved)');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Switch to a different project
|
|
778
|
+
* Clears current tabs and loads sessions from the new project
|
|
779
|
+
*/
|
|
780
|
+
async function switchProject() {
|
|
781
|
+
const newProjectId = getProjectId();
|
|
782
|
+
debug.log('preview', `๐ Switching to project: ${newProjectId}`);
|
|
783
|
+
|
|
784
|
+
// Set restore lock during project switch
|
|
785
|
+
isRestoring = true;
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
// Update projectId for interactions
|
|
789
|
+
setInteractionProjectId(newProjectId);
|
|
790
|
+
|
|
791
|
+
// Clear all local tabs
|
|
792
|
+
tabManager.clearAllTabs();
|
|
793
|
+
|
|
794
|
+
// Clear frontend tracking
|
|
795
|
+
browserCleanup.clearAll();
|
|
796
|
+
|
|
797
|
+
// Recover sessions from new project
|
|
798
|
+
// Note: recoverExistingSessions will handle its own lock
|
|
799
|
+
isRestoring = false;
|
|
800
|
+
await recoverExistingSessions();
|
|
801
|
+
} catch (error) {
|
|
802
|
+
debug.error('preview', 'โ Error switching project:', error);
|
|
803
|
+
isRestoring = false;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return {
|
|
808
|
+
// Managers
|
|
809
|
+
tabManager,
|
|
810
|
+
streamHandler,
|
|
811
|
+
nativeUIHandler,
|
|
812
|
+
mcpHandler,
|
|
813
|
+
|
|
814
|
+
// Tab operations
|
|
815
|
+
createNewTab,
|
|
816
|
+
switchToTab,
|
|
817
|
+
closeTab,
|
|
818
|
+
|
|
819
|
+
// Browser operations
|
|
820
|
+
launchBrowserForTab,
|
|
821
|
+
navigateBrowserForTab,
|
|
822
|
+
|
|
823
|
+
// Device control
|
|
824
|
+
changeDeviceSize,
|
|
825
|
+
toggleRotation,
|
|
826
|
+
|
|
827
|
+
// Lifecycle
|
|
828
|
+
initialize,
|
|
829
|
+
cleanup,
|
|
830
|
+
switchProject,
|
|
831
|
+
|
|
832
|
+
// Utilities
|
|
833
|
+
sendInteraction
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export type BrowserCoordinator = ReturnType<typeof createBrowserCoordinator>;
|