crca 1.4.0__py3-none-any.whl

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 (501) hide show
  1. .github/ISSUE_TEMPLATE/bug_report.md +65 -0
  2. .github/ISSUE_TEMPLATE/feature_request.md +41 -0
  3. .github/PULL_REQUEST_TEMPLATE.md +20 -0
  4. .github/workflows/publish-manual.yml +61 -0
  5. .github/workflows/publish.yml +64 -0
  6. .gitignore +214 -0
  7. CRCA.py +4156 -0
  8. LICENSE +201 -0
  9. MANIFEST.in +43 -0
  10. PKG-INFO +5035 -0
  11. README.md +4959 -0
  12. __init__.py +17 -0
  13. branches/CRCA-Q.py +2728 -0
  14. branches/crca_cg/corposwarm.py +9065 -0
  15. branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
  16. branches/crca_cg/package.json +5 -0
  17. branches/crca_cg/test_bolt_integration.py +446 -0
  18. branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
  19. branches/crca_cg/test_new_features.py +163 -0
  20. branches/crca_sd/__init__.py +149 -0
  21. branches/crca_sd/crca_sd_core.py +770 -0
  22. branches/crca_sd/crca_sd_governance.py +1325 -0
  23. branches/crca_sd/crca_sd_mpc.py +1130 -0
  24. branches/crca_sd/crca_sd_realtime.py +1844 -0
  25. branches/crca_sd/crca_sd_tui.py +1133 -0
  26. crca-1.4.0.dist-info/METADATA +5035 -0
  27. crca-1.4.0.dist-info/RECORD +501 -0
  28. crca-1.4.0.dist-info/WHEEL +4 -0
  29. crca-1.4.0.dist-info/licenses/LICENSE +201 -0
  30. docs/CRCA-Q.md +2333 -0
  31. examples/config.yaml.example +25 -0
  32. examples/crca_sd_example.py +513 -0
  33. examples/data_broker_example.py +294 -0
  34. examples/logistics_corporation.py +861 -0
  35. examples/palantir_example.py +299 -0
  36. examples/policy_bench.py +934 -0
  37. examples/pridnestrovia-sd.py +705 -0
  38. examples/pridnestrovia_realtime.py +1902 -0
  39. prompts/__init__.py +10 -0
  40. prompts/default_crca.py +101 -0
  41. pyproject.toml +151 -0
  42. requirements.txt +76 -0
  43. schemas/__init__.py +43 -0
  44. schemas/mcpSchemas.py +51 -0
  45. schemas/policy.py +458 -0
  46. templates/__init__.py +38 -0
  47. templates/base_specialized_agent.py +195 -0
  48. templates/drift_detection.py +325 -0
  49. templates/examples/causal_agent_template.py +309 -0
  50. templates/examples/drag_drop_example.py +213 -0
  51. templates/examples/logistics_agent_template.py +207 -0
  52. templates/examples/trading_agent_template.py +206 -0
  53. templates/feature_mixins.py +253 -0
  54. templates/graph_management.py +442 -0
  55. templates/llm_integration.py +194 -0
  56. templates/module_registry.py +276 -0
  57. templates/mpc_planner.py +280 -0
  58. templates/policy_loop.py +1168 -0
  59. templates/prediction_framework.py +448 -0
  60. templates/statistical_methods.py +778 -0
  61. tests/sanity.yml +31 -0
  62. tests/sanity_check +406 -0
  63. tests/test_core.py +47 -0
  64. tests/test_crca_excel.py +166 -0
  65. tests/test_crca_sd.py +780 -0
  66. tests/test_data_broker.py +424 -0
  67. tests/test_palantir.py +349 -0
  68. tools/__init__.py +38 -0
  69. tools/actuators.py +437 -0
  70. tools/bolt.diy/Dockerfile +103 -0
  71. tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
  72. tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
  73. tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
  74. tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
  75. tools/bolt.diy/app/components/@settings/index.ts +12 -0
  76. tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
  77. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
  78. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
  79. tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
  80. tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
  81. tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
  82. tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
  83. tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
  84. tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
  85. tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
  86. tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
  87. tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
  88. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
  89. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
  90. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
  91. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
  92. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
  93. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
  94. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
  95. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
  96. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
  97. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
  98. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
  99. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
  100. tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
  101. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
  102. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
  103. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
  104. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
  105. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
  106. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
  107. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
  108. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
  109. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
  110. tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
  111. tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
  112. tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
  113. tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
  114. tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
  115. tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
  116. tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
  117. tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
  118. tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
  119. tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
  120. tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
  121. tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
  122. tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
  123. tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
  124. tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
  125. tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
  126. tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
  127. tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
  128. tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
  129. tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
  130. tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
  131. tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
  132. tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
  133. tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
  134. tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
  135. tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
  136. tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
  137. tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
  138. tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
  139. tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
  140. tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
  141. tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
  142. tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
  143. tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
  144. tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
  145. tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
  146. tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
  147. tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
  148. tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
  149. tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
  150. tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
  151. tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
  152. tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
  153. tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
  154. tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
  155. tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
  156. tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
  157. tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
  158. tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
  159. tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
  160. tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
  161. tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
  162. tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
  163. tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
  164. tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
  165. tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
  166. tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
  167. tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
  168. tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
  169. tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
  170. tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
  171. tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
  172. tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
  173. tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
  174. tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
  175. tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
  176. tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
  177. tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
  178. tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
  179. tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
  180. tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
  181. tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
  182. tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
  183. tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
  184. tools/bolt.diy/app/components/header/Header.tsx +42 -0
  185. tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
  186. tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
  187. tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
  188. tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
  189. tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
  190. tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
  191. tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
  192. tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
  193. tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
  194. tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
  195. tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
  196. tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
  197. tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
  198. tools/bolt.diy/app/components/txt +1 -0
  199. tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
  200. tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
  201. tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
  202. tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
  203. tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
  204. tools/bolt.diy/app/components/ui/Button.tsx +46 -0
  205. tools/bolt.diy/app/components/ui/Card.tsx +55 -0
  206. tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
  207. tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
  208. tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
  209. tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
  210. tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
  211. tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
  212. tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
  213. tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
  214. tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
  215. tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
  216. tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
  217. tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
  218. tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
  219. tools/bolt.diy/app/components/ui/Input.tsx +22 -0
  220. tools/bolt.diy/app/components/ui/Label.tsx +20 -0
  221. tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
  222. tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
  223. tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
  224. tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
  225. tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
  226. tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
  227. tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
  228. tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
  229. tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
  230. tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
  231. tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
  232. tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
  233. tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
  234. tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
  235. tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
  236. tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
  237. tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
  238. tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
  239. tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
  240. tools/bolt.diy/app/components/ui/index.ts +38 -0
  241. tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
  242. tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
  243. tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
  244. tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
  245. tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
  246. tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
  247. tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
  248. tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
  249. tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
  250. tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
  251. tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
  252. tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
  253. tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
  254. tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
  255. tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
  256. tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
  257. tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
  258. tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
  259. tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
  260. tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
  261. tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
  262. tools/bolt.diy/app/entry.client.tsx +7 -0
  263. tools/bolt.diy/app/entry.server.tsx +80 -0
  264. tools/bolt.diy/app/root.tsx +156 -0
  265. tools/bolt.diy/app/routes/_index.tsx +175 -0
  266. tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
  267. tools/bolt.diy/app/routes/api.chat.ts +463 -0
  268. tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
  269. tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
  270. tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
  271. tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
  272. tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
  273. tools/bolt.diy/app/routes/api.git-info.ts +69 -0
  274. tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
  275. tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
  276. tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
  277. tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
  278. tools/bolt.diy/app/routes/api.github-template.ts +242 -0
  279. tools/bolt.diy/app/routes/api.github-user.ts +287 -0
  280. tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
  281. tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
  282. tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
  283. tools/bolt.diy/app/routes/api.health.ts +8 -0
  284. tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
  285. tools/bolt.diy/app/routes/api.mandate.ts +351 -0
  286. tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
  287. tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
  288. tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
  289. tools/bolt.diy/app/routes/api.models.ts +90 -0
  290. tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
  291. tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
  292. tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
  293. tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
  294. tools/bolt.diy/app/routes/api.supabase.ts +56 -0
  295. tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
  296. tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
  297. tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
  298. tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
  299. tools/bolt.diy/app/routes/api.update.ts +21 -0
  300. tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
  301. tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
  302. tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
  303. tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
  304. tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
  305. tools/bolt.diy/app/routes/git.tsx +25 -0
  306. tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
  307. tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
  308. tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
  309. tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
  310. tools/bolt.diy/app/styles/animations.scss +49 -0
  311. tools/bolt.diy/app/styles/components/code.scss +9 -0
  312. tools/bolt.diy/app/styles/components/editor.scss +135 -0
  313. tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
  314. tools/bolt.diy/app/styles/components/terminal.scss +3 -0
  315. tools/bolt.diy/app/styles/components/toast.scss +23 -0
  316. tools/bolt.diy/app/styles/diff-view.css +72 -0
  317. tools/bolt.diy/app/styles/index.scss +73 -0
  318. tools/bolt.diy/app/styles/variables.scss +255 -0
  319. tools/bolt.diy/app/styles/z-index.scss +37 -0
  320. tools/bolt.diy/app/types/GitHub.ts +182 -0
  321. tools/bolt.diy/app/types/GitLab.ts +103 -0
  322. tools/bolt.diy/app/types/actions.ts +85 -0
  323. tools/bolt.diy/app/types/artifact.ts +5 -0
  324. tools/bolt.diy/app/types/context.ts +26 -0
  325. tools/bolt.diy/app/types/design-scheme.ts +93 -0
  326. tools/bolt.diy/app/types/global.d.ts +13 -0
  327. tools/bolt.diy/app/types/mandate.ts +333 -0
  328. tools/bolt.diy/app/types/model.ts +25 -0
  329. tools/bolt.diy/app/types/netlify.ts +94 -0
  330. tools/bolt.diy/app/types/supabase.ts +54 -0
  331. tools/bolt.diy/app/types/template.ts +8 -0
  332. tools/bolt.diy/app/types/terminal.ts +9 -0
  333. tools/bolt.diy/app/types/theme.ts +1 -0
  334. tools/bolt.diy/app/types/vercel.ts +67 -0
  335. tools/bolt.diy/app/utils/buffer.ts +29 -0
  336. tools/bolt.diy/app/utils/classNames.ts +65 -0
  337. tools/bolt.diy/app/utils/constants.ts +147 -0
  338. tools/bolt.diy/app/utils/debounce.ts +13 -0
  339. tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
  340. tools/bolt.diy/app/utils/diff.spec.ts +11 -0
  341. tools/bolt.diy/app/utils/diff.ts +117 -0
  342. tools/bolt.diy/app/utils/easings.ts +3 -0
  343. tools/bolt.diy/app/utils/fileLocks.ts +96 -0
  344. tools/bolt.diy/app/utils/fileUtils.ts +121 -0
  345. tools/bolt.diy/app/utils/folderImport.ts +73 -0
  346. tools/bolt.diy/app/utils/formatSize.ts +12 -0
  347. tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
  348. tools/bolt.diy/app/utils/githubStats.ts +9 -0
  349. tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
  350. tools/bolt.diy/app/utils/logger.ts +162 -0
  351. tools/bolt.diy/app/utils/markdown.ts +155 -0
  352. tools/bolt.diy/app/utils/mobile.ts +4 -0
  353. tools/bolt.diy/app/utils/os.ts +4 -0
  354. tools/bolt.diy/app/utils/path.ts +19 -0
  355. tools/bolt.diy/app/utils/projectCommands.ts +197 -0
  356. tools/bolt.diy/app/utils/promises.ts +19 -0
  357. tools/bolt.diy/app/utils/react.ts +6 -0
  358. tools/bolt.diy/app/utils/sampler.ts +49 -0
  359. tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
  360. tools/bolt.diy/app/utils/shell.ts +384 -0
  361. tools/bolt.diy/app/utils/stacktrace.ts +27 -0
  362. tools/bolt.diy/app/utils/stripIndent.ts +23 -0
  363. tools/bolt.diy/app/utils/terminal.ts +11 -0
  364. tools/bolt.diy/app/utils/unreachable.ts +3 -0
  365. tools/bolt.diy/app/vite-env.d.ts +2 -0
  366. tools/bolt.diy/assets/entitlements.mac.plist +25 -0
  367. tools/bolt.diy/assets/icons/icon.icns +0 -0
  368. tools/bolt.diy/assets/icons/icon.ico +0 -0
  369. tools/bolt.diy/assets/icons/icon.png +0 -0
  370. tools/bolt.diy/bindings.js +78 -0
  371. tools/bolt.diy/bindings.sh +33 -0
  372. tools/bolt.diy/docker-compose.yaml +145 -0
  373. tools/bolt.diy/electron/main/index.ts +201 -0
  374. tools/bolt.diy/electron/main/tsconfig.json +30 -0
  375. tools/bolt.diy/electron/main/ui/menu.ts +29 -0
  376. tools/bolt.diy/electron/main/ui/window.ts +54 -0
  377. tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
  378. tools/bolt.diy/electron/main/utils/constants.ts +4 -0
  379. tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
  380. tools/bolt.diy/electron/main/utils/reload.ts +35 -0
  381. tools/bolt.diy/electron/main/utils/serve.ts +71 -0
  382. tools/bolt.diy/electron/main/utils/store.ts +3 -0
  383. tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
  384. tools/bolt.diy/electron/main/vite.config.ts +44 -0
  385. tools/bolt.diy/electron/preload/index.ts +22 -0
  386. tools/bolt.diy/electron/preload/tsconfig.json +7 -0
  387. tools/bolt.diy/electron/preload/vite.config.ts +31 -0
  388. tools/bolt.diy/electron-builder.yml +64 -0
  389. tools/bolt.diy/electron-update.yml +4 -0
  390. tools/bolt.diy/eslint.config.mjs +57 -0
  391. tools/bolt.diy/functions/[[path]].ts +12 -0
  392. tools/bolt.diy/icons/angular.svg +1 -0
  393. tools/bolt.diy/icons/astro.svg +8 -0
  394. tools/bolt.diy/icons/chat.svg +1 -0
  395. tools/bolt.diy/icons/expo-brand.svg +1 -0
  396. tools/bolt.diy/icons/expo.svg +4 -0
  397. tools/bolt.diy/icons/logo-text.svg +1 -0
  398. tools/bolt.diy/icons/logo.svg +4 -0
  399. tools/bolt.diy/icons/mcp.svg +1 -0
  400. tools/bolt.diy/icons/nativescript.svg +1 -0
  401. tools/bolt.diy/icons/netlify.svg +10 -0
  402. tools/bolt.diy/icons/nextjs.svg +1 -0
  403. tools/bolt.diy/icons/nuxt.svg +1 -0
  404. tools/bolt.diy/icons/qwik.svg +1 -0
  405. tools/bolt.diy/icons/react.svg +1 -0
  406. tools/bolt.diy/icons/remix.svg +24 -0
  407. tools/bolt.diy/icons/remotion.svg +1 -0
  408. tools/bolt.diy/icons/shadcn.svg +21 -0
  409. tools/bolt.diy/icons/slidev.svg +60 -0
  410. tools/bolt.diy/icons/solidjs.svg +1 -0
  411. tools/bolt.diy/icons/stars.svg +1 -0
  412. tools/bolt.diy/icons/svelte.svg +1 -0
  413. tools/bolt.diy/icons/typescript.svg +1 -0
  414. tools/bolt.diy/icons/vite.svg +1 -0
  415. tools/bolt.diy/icons/vue.svg +1 -0
  416. tools/bolt.diy/load-context.ts +9 -0
  417. tools/bolt.diy/notarize.cjs +31 -0
  418. tools/bolt.diy/package.json +218 -0
  419. tools/bolt.diy/playwright.config.preview.ts +35 -0
  420. tools/bolt.diy/pre-start.cjs +26 -0
  421. tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
  422. tools/bolt.diy/public/apple-touch-icon.png +0 -0
  423. tools/bolt.diy/public/favicon.ico +0 -0
  424. tools/bolt.diy/public/favicon.svg +4 -0
  425. tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
  426. tools/bolt.diy/public/icons/Anthropic.svg +4 -0
  427. tools/bolt.diy/public/icons/Cohere.svg +4 -0
  428. tools/bolt.diy/public/icons/Deepseek.svg +5 -0
  429. tools/bolt.diy/public/icons/Default.svg +4 -0
  430. tools/bolt.diy/public/icons/Google.svg +4 -0
  431. tools/bolt.diy/public/icons/Groq.svg +4 -0
  432. tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
  433. tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
  434. tools/bolt.diy/public/icons/LMStudio.svg +5 -0
  435. tools/bolt.diy/public/icons/Mistral.svg +4 -0
  436. tools/bolt.diy/public/icons/Ollama.svg +4 -0
  437. tools/bolt.diy/public/icons/OpenAI.svg +4 -0
  438. tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
  439. tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
  440. tools/bolt.diy/public/icons/Perplexity.svg +4 -0
  441. tools/bolt.diy/public/icons/Together.svg +4 -0
  442. tools/bolt.diy/public/icons/xAI.svg +5 -0
  443. tools/bolt.diy/public/inspector-script.js +292 -0
  444. tools/bolt.diy/public/logo-dark-styled.png +0 -0
  445. tools/bolt.diy/public/logo-dark.png +0 -0
  446. tools/bolt.diy/public/logo-light-styled.png +0 -0
  447. tools/bolt.diy/public/logo-light.png +0 -0
  448. tools/bolt.diy/public/logo.svg +15 -0
  449. tools/bolt.diy/public/social_preview_index.jpg +0 -0
  450. tools/bolt.diy/scripts/clean.js +45 -0
  451. tools/bolt.diy/scripts/electron-dev.mjs +181 -0
  452. tools/bolt.diy/scripts/setup-env.sh +41 -0
  453. tools/bolt.diy/scripts/update-imports.sh +7 -0
  454. tools/bolt.diy/scripts/update.sh +52 -0
  455. tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
  456. tools/bolt.diy/services/execution-governor/config.ts +42 -0
  457. tools/bolt.diy/services/execution-governor/index.ts +683 -0
  458. tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
  459. tools/bolt.diy/services/execution-governor/package.json +31 -0
  460. tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
  461. tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
  462. tools/bolt.diy/services/execution-governor/types.ts +145 -0
  463. tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
  464. tools/bolt.diy/services/headless-executor/executor.ts +210 -0
  465. tools/bolt.diy/services/headless-executor/index.ts +323 -0
  466. tools/bolt.diy/services/headless-executor/package.json +27 -0
  467. tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
  468. tools/bolt.diy/services/headless-executor/types.ts +38 -0
  469. tools/bolt.diy/test-workflows.sh +240 -0
  470. tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
  471. tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
  472. tools/bolt.diy/tests/mandates/complex.json +53 -0
  473. tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
  474. tools/bolt.diy/tests/mandates/simple.json +35 -0
  475. tools/bolt.diy/tsconfig.json +37 -0
  476. tools/bolt.diy/types/istextorbinary.d.ts +15 -0
  477. tools/bolt.diy/uno.config.ts +279 -0
  478. tools/bolt.diy/vite-electron.config.ts +76 -0
  479. tools/bolt.diy/vite.config.ts +112 -0
  480. tools/bolt.diy/worker-configuration.d.ts +22 -0
  481. tools/bolt.diy/wrangler.toml +6 -0
  482. tools/code_generator.py +461 -0
  483. tools/file_operations.py +465 -0
  484. tools/mandate_generator.py +337 -0
  485. tools/mcpClientUtils.py +1216 -0
  486. tools/sensors.py +285 -0
  487. utils/Agent_types.py +15 -0
  488. utils/AnyToStr.py +0 -0
  489. utils/HHCS.py +277 -0
  490. utils/__init__.py +30 -0
  491. utils/agent.py +3627 -0
  492. utils/aop.py +2948 -0
  493. utils/canonical.py +143 -0
  494. utils/conversation.py +1195 -0
  495. utils/doctrine_versioning +230 -0
  496. utils/formatter.py +474 -0
  497. utils/ledger.py +311 -0
  498. utils/out_types.py +16 -0
  499. utils/rollback.py +339 -0
  500. utils/router.py +929 -0
  501. utils/tui.py +1908 -0
@@ -0,0 +1,1049 @@
1
+ import { memo, useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useStore } from '@nanostores/react';
3
+ import { IconButton } from '~/components/ui/IconButton';
4
+ import { workbenchStore } from '~/lib/stores/workbench';
5
+ import { PortDropdown } from './PortDropdown';
6
+ import { ScreenshotSelector } from './ScreenshotSelector';
7
+ import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
8
+ import { ExpoQrModal } from '~/components/workbench/ExpoQrModal';
9
+ import type { ElementInfo } from './Inspector';
10
+
11
+ type ResizeSide = 'left' | 'right' | null;
12
+
13
+ interface PreviewProps {
14
+ setSelectedElement?: (element: ElementInfo | null) => void;
15
+ }
16
+
17
+ interface WindowSize {
18
+ name: string;
19
+ width: number;
20
+ height: number;
21
+ icon: string;
22
+ hasFrame?: boolean;
23
+ frameType?: 'mobile' | 'tablet' | 'laptop' | 'desktop';
24
+ }
25
+
26
+ const WINDOW_SIZES: WindowSize[] = [
27
+ { name: 'iPhone SE', width: 375, height: 667, icon: 'i-ph:device-mobile', hasFrame: true, frameType: 'mobile' },
28
+ { name: 'iPhone 12/13', width: 390, height: 844, icon: 'i-ph:device-mobile', hasFrame: true, frameType: 'mobile' },
29
+ {
30
+ name: 'iPhone 12/13 Pro Max',
31
+ width: 428,
32
+ height: 926,
33
+ icon: 'i-ph:device-mobile',
34
+ hasFrame: true,
35
+ frameType: 'mobile',
36
+ },
37
+ { name: 'iPad Mini', width: 768, height: 1024, icon: 'i-ph:device-tablet', hasFrame: true, frameType: 'tablet' },
38
+ { name: 'iPad Air', width: 820, height: 1180, icon: 'i-ph:device-tablet', hasFrame: true, frameType: 'tablet' },
39
+ { name: 'iPad Pro 11"', width: 834, height: 1194, icon: 'i-ph:device-tablet', hasFrame: true, frameType: 'tablet' },
40
+ {
41
+ name: 'iPad Pro 12.9"',
42
+ width: 1024,
43
+ height: 1366,
44
+ icon: 'i-ph:device-tablet',
45
+ hasFrame: true,
46
+ frameType: 'tablet',
47
+ },
48
+ { name: 'Small Laptop', width: 1280, height: 800, icon: 'i-ph:laptop', hasFrame: true, frameType: 'laptop' },
49
+ { name: 'Laptop', width: 1366, height: 768, icon: 'i-ph:laptop', hasFrame: true, frameType: 'laptop' },
50
+ { name: 'Large Laptop', width: 1440, height: 900, icon: 'i-ph:laptop', hasFrame: true, frameType: 'laptop' },
51
+ { name: 'Desktop', width: 1920, height: 1080, icon: 'i-ph:monitor', hasFrame: true, frameType: 'desktop' },
52
+ { name: '4K Display', width: 3840, height: 2160, icon: 'i-ph:monitor', hasFrame: true, frameType: 'desktop' },
53
+ ];
54
+
55
+ export const Preview = memo(({ setSelectedElement }: PreviewProps) => {
56
+ const iframeRef = useRef<HTMLIFrameElement>(null);
57
+ const containerRef = useRef<HTMLDivElement>(null);
58
+ const inputRef = useRef<HTMLInputElement>(null);
59
+ const [activePreviewIndex, setActivePreviewIndex] = useState(0);
60
+ const [isPortDropdownOpen, setIsPortDropdownOpen] = useState(false);
61
+ const [isFullscreen, setIsFullscreen] = useState(false);
62
+ const hasSelectedPreview = useRef(false);
63
+ const previews = useStore(workbenchStore.previews);
64
+ const activePreview = previews[activePreviewIndex];
65
+ const [displayPath, setDisplayPath] = useState('/');
66
+ const [iframeUrl, setIframeUrl] = useState<string | undefined>();
67
+ const [isSelectionMode, setIsSelectionMode] = useState(false);
68
+ const [isInspectorMode, setIsInspectorMode] = useState(false);
69
+ const [isDeviceModeOn, setIsDeviceModeOn] = useState(false);
70
+ const [widthPercent, setWidthPercent] = useState<number>(37.5);
71
+ const [currentWidth, setCurrentWidth] = useState<number>(0);
72
+
73
+ const resizingState = useRef({
74
+ isResizing: false,
75
+ side: null as ResizeSide,
76
+ startX: 0,
77
+ startWidthPercent: 37.5,
78
+ windowWidth: window.innerWidth,
79
+ pointerId: null as number | null,
80
+ });
81
+
82
+ // Reduce scaling factor to make resizing less sensitive
83
+ const SCALING_FACTOR = 1;
84
+
85
+ const [isWindowSizeDropdownOpen, setIsWindowSizeDropdownOpen] = useState(false);
86
+ const [selectedWindowSize, setSelectedWindowSize] = useState<WindowSize>(WINDOW_SIZES[0]);
87
+ const [isLandscape, setIsLandscape] = useState(false);
88
+ const [showDeviceFrame, setShowDeviceFrame] = useState(true);
89
+ const [showDeviceFrameInPreview, setShowDeviceFrameInPreview] = useState(false);
90
+ const expoUrl = useStore(expoUrlAtom);
91
+ const [isExpoQrModalOpen, setIsExpoQrModalOpen] = useState(false);
92
+
93
+ useEffect(() => {
94
+ if (!activePreview) {
95
+ setIframeUrl(undefined);
96
+ setDisplayPath('/');
97
+
98
+ return;
99
+ }
100
+
101
+ const { baseUrl } = activePreview;
102
+ setIframeUrl(baseUrl);
103
+ setDisplayPath('/');
104
+ }, [activePreview]);
105
+
106
+ const findMinPortIndex = useCallback(
107
+ (minIndex: number, preview: { port: number }, index: number, array: { port: number }[]) => {
108
+ return preview.port < array[minIndex].port ? index : minIndex;
109
+ },
110
+ [],
111
+ );
112
+
113
+ useEffect(() => {
114
+ if (previews.length > 1 && !hasSelectedPreview.current) {
115
+ const minPortIndex = previews.reduce(findMinPortIndex, 0);
116
+ setActivePreviewIndex(minPortIndex);
117
+ }
118
+ }, [previews, findMinPortIndex]);
119
+
120
+ const reloadPreview = () => {
121
+ if (iframeRef.current) {
122
+ iframeRef.current.src = iframeRef.current.src;
123
+ }
124
+ };
125
+
126
+ const toggleFullscreen = async () => {
127
+ if (!isFullscreen && containerRef.current) {
128
+ await containerRef.current.requestFullscreen();
129
+ } else if (document.fullscreenElement) {
130
+ await document.exitFullscreen();
131
+ }
132
+ };
133
+
134
+ useEffect(() => {
135
+ const handleFullscreenChange = () => {
136
+ setIsFullscreen(!!document.fullscreenElement);
137
+ };
138
+
139
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
140
+
141
+ return () => {
142
+ document.removeEventListener('fullscreenchange', handleFullscreenChange);
143
+ };
144
+ }, []);
145
+
146
+ const toggleDeviceMode = () => {
147
+ setIsDeviceModeOn((prev) => !prev);
148
+ };
149
+
150
+ const startResizing = (e: React.PointerEvent, side: ResizeSide) => {
151
+ if (!isDeviceModeOn) {
152
+ return;
153
+ }
154
+
155
+ const target = e.currentTarget as HTMLElement;
156
+ target.setPointerCapture(e.pointerId);
157
+
158
+ document.body.style.userSelect = 'none';
159
+ document.body.style.cursor = 'ew-resize';
160
+
161
+ resizingState.current = {
162
+ isResizing: true,
163
+ side,
164
+ startX: e.clientX,
165
+ startWidthPercent: widthPercent,
166
+ windowWidth: window.innerWidth,
167
+ pointerId: e.pointerId,
168
+ };
169
+ };
170
+
171
+ const ResizeHandle = ({ side }: { side: ResizeSide }) => {
172
+ if (!side) {
173
+ return null;
174
+ }
175
+
176
+ return (
177
+ <div
178
+ className={`resize-handle-${side}`}
179
+ onPointerDown={(e) => startResizing(e, side)}
180
+ style={{
181
+ position: 'absolute',
182
+ top: 0,
183
+ ...(side === 'left' ? { left: 0, marginLeft: '-7px' } : { right: 0, marginRight: '-7px' }),
184
+ width: '15px',
185
+ height: '100%',
186
+ cursor: 'ew-resize',
187
+ background: 'var(--bolt-elements-background-depth-4, rgba(0,0,0,.3))',
188
+ display: 'flex',
189
+ alignItems: 'center',
190
+ justifyContent: 'center',
191
+ transition: 'background 0.2s',
192
+ userSelect: 'none',
193
+ touchAction: 'none',
194
+ zIndex: 10,
195
+ }}
196
+ onMouseOver={(e) =>
197
+ (e.currentTarget.style.background = 'var(--bolt-elements-background-depth-4, rgba(0,0,0,.3))')
198
+ }
199
+ onMouseOut={(e) =>
200
+ (e.currentTarget.style.background = 'var(--bolt-elements-background-depth-3, rgba(0,0,0,.15))')
201
+ }
202
+ title="Drag to resize width"
203
+ >
204
+ <GripIcon />
205
+ </div>
206
+ );
207
+ };
208
+
209
+ useEffect(() => {
210
+ // Skip if not in device mode
211
+ if (!isDeviceModeOn) {
212
+ return;
213
+ }
214
+
215
+ const handlePointerMove = (e: PointerEvent) => {
216
+ const state = resizingState.current;
217
+
218
+ if (!state.isResizing || e.pointerId !== state.pointerId) {
219
+ return;
220
+ }
221
+
222
+ const dx = e.clientX - state.startX;
223
+ const dxPercent = (dx / state.windowWidth) * 100 * SCALING_FACTOR;
224
+
225
+ let newWidthPercent = state.startWidthPercent;
226
+
227
+ if (state.side === 'right') {
228
+ newWidthPercent = state.startWidthPercent + dxPercent;
229
+ } else if (state.side === 'left') {
230
+ newWidthPercent = state.startWidthPercent - dxPercent;
231
+ }
232
+
233
+ // Limit width percentage between 10% and 90%
234
+ newWidthPercent = Math.max(10, Math.min(newWidthPercent, 90));
235
+
236
+ // Force a synchronous update to ensure the UI reflects the change immediately
237
+ setWidthPercent(newWidthPercent);
238
+
239
+ // Calculate and update the actual pixel width
240
+ if (containerRef.current) {
241
+ const containerWidth = containerRef.current.clientWidth;
242
+ const newWidth = Math.round((containerWidth * newWidthPercent) / 100);
243
+ setCurrentWidth(newWidth);
244
+
245
+ // Apply the width directly to the container for immediate feedback
246
+ const previewContainer = containerRef.current.querySelector('div[style*="width"]');
247
+
248
+ if (previewContainer) {
249
+ (previewContainer as HTMLElement).style.width = `${newWidthPercent}%`;
250
+ }
251
+ }
252
+ };
253
+
254
+ const handlePointerUp = (e: PointerEvent) => {
255
+ const state = resizingState.current;
256
+
257
+ if (!state.isResizing || e.pointerId !== state.pointerId) {
258
+ return;
259
+ }
260
+
261
+ // Find all resize handles
262
+ const handles = document.querySelectorAll('.resize-handle-left, .resize-handle-right');
263
+
264
+ // Release pointer capture from any handle that has it
265
+ handles.forEach((handle) => {
266
+ if ((handle as HTMLElement).hasPointerCapture?.(e.pointerId)) {
267
+ (handle as HTMLElement).releasePointerCapture(e.pointerId);
268
+ }
269
+ });
270
+
271
+ // Reset state
272
+ resizingState.current = {
273
+ ...resizingState.current,
274
+ isResizing: false,
275
+ side: null,
276
+ pointerId: null,
277
+ };
278
+
279
+ document.body.style.userSelect = '';
280
+ document.body.style.cursor = '';
281
+ };
282
+
283
+ // Add event listeners
284
+ document.addEventListener('pointermove', handlePointerMove, { passive: false });
285
+ document.addEventListener('pointerup', handlePointerUp);
286
+ document.addEventListener('pointercancel', handlePointerUp);
287
+
288
+ // Define cleanup function
289
+ function cleanupResizeListeners() {
290
+ document.removeEventListener('pointermove', handlePointerMove);
291
+ document.removeEventListener('pointerup', handlePointerUp);
292
+ document.removeEventListener('pointercancel', handlePointerUp);
293
+
294
+ // Release any lingering pointer captures
295
+ if (resizingState.current.pointerId !== null) {
296
+ const handles = document.querySelectorAll('.resize-handle-left, .resize-handle-right');
297
+ handles.forEach((handle) => {
298
+ if ((handle as HTMLElement).hasPointerCapture?.(resizingState.current.pointerId!)) {
299
+ (handle as HTMLElement).releasePointerCapture(resizingState.current.pointerId!);
300
+ }
301
+ });
302
+
303
+ // Reset state
304
+ resizingState.current = {
305
+ ...resizingState.current,
306
+ isResizing: false,
307
+ side: null,
308
+ pointerId: null,
309
+ };
310
+
311
+ document.body.style.userSelect = '';
312
+ document.body.style.cursor = '';
313
+ }
314
+ }
315
+
316
+ // Return the cleanup function
317
+ // eslint-disable-next-line consistent-return
318
+ return cleanupResizeListeners;
319
+ }, [isDeviceModeOn, SCALING_FACTOR]);
320
+
321
+ useEffect(() => {
322
+ const handleWindowResize = () => {
323
+ // Update the window width in the resizing state
324
+ resizingState.current.windowWidth = window.innerWidth;
325
+
326
+ // Update the current width in pixels
327
+ if (containerRef.current && isDeviceModeOn) {
328
+ const containerWidth = containerRef.current.clientWidth;
329
+ setCurrentWidth(Math.round((containerWidth * widthPercent) / 100));
330
+ }
331
+ };
332
+
333
+ window.addEventListener('resize', handleWindowResize);
334
+
335
+ // Initial calculation of current width
336
+ if (containerRef.current && isDeviceModeOn) {
337
+ const containerWidth = containerRef.current.clientWidth;
338
+ setCurrentWidth(Math.round((containerWidth * widthPercent) / 100));
339
+ }
340
+
341
+ return () => {
342
+ window.removeEventListener('resize', handleWindowResize);
343
+ };
344
+ }, [isDeviceModeOn, widthPercent]);
345
+
346
+ // Update current width when device mode is toggled
347
+ useEffect(() => {
348
+ if (containerRef.current && isDeviceModeOn) {
349
+ const containerWidth = containerRef.current.clientWidth;
350
+ setCurrentWidth(Math.round((containerWidth * widthPercent) / 100));
351
+ }
352
+ }, [isDeviceModeOn]);
353
+
354
+ const GripIcon = () => (
355
+ <div
356
+ style={{
357
+ display: 'flex',
358
+ justifyContent: 'center',
359
+ alignItems: 'center',
360
+ height: '100%',
361
+ pointerEvents: 'none',
362
+ }}
363
+ >
364
+ <div
365
+ style={{
366
+ color: 'var(--bolt-elements-textSecondary, rgba(0,0,0,0.5))',
367
+ fontSize: '10px',
368
+ lineHeight: '5px',
369
+ userSelect: 'none',
370
+ marginLeft: '1px',
371
+ }}
372
+ >
373
+ ••• •••
374
+ </div>
375
+ </div>
376
+ );
377
+
378
+ const openInNewWindow = (size: WindowSize) => {
379
+ if (activePreview?.baseUrl) {
380
+ const match = activePreview.baseUrl.match(/^https?:\/\/([^.]+)\.local-credentialless\.webcontainer-api\.io/);
381
+
382
+ if (match) {
383
+ const previewId = match[1];
384
+ const previewUrl = `/webcontainer/preview/${previewId}`;
385
+
386
+ // Adjust dimensions for landscape mode if applicable
387
+ let width = size.width;
388
+ let height = size.height;
389
+
390
+ if (isLandscape && (size.frameType === 'mobile' || size.frameType === 'tablet')) {
391
+ // Swap width and height for landscape mode
392
+ width = size.height;
393
+ height = size.width;
394
+ }
395
+
396
+ // Create a window with device frame if enabled
397
+ if (showDeviceFrame && size.hasFrame) {
398
+ // Calculate frame dimensions
399
+ const frameWidth = size.frameType === 'mobile' ? (isLandscape ? 120 : 40) : 60; // Width padding on each side
400
+ const frameHeight = size.frameType === 'mobile' ? (isLandscape ? 80 : 80) : isLandscape ? 60 : 100; // Height padding on top and bottom
401
+
402
+ // Create a window with the correct dimensions first
403
+ const newWindow = window.open(
404
+ '',
405
+ '_blank',
406
+ `width=${width + frameWidth},height=${height + frameHeight + 40},menubar=no,toolbar=no,location=no,status=no`,
407
+ );
408
+
409
+ if (!newWindow) {
410
+ console.error('Failed to open new window');
411
+ return;
412
+ }
413
+
414
+ // Create the HTML content for the frame
415
+ const frameColor = getFrameColor();
416
+ const frameRadius = size.frameType === 'mobile' ? '36px' : '20px';
417
+ const framePadding =
418
+ size.frameType === 'mobile'
419
+ ? isLandscape
420
+ ? '40px 60px'
421
+ : '40px 20px'
422
+ : isLandscape
423
+ ? '30px 50px'
424
+ : '50px 30px';
425
+
426
+ // Position notch and home button based on orientation
427
+ const notchTop = isLandscape ? '50%' : '20px';
428
+ const notchLeft = isLandscape ? '30px' : '50%';
429
+ const notchTransform = isLandscape ? 'translateY(-50%)' : 'translateX(-50%)';
430
+ const notchWidth = isLandscape ? '8px' : size.frameType === 'mobile' ? '60px' : '80px';
431
+ const notchHeight = isLandscape ? (size.frameType === 'mobile' ? '60px' : '80px') : '8px';
432
+
433
+ const homeBottom = isLandscape ? '50%' : '15px';
434
+ const homeRight = isLandscape ? '30px' : '50%';
435
+ const homeTransform = isLandscape ? 'translateY(50%)' : 'translateX(50%)';
436
+ const homeWidth = isLandscape ? '4px' : '40px';
437
+ const homeHeight = isLandscape ? '40px' : '4px';
438
+
439
+ // Create HTML content for the wrapper page
440
+ const htmlContent = `
441
+ <!DOCTYPE html>
442
+ <html>
443
+ <head>
444
+ <meta charset="utf-8">
445
+ <title>${size.name} Preview</title>
446
+ <style>
447
+ body {
448
+ margin: 0;
449
+ padding: 0;
450
+ display: flex;
451
+ justify-content: center;
452
+ align-items: center;
453
+ height: 100vh;
454
+ background: #f0f0f0;
455
+ overflow: hidden;
456
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
457
+ }
458
+
459
+ .device-container {
460
+ position: relative;
461
+ }
462
+
463
+ .device-name {
464
+ position: absolute;
465
+ top: -30px;
466
+ left: 0;
467
+ right: 0;
468
+ text-align: center;
469
+ font-size: 14px;
470
+ color: #333;
471
+ }
472
+
473
+ .device-frame {
474
+ position: relative;
475
+ border-radius: ${frameRadius};
476
+ background: ${frameColor};
477
+ padding: ${framePadding};
478
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
479
+ overflow: hidden;
480
+ }
481
+
482
+ /* Notch */
483
+ .device-frame:before {
484
+ content: '';
485
+ position: absolute;
486
+ top: ${notchTop};
487
+ left: ${notchLeft};
488
+ transform: ${notchTransform};
489
+ width: ${notchWidth};
490
+ height: ${notchHeight};
491
+ background: #333;
492
+ border-radius: 4px;
493
+ z-index: 2;
494
+ }
495
+
496
+ /* Home button */
497
+ .device-frame:after {
498
+ content: '';
499
+ position: absolute;
500
+ bottom: ${homeBottom};
501
+ right: ${homeRight};
502
+ transform: ${homeTransform};
503
+ width: ${homeWidth};
504
+ height: ${homeHeight};
505
+ background: #333;
506
+ border-radius: 50%;
507
+ z-index: 2;
508
+ }
509
+
510
+ iframe {
511
+ border: none;
512
+ width: ${width}px;
513
+ height: ${height}px;
514
+ background: white;
515
+ display: block;
516
+ }
517
+ </style>
518
+ </head>
519
+ <body>
520
+ <div class="device-container">
521
+ <div class="device-name">${size.name} ${isLandscape ? '(Landscape)' : '(Portrait)'}</div>
522
+ <div class="device-frame">
523
+ <iframe src="${previewUrl}" sandbox="allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation allow-same-origin" allow="cross-origin-isolated"></iframe>
524
+ </div>
525
+ </div>
526
+ </body>
527
+ </html>
528
+ `;
529
+
530
+ // Write the HTML content to the new window
531
+ newWindow.document.open();
532
+ newWindow.document.write(htmlContent);
533
+ newWindow.document.close();
534
+ } else {
535
+ // Standard window without frame
536
+ const newWindow = window.open(
537
+ previewUrl,
538
+ '_blank',
539
+ `width=${width},height=${height},menubar=no,toolbar=no,location=no,status=no`,
540
+ );
541
+
542
+ if (newWindow) {
543
+ newWindow.focus();
544
+ }
545
+ }
546
+ } else {
547
+ console.warn('[Preview] Invalid WebContainer URL:', activePreview.baseUrl);
548
+ }
549
+ }
550
+ };
551
+
552
+ const openInNewTab = () => {
553
+ if (activePreview?.baseUrl) {
554
+ window.open(activePreview?.baseUrl, '_blank');
555
+ }
556
+ };
557
+
558
+ // Function to get the correct frame padding based on orientation
559
+ const getFramePadding = useCallback(() => {
560
+ if (!selectedWindowSize) {
561
+ return '40px 20px';
562
+ }
563
+
564
+ const isMobile = selectedWindowSize.frameType === 'mobile';
565
+
566
+ if (isLandscape) {
567
+ // Increase horizontal padding in landscape mode to ensure full device frame is visible
568
+ return isMobile ? '40px 60px' : '30px 50px';
569
+ }
570
+
571
+ return isMobile ? '40px 20px' : '50px 30px';
572
+ }, [isLandscape, selectedWindowSize]);
573
+
574
+ // Function to get the scale factor for the device frame
575
+ const getDeviceScale = useCallback(() => {
576
+ // Always return 1 to ensure the device frame is shown at its exact size
577
+ return 1;
578
+ }, [isLandscape, selectedWindowSize, widthPercent]);
579
+
580
+ // Update the device scale when needed
581
+ useEffect(() => {
582
+ /*
583
+ * Intentionally disabled - we want to maintain scale of 1
584
+ * No dynamic scaling to ensure device frame matches external window exactly
585
+ */
586
+ // Intentionally empty cleanup function - no cleanup needed
587
+ return () => {
588
+ // No cleanup needed
589
+ };
590
+ }, [isDeviceModeOn, showDeviceFrameInPreview, getDeviceScale, isLandscape, selectedWindowSize]);
591
+
592
+ // Function to get the frame color based on dark mode
593
+ const getFrameColor = useCallback(() => {
594
+ // Check if the document has a dark class or data-theme="dark"
595
+ const isDarkMode =
596
+ document.documentElement.classList.contains('dark') ||
597
+ document.documentElement.getAttribute('data-theme') === 'dark' ||
598
+ window.matchMedia('(prefers-color-scheme: dark)').matches;
599
+
600
+ // Return a darker color for light mode, lighter color for dark mode
601
+ return isDarkMode ? '#555' : '#111';
602
+ }, []);
603
+
604
+ // Effect to handle color scheme changes
605
+ useEffect(() => {
606
+ const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
607
+
608
+ const handleColorSchemeChange = () => {
609
+ // Force a re-render when color scheme changes
610
+ if (showDeviceFrameInPreview) {
611
+ setShowDeviceFrameInPreview(true);
612
+ }
613
+ };
614
+
615
+ darkModeMediaQuery.addEventListener('change', handleColorSchemeChange);
616
+
617
+ return () => {
618
+ darkModeMediaQuery.removeEventListener('change', handleColorSchemeChange);
619
+ };
620
+ }, [showDeviceFrameInPreview]);
621
+
622
+ useEffect(() => {
623
+ const handleMessage = (event: MessageEvent) => {
624
+ if (event.data.type === 'INSPECTOR_READY') {
625
+ if (iframeRef.current?.contentWindow) {
626
+ iframeRef.current.contentWindow.postMessage(
627
+ {
628
+ type: 'INSPECTOR_ACTIVATE',
629
+ active: isInspectorMode,
630
+ },
631
+ '*',
632
+ );
633
+ }
634
+ } else if (event.data.type === 'INSPECTOR_CLICK') {
635
+ const element = event.data.elementInfo;
636
+
637
+ navigator.clipboard.writeText(element.displayText).then(() => {
638
+ setSelectedElement?.(element);
639
+ });
640
+ }
641
+ };
642
+
643
+ window.addEventListener('message', handleMessage);
644
+
645
+ return () => window.removeEventListener('message', handleMessage);
646
+ }, [isInspectorMode]);
647
+
648
+ const toggleInspectorMode = () => {
649
+ const newInspectorMode = !isInspectorMode;
650
+ setIsInspectorMode(newInspectorMode);
651
+
652
+ if (iframeRef.current?.contentWindow) {
653
+ iframeRef.current.contentWindow.postMessage(
654
+ {
655
+ type: 'INSPECTOR_ACTIVATE',
656
+ active: newInspectorMode,
657
+ },
658
+ '*',
659
+ );
660
+ }
661
+ };
662
+
663
+ return (
664
+ <div ref={containerRef} className={`w-full h-full flex flex-col relative`}>
665
+ {isPortDropdownOpen && (
666
+ <div className="z-iframe-overlay w-full h-full absolute" onClick={() => setIsPortDropdownOpen(false)} />
667
+ )}
668
+ <div className="bg-bolt-elements-background-depth-2 p-2 flex items-center gap-2">
669
+ <div className="flex items-center gap-2">
670
+ <IconButton icon="i-ph:arrow-clockwise" onClick={reloadPreview} />
671
+ <IconButton
672
+ icon="i-ph:selection"
673
+ onClick={() => setIsSelectionMode(!isSelectionMode)}
674
+ className={isSelectionMode ? 'bg-bolt-elements-background-depth-3' : ''}
675
+ />
676
+ </div>
677
+
678
+ <div className="flex-grow flex items-center gap-1 bg-bolt-elements-preview-addressBar-background border border-bolt-elements-borderColor text-bolt-elements-preview-addressBar-text rounded-full px-1 py-1 text-sm hover:bg-bolt-elements-preview-addressBar-backgroundHover hover:focus-within:bg-bolt-elements-preview-addressBar-backgroundActive focus-within:bg-bolt-elements-preview-addressBar-backgroundActive focus-within-border-bolt-elements-borderColorActive focus-within:text-bolt-elements-preview-addressBar-textActive">
679
+ <PortDropdown
680
+ activePreviewIndex={activePreviewIndex}
681
+ setActivePreviewIndex={setActivePreviewIndex}
682
+ isDropdownOpen={isPortDropdownOpen}
683
+ setHasSelectedPreview={(value) => (hasSelectedPreview.current = value)}
684
+ setIsDropdownOpen={setIsPortDropdownOpen}
685
+ previews={previews}
686
+ />
687
+ <input
688
+ title="URL Path"
689
+ ref={inputRef}
690
+ className="w-full bg-transparent outline-none"
691
+ type="text"
692
+ value={displayPath}
693
+ onChange={(event) => {
694
+ setDisplayPath(event.target.value);
695
+ }}
696
+ onKeyDown={(event) => {
697
+ if (event.key === 'Enter' && activePreview) {
698
+ let targetPath = displayPath.trim();
699
+
700
+ if (!targetPath.startsWith('/')) {
701
+ targetPath = '/' + targetPath;
702
+ }
703
+
704
+ const fullUrl = activePreview.baseUrl + targetPath;
705
+ setIframeUrl(fullUrl);
706
+ setDisplayPath(targetPath);
707
+
708
+ if (inputRef.current) {
709
+ inputRef.current.blur();
710
+ }
711
+ }
712
+ }}
713
+ disabled={!activePreview}
714
+ />
715
+ </div>
716
+
717
+ <div className="flex items-center gap-2">
718
+ <IconButton
719
+ icon="i-ph:devices"
720
+ onClick={toggleDeviceMode}
721
+ title={isDeviceModeOn ? 'Switch to Responsive Mode' : 'Switch to Device Mode'}
722
+ />
723
+
724
+ {expoUrl && <IconButton icon="i-ph:qr-code" onClick={() => setIsExpoQrModalOpen(true)} title="Show QR" />}
725
+
726
+ <ExpoQrModal open={isExpoQrModalOpen} onClose={() => setIsExpoQrModalOpen(false)} />
727
+
728
+ {isDeviceModeOn && (
729
+ <>
730
+ <IconButton
731
+ icon="i-ph:device-rotate"
732
+ onClick={() => setIsLandscape(!isLandscape)}
733
+ title={isLandscape ? 'Switch to Portrait' : 'Switch to Landscape'}
734
+ />
735
+ <IconButton
736
+ icon={showDeviceFrameInPreview ? 'i-ph:device-mobile' : 'i-ph:device-mobile-slash'}
737
+ onClick={() => setShowDeviceFrameInPreview(!showDeviceFrameInPreview)}
738
+ title={showDeviceFrameInPreview ? 'Hide Device Frame' : 'Show Device Frame'}
739
+ />
740
+ </>
741
+ )}
742
+ <IconButton
743
+ icon="i-ph:cursor-click"
744
+ onClick={toggleInspectorMode}
745
+ className={
746
+ isInspectorMode ? 'bg-bolt-elements-background-depth-3 !text-bolt-elements-item-contentAccent' : ''
747
+ }
748
+ title={isInspectorMode ? 'Disable Element Inspector' : 'Enable Element Inspector'}
749
+ />
750
+ <IconButton
751
+ icon={isFullscreen ? 'i-ph:arrows-in' : 'i-ph:arrows-out'}
752
+ onClick={toggleFullscreen}
753
+ title={isFullscreen ? 'Exit Full Screen' : 'Full Screen'}
754
+ />
755
+
756
+ <div className="flex items-center relative">
757
+ <IconButton
758
+ icon="i-ph:list"
759
+ onClick={() => setIsWindowSizeDropdownOpen(!isWindowSizeDropdownOpen)}
760
+ title="New Window Options"
761
+ />
762
+
763
+ {isWindowSizeDropdownOpen && (
764
+ <>
765
+ <div className="fixed inset-0 z-50" onClick={() => setIsWindowSizeDropdownOpen(false)} />
766
+ <div className="absolute right-0 top-full mt-2 z-50 min-w-[240px] max-h-[400px] overflow-y-auto bg-white dark:bg-black rounded-xl shadow-2xl border border-[#E5E7EB] dark:border-[rgba(255,255,255,0.1)] overflow-hidden">
767
+ <div className="p-3 border-b border-[#E5E7EB] dark:border-[rgba(255,255,255,0.1)]">
768
+ <div className="flex items-center justify-between mb-2">
769
+ <span className="text-sm font-medium text-[#111827] dark:text-gray-300">Window Options</span>
770
+ </div>
771
+ <div className="flex flex-col gap-2">
772
+ <button
773
+ className={`flex w-full justify-between items-center text-start bg-transparent text-xs text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary`}
774
+ onClick={() => {
775
+ openInNewTab();
776
+ }}
777
+ >
778
+ <span>Open in new tab</span>
779
+ <div className="i-ph:arrow-square-out h-5 w-4" />
780
+ </button>
781
+ <button
782
+ className={`flex w-full justify-between items-center text-start bg-transparent text-xs text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary`}
783
+ onClick={() => {
784
+ if (!activePreview?.baseUrl) {
785
+ console.warn('[Preview] No active preview available');
786
+ return;
787
+ }
788
+
789
+ const match = activePreview.baseUrl.match(
790
+ /^https?:\/\/([^.]+)\.local-credentialless\.webcontainer-api\.io/,
791
+ );
792
+
793
+ if (!match) {
794
+ console.warn('[Preview] Invalid WebContainer URL:', activePreview.baseUrl);
795
+ return;
796
+ }
797
+
798
+ const previewId = match[1];
799
+ const previewUrl = `/webcontainer/preview/${previewId}`;
800
+
801
+ // Open in a new window with simple parameters
802
+ window.open(
803
+ previewUrl,
804
+ `preview-${previewId}`,
805
+ 'width=1280,height=720,menubar=no,toolbar=no,location=no,status=no,resizable=yes',
806
+ );
807
+ }}
808
+ >
809
+ <span>Open in new window</span>
810
+ <div className="i-ph:browser h-5 w-4" />
811
+ </button>
812
+ <div className="flex items-center justify-between">
813
+ <span className="text-xs text-bolt-elements-textTertiary">Show Device Frame</span>
814
+ <button
815
+ className={`w-10 h-5 rounded-full transition-colors duration-200 ${
816
+ showDeviceFrame ? 'bg-[#6D28D9]' : 'bg-gray-300 dark:bg-gray-700'
817
+ } relative`}
818
+ onClick={(e) => {
819
+ e.stopPropagation();
820
+ setShowDeviceFrame(!showDeviceFrame);
821
+ }}
822
+ >
823
+ <span
824
+ className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform duration-200 ${
825
+ showDeviceFrame ? 'transform translate-x-5' : ''
826
+ }`}
827
+ />
828
+ </button>
829
+ </div>
830
+ <div className="flex items-center justify-between">
831
+ <span className="text-xs text-bolt-elements-textTertiary">Landscape Mode</span>
832
+ <button
833
+ className={`w-10 h-5 rounded-full transition-colors duration-200 ${
834
+ isLandscape ? 'bg-[#6D28D9]' : 'bg-gray-300 dark:bg-gray-700'
835
+ } relative`}
836
+ onClick={(e) => {
837
+ e.stopPropagation();
838
+ setIsLandscape(!isLandscape);
839
+ }}
840
+ >
841
+ <span
842
+ className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform duration-200 ${
843
+ isLandscape ? 'transform translate-x-5' : ''
844
+ }`}
845
+ />
846
+ </button>
847
+ </div>
848
+ </div>
849
+ </div>
850
+ {WINDOW_SIZES.map((size) => (
851
+ <button
852
+ key={size.name}
853
+ className="w-full px-4 py-3.5 text-left text-[#111827] dark:text-gray-300 text-sm whitespace-nowrap flex items-center gap-3 group hover:bg-[#F5EEFF] dark:hover:bg-gray-900 bg-white dark:bg-black"
854
+ onClick={() => {
855
+ setSelectedWindowSize(size);
856
+ setIsWindowSizeDropdownOpen(false);
857
+ openInNewWindow(size);
858
+ }}
859
+ >
860
+ <div
861
+ className={`${size.icon} w-5 h-5 text-[#6B7280] dark:text-gray-400 group-hover:text-[#6D28D9] dark:group-hover:text-[#6D28D9] transition-colors duration-200`}
862
+ />
863
+ <div className="flex-grow flex flex-col">
864
+ <span className="font-medium group-hover:text-[#6D28D9] dark:group-hover:text-[#6D28D9] transition-colors duration-200">
865
+ {size.name}
866
+ </span>
867
+ <span className="text-xs text-[#6B7280] dark:text-gray-400 group-hover:text-[#6D28D9] dark:group-hover:text-[#6D28D9] transition-colors duration-200">
868
+ {isLandscape && (size.frameType === 'mobile' || size.frameType === 'tablet')
869
+ ? `${size.height} × ${size.width}`
870
+ : `${size.width} × ${size.height}`}
871
+ {size.hasFrame && showDeviceFrame ? ' (with frame)' : ''}
872
+ </span>
873
+ </div>
874
+ {selectedWindowSize.name === size.name && (
875
+ <div className="text-[#6D28D9] dark:text-[#6D28D9]">
876
+ <svg
877
+ xmlns="http://www.w3.org/2000/svg"
878
+ width="16"
879
+ height="16"
880
+ viewBox="0 0 24 24"
881
+ fill="none"
882
+ stroke="currentColor"
883
+ strokeWidth="2"
884
+ strokeLinecap="round"
885
+ strokeLinejoin="round"
886
+ >
887
+ <polyline points="20 6 9 17 4 12"></polyline>
888
+ </svg>
889
+ </div>
890
+ )}
891
+ </button>
892
+ ))}
893
+ </div>
894
+ </>
895
+ )}
896
+ </div>
897
+ </div>
898
+ </div>
899
+
900
+ <div className="flex-1 border-t border-bolt-elements-borderColor flex justify-center items-center overflow-auto">
901
+ <div
902
+ style={{
903
+ width: isDeviceModeOn ? (showDeviceFrameInPreview ? '100%' : `${widthPercent}%`) : '100%',
904
+ height: '100%',
905
+ overflow: 'auto',
906
+ background: 'var(--bolt-elements-background-depth-1)',
907
+ position: 'relative',
908
+ display: 'flex',
909
+ justifyContent: 'center',
910
+ alignItems: 'center',
911
+ }}
912
+ >
913
+ {activePreview ? (
914
+ <>
915
+ {isDeviceModeOn && showDeviceFrameInPreview ? (
916
+ <div
917
+ className="device-wrapper"
918
+ style={{
919
+ display: 'flex',
920
+ justifyContent: 'center',
921
+ alignItems: 'center',
922
+ width: '100%',
923
+ height: '100%',
924
+ padding: '0',
925
+ overflow: 'auto',
926
+ transition: 'all 0.3s ease',
927
+ position: 'relative',
928
+ }}
929
+ >
930
+ <div
931
+ className="device-frame-container"
932
+ style={{
933
+ position: 'relative',
934
+ borderRadius: selectedWindowSize.frameType === 'mobile' ? '36px' : '20px',
935
+ background: getFrameColor(),
936
+ padding: getFramePadding(),
937
+ boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
938
+ overflow: 'hidden',
939
+ transform: 'scale(1)',
940
+ transformOrigin: 'center center',
941
+ transition: 'all 0.3s ease',
942
+ margin: '40px',
943
+ width: isLandscape
944
+ ? `${selectedWindowSize.height + (selectedWindowSize.frameType === 'mobile' ? 120 : 60)}px`
945
+ : `${selectedWindowSize.width + (selectedWindowSize.frameType === 'mobile' ? 40 : 60)}px`,
946
+ height: isLandscape
947
+ ? `${selectedWindowSize.width + (selectedWindowSize.frameType === 'mobile' ? 80 : 60)}px`
948
+ : `${selectedWindowSize.height + (selectedWindowSize.frameType === 'mobile' ? 80 : 100)}px`,
949
+ }}
950
+ >
951
+ {/* Notch - positioned based on orientation */}
952
+ <div
953
+ style={{
954
+ position: 'absolute',
955
+ top: isLandscape ? '50%' : '20px',
956
+ left: isLandscape ? '30px' : '50%',
957
+ transform: isLandscape ? 'translateY(-50%)' : 'translateX(-50%)',
958
+ width: isLandscape ? '8px' : selectedWindowSize.frameType === 'mobile' ? '60px' : '80px',
959
+ height: isLandscape ? (selectedWindowSize.frameType === 'mobile' ? '60px' : '80px') : '8px',
960
+ background: '#333',
961
+ borderRadius: '4px',
962
+ zIndex: 2,
963
+ }}
964
+ />
965
+
966
+ {/* Home button - positioned based on orientation */}
967
+ <div
968
+ style={{
969
+ position: 'absolute',
970
+ bottom: isLandscape ? '50%' : '15px',
971
+ right: isLandscape ? '30px' : '50%',
972
+ transform: isLandscape ? 'translateY(50%)' : 'translateX(50%)',
973
+ width: isLandscape ? '4px' : '40px',
974
+ height: isLandscape ? '40px' : '4px',
975
+ background: '#333',
976
+ borderRadius: '50%',
977
+ zIndex: 2,
978
+ }}
979
+ />
980
+
981
+ <iframe
982
+ ref={iframeRef}
983
+ title="preview"
984
+ style={{
985
+ border: 'none',
986
+ width: isLandscape ? `${selectedWindowSize.height}px` : `${selectedWindowSize.width}px`,
987
+ height: isLandscape ? `${selectedWindowSize.width}px` : `${selectedWindowSize.height}px`,
988
+ background: 'white',
989
+ display: 'block',
990
+ }}
991
+ src={iframeUrl}
992
+ sandbox="allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation allow-same-origin"
993
+ allow="cross-origin-isolated"
994
+ />
995
+ </div>
996
+ </div>
997
+ ) : (
998
+ <iframe
999
+ ref={iframeRef}
1000
+ title="preview"
1001
+ className="border-none w-full h-full bg-bolt-elements-background-depth-1"
1002
+ src={iframeUrl}
1003
+ sandbox="allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation allow-same-origin"
1004
+ allow="geolocation; ch-ua-full-version-list; cross-origin-isolated; screen-wake-lock; publickey-credentials-get; shared-storage-select-url; ch-ua-arch; bluetooth; compute-pressure; ch-prefers-reduced-transparency; deferred-fetch; usb; ch-save-data; publickey-credentials-create; shared-storage; deferred-fetch-minimal; run-ad-auction; ch-ua-form-factors; ch-downlink; otp-credentials; payment; ch-ua; ch-ua-model; ch-ect; autoplay; camera; private-state-token-issuance; accelerometer; ch-ua-platform-version; idle-detection; private-aggregation; interest-cohort; ch-viewport-height; local-fonts; ch-ua-platform; midi; ch-ua-full-version; xr-spatial-tracking; clipboard-read; gamepad; display-capture; keyboard-map; join-ad-interest-group; ch-width; ch-prefers-reduced-motion; browsing-topics; encrypted-media; gyroscope; serial; ch-rtt; ch-ua-mobile; window-management; unload; ch-dpr; ch-prefers-color-scheme; ch-ua-wow64; attribution-reporting; fullscreen; identity-credentials-get; private-state-token-redemption; hid; ch-ua-bitness; storage-access; sync-xhr; ch-device-memory; ch-viewport-width; picture-in-picture; magnetometer; clipboard-write; microphone"
1005
+ />
1006
+ )}
1007
+ <ScreenshotSelector
1008
+ isSelectionMode={isSelectionMode}
1009
+ setIsSelectionMode={setIsSelectionMode}
1010
+ containerRef={iframeRef}
1011
+ />
1012
+ </>
1013
+ ) : (
1014
+ <div className="flex w-full h-full justify-center items-center bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary">
1015
+ No preview available
1016
+ </div>
1017
+ )}
1018
+
1019
+ {isDeviceModeOn && !showDeviceFrameInPreview && (
1020
+ <>
1021
+ {/* Width indicator */}
1022
+ <div
1023
+ style={{
1024
+ position: 'absolute',
1025
+ top: '-25px',
1026
+ left: '50%',
1027
+ transform: 'translateX(-50%)',
1028
+ background: 'var(--bolt-elements-background-depth-3, rgba(0,0,0,0.7))',
1029
+ color: 'var(--bolt-elements-textPrimary, white)',
1030
+ padding: '2px 8px',
1031
+ borderRadius: '4px',
1032
+ fontSize: '12px',
1033
+ pointerEvents: 'none',
1034
+ opacity: resizingState.current.isResizing ? 1 : 0,
1035
+ transition: 'opacity 0.3s',
1036
+ }}
1037
+ >
1038
+ {currentWidth}px
1039
+ </div>
1040
+
1041
+ <ResizeHandle side="left" />
1042
+ <ResizeHandle side="right" />
1043
+ </>
1044
+ )}
1045
+ </div>
1046
+ </div>
1047
+ </div>
1048
+ );
1049
+ });