@dimina-kit/devtools 0.4.0-dev.20260828161241 → 0.4.0-dev.20260907055341

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 (277) hide show
  1. package/README.md +52 -729
  2. package/dist/main/app/app.d.ts +27 -7
  3. package/dist/main/app/app.js +285 -681
  4. package/dist/main/app/editor-view.d.ts +21 -0
  5. package/dist/main/app/editor-view.js +71 -0
  6. package/dist/main/app/global-mirrors.d.ts +23 -0
  7. package/dist/main/app/global-mirrors.js +37 -0
  8. package/dist/main/app/host-sidebar-default.d.ts +19 -0
  9. package/dist/main/app/host-sidebar-default.js +44 -0
  10. package/dist/main/app/menu-setup.d.ts +13 -0
  11. package/dist/main/app/menu-setup.js +49 -0
  12. package/dist/main/app/open-project-ipc.d.ts +13 -0
  13. package/dist/main/app/open-project-ipc.js +21 -0
  14. package/dist/main/app/project-list-surface.d.ts +31 -0
  15. package/dist/main/app/project-list-surface.js +20 -0
  16. package/dist/main/app/project-window.d.ts +36 -0
  17. package/dist/main/app/project-window.js +93 -0
  18. package/dist/main/app/servers.d.ts +17 -0
  19. package/dist/main/app/servers.js +60 -0
  20. package/dist/main/app/ui-extension-targets.d.ts +42 -0
  21. package/dist/main/app/ui-extension-targets.js +101 -0
  22. package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
  23. package/dist/main/app/window-close-reveal.harness.js +65 -0
  24. package/dist/main/app/window-events.d.ts +15 -0
  25. package/dist/main/app/window-events.js +45 -0
  26. package/dist/main/app/window-runtime-services.d.ts +41 -0
  27. package/dist/main/app/window-runtime-services.js +178 -0
  28. package/dist/main/app/workbench-modules.d.ts +24 -0
  29. package/dist/main/app/workbench-modules.js +51 -0
  30. package/dist/main/app/workbench-window.d.ts +85 -0
  31. package/dist/main/app/workbench-window.js +310 -0
  32. package/dist/main/index.bundle.js +10669 -9096
  33. package/dist/main/ipc/app.d.ts +4 -1
  34. package/dist/main/ipc/app.js +4 -3
  35. package/dist/main/ipc/device-picker.d.ts +16 -0
  36. package/dist/main/ipc/device-picker.js +27 -0
  37. package/dist/main/ipc/index.d.ts +1 -0
  38. package/dist/main/ipc/index.js +1 -0
  39. package/dist/main/ipc/internal-devtools.d.ts +2 -1
  40. package/dist/main/ipc/internal-devtools.js +4 -3
  41. package/dist/main/ipc/popover.d.ts +4 -1
  42. package/dist/main/ipc/popover.js +25 -9
  43. package/dist/main/ipc/project-create.d.ts +2 -1
  44. package/dist/main/ipc/project-create.js +6 -5
  45. package/dist/main/ipc/project-fs.d.ts +3 -1
  46. package/dist/main/ipc/project-fs.js +17 -17
  47. package/dist/main/ipc/projects.d.ts +3 -3
  48. package/dist/main/ipc/projects.js +13 -12
  49. package/dist/main/ipc/session.d.ts +4 -1
  50. package/dist/main/ipc/session.js +16 -11
  51. package/dist/main/ipc/settings.d.ts +4 -1
  52. package/dist/main/ipc/settings.js +12 -19
  53. package/dist/main/ipc/simulator-module.d.ts +5 -0
  54. package/dist/main/ipc/simulator-module.js +9 -5
  55. package/dist/main/ipc/simulator.d.ts +4 -1
  56. package/dist/main/ipc/simulator.js +12 -18
  57. package/dist/main/ipc/tooltip.d.ts +2 -1
  58. package/dist/main/ipc/tooltip.js +8 -7
  59. package/dist/main/ipc/views.d.ts +4 -1
  60. package/dist/main/ipc/views.js +20 -9
  61. package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +2 -20
  62. package/dist/main/runtime/devtools-backend-before-quit.harness.js +14 -283
  63. package/dist/main/runtime/devtools-backend.js +14 -3
  64. package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
  65. package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
  66. package/dist/main/services/app-services.d.ts +62 -0
  67. package/dist/main/services/app-services.js +64 -0
  68. package/dist/main/services/automation/connection-target.d.ts +36 -0
  69. package/dist/main/services/automation/connection-target.js +29 -0
  70. package/dist/main/services/automation/console-bridge.d.ts +28 -0
  71. package/dist/main/services/automation/console-bridge.js +91 -0
  72. package/dist/main/services/automation/handlers/app.js +4 -4
  73. package/dist/main/services/automation/handlers/element.js +55 -21
  74. package/dist/main/services/automation/index.d.ts +12 -1
  75. package/dist/main/services/automation/index.js +54 -133
  76. package/dist/main/services/default-adapter.js +1 -1
  77. package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
  78. package/dist/main/services/fs-bridge-protocol.js +34 -0
  79. package/dist/main/services/fs-watch-sse.d.ts +8 -1
  80. package/dist/main/services/fs-watch-sse.js +19 -5
  81. package/dist/main/services/mcp/connection-owner.d.ts +38 -0
  82. package/dist/main/services/mcp/connection-owner.js +79 -0
  83. package/dist/main/services/mcp/index.d.ts +3 -1
  84. package/dist/main/services/mcp/index.js +4 -3
  85. package/dist/main/services/mcp/opened-project.d.ts +88 -0
  86. package/dist/main/services/mcp/opened-project.js +60 -0
  87. package/dist/main/services/mcp/target-manager.d.ts +71 -23
  88. package/dist/main/services/mcp/target-manager.js +269 -142
  89. package/dist/main/services/mcp/target-selection.d.ts +47 -0
  90. package/dist/main/services/mcp/target-selection.js +71 -0
  91. package/dist/main/services/mcp/tools/context-tools.js +20 -5
  92. package/dist/main/services/mcp/tools/project-tools.d.ts +29 -6
  93. package/dist/main/services/mcp/tools/project-tools.js +37 -25
  94. package/dist/main/services/module.d.ts +13 -5
  95. package/dist/main/services/notifications/renderer-notifier.d.ts +28 -4
  96. package/dist/main/services/notifications/renderer-notifier.js +15 -3
  97. package/dist/main/services/projects/local-provider.js +2 -0
  98. package/dist/main/services/projects/project-repository.d.ts +31 -1
  99. package/dist/main/services/projects/project-repository.js +144 -20
  100. package/dist/main/services/projects/types.d.ts +31 -8
  101. package/dist/main/services/safe-area/index.d.ts +19 -5
  102. package/dist/main/services/safe-area/index.js +38 -13
  103. package/dist/main/services/simulator/ui-extensions.d.ts +8 -0
  104. package/dist/main/services/simulator/ui-extensions.js +16 -6
  105. package/dist/main/services/simulator-storage/index.d.ts +12 -1
  106. package/dist/main/services/simulator-storage/index.js +22 -7
  107. package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
  108. package/dist/main/services/simulator-storage/window-scope.js +20 -0
  109. package/dist/main/services/views/host-slot-port-channel.d.ts +20 -3
  110. package/dist/main/services/views/host-slot-port-channel.js +33 -5
  111. package/dist/main/services/views/managed-web-contents-view.js +21 -1
  112. package/dist/main/services/views/native-simulator-view.js +13 -16
  113. package/dist/main/services/views/overlay-panels-view.d.ts +13 -0
  114. package/dist/main/services/views/overlay-panels-view.js +31 -0
  115. package/dist/main/services/views/view-manager.d.ts +1 -1
  116. package/dist/main/services/views/view-manager.js +3 -0
  117. package/dist/main/services/window-contexts/context-router.d.ts +36 -0
  118. package/dist/main/services/window-contexts/context-router.js +55 -0
  119. package/dist/main/services/workbench-coi-host.d.ts +52 -0
  120. package/dist/main/services/workbench-coi-host.js +227 -0
  121. package/dist/main/services/workbench-coi-server.js +75 -133
  122. package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
  123. package/dist/main/services/workbench-coi-shutdown.js +69 -0
  124. package/dist/main/services/workbench-coi-static.d.ts +23 -0
  125. package/dist/main/services/workbench-coi-static.js +87 -0
  126. package/dist/main/services/workbench-context.d.ts +27 -3
  127. package/dist/main/services/workbench-context.js +10 -5
  128. package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
  129. package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
  130. package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
  131. package/dist/main/services/workspace/compile-mode-store.js +71 -0
  132. package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
  133. package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
  134. package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
  135. package/dist/main/services/workspace/workspace-referer.js +26 -0
  136. package/dist/main/services/workspace/workspace-service.d.ts +4 -0
  137. package/dist/main/services/workspace/workspace-service.js +51 -84
  138. package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
  139. package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
  140. package/dist/main/utils/ipc-context-source.d.ts +40 -0
  141. package/dist/main/utils/ipc-context-source.js +23 -0
  142. package/dist/main/utils/ipc-registry.d.ts +42 -36
  143. package/dist/main/utils/ipc-registry.js +117 -50
  144. package/dist/main/utils/sender-policy.d.ts +9 -0
  145. package/dist/main/utils/sender-policy.js +26 -3
  146. package/dist/main/utils/theme.d.ts +3 -2
  147. package/dist/main/utils/theme.js +3 -2
  148. package/dist/main/windows/main-window/create.d.ts +16 -0
  149. package/dist/main/windows/main-window/create.js +19 -11
  150. package/dist/main/windows/main-window/events.d.ts +1 -1
  151. package/dist/main/windows/main-window/events.js +26 -13
  152. package/dist/main/windows/main-window/index.d.ts +1 -1
  153. package/dist/main/windows/main-window/index.js +1 -1
  154. package/dist/native-host/common/common.js +94 -73
  155. package/dist/native-host/render/render.js +4079 -3171
  156. package/dist/native-host/service/service.js +2 -2
  157. package/dist/preload/runtime/native-host.d.ts +4 -0
  158. package/dist/preload/runtime/temp-files.js +9 -2
  159. package/dist/preload/runtime/wait-for-slot-root.d.ts +7 -4
  160. package/dist/preload/runtime/wait-for-slot-root.js +36 -8
  161. package/dist/preload/windows/host-dialog-runtime.cjs +10 -3
  162. package/dist/preload/windows/host-dialog-runtime.cjs.map +2 -2
  163. package/dist/preload/windows/host-sidebar-runtime.cjs +10 -3
  164. package/dist/preload/windows/host-sidebar-runtime.cjs.map +2 -2
  165. package/dist/preload/windows/host-toolbar-runtime.cjs +10 -3
  166. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  167. package/dist/preload/windows/main.cjs +181 -4
  168. package/dist/preload/windows/main.cjs.map +4 -4
  169. package/dist/preload/windows/simulator.cjs +10 -0
  170. package/dist/preload/windows/simulator.cjs.map +2 -2
  171. package/dist/preload/windows/simulator.js +10 -0
  172. package/dist/render-host/pageFrame.html +1 -1
  173. package/dist/render-host/preload.cjs +16 -4
  174. package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
  175. package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
  176. package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
  177. package/dist/renderer/assets/{createLucideIcon-Bqi8hpdi.js → createLucideIcon-cZ9FlZZw.js} +2 -2
  178. package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
  179. package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
  180. package/dist/renderer/assets/dist-B5HADirS.js +2 -0
  181. package/dist/renderer/assets/{dist-DKWdE1TK.js → dist-DIFlRMKe.js} +2 -2
  182. package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
  183. package/dist/renderer/assets/index-CEL_XFge.js +2 -0
  184. package/dist/renderer/assets/{input-Ct3alvkq.js → input-CAObw95L.js} +2 -2
  185. package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
  186. package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
  187. package/dist/renderer/assets/{jsx-runtime-DGLHmmOq.js → jsx-runtime-DIw5wnYl.js} +1 -1
  188. package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
  189. package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
  190. package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
  191. package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
  192. package/dist/renderer/assets/{projectCreateDialog-Bbjz75bP.js → projectCreateDialog-BmLqiYxn.js} +2 -2
  193. package/dist/renderer/assets/search-BMvuYF48.js +2 -0
  194. package/dist/renderer/assets/select-nFP95TxM.js +2 -0
  195. package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
  196. package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
  197. package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
  198. package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
  199. package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
  200. package/dist/renderer/assets/{tooltip-B_-_YVbw.js → tooltip-D6AxXRk5.js} +2 -2
  201. package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
  202. package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
  203. package/dist/renderer/assets/{utils-kfQov6Pa.js → utils-sEB5OZEQ.js} +2 -2
  204. package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
  205. package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
  206. package/dist/renderer/assets/workbench-BLJ0wKsg.js +5 -0
  207. package/dist/renderer/assets/{workbenchSettings-CV4LVA9b.js → workbenchSettings-YD0Ctbm_.js} +3 -3
  208. package/dist/renderer/entries/device-picker/index.html +28 -0
  209. package/dist/renderer/entries/host-sidebar-default/index.html +6 -5
  210. package/dist/renderer/entries/main/index.html +16 -17
  211. package/dist/renderer/entries/popover/index.html +16 -10
  212. package/dist/renderer/entries/project-create-dialog/index.html +12 -12
  213. package/dist/renderer/entries/settings/index.html +10 -10
  214. package/dist/renderer/entries/tooltip/index.html +5 -5
  215. package/dist/renderer/entries/update-dialog/index.html +10 -10
  216. package/dist/renderer/entries/workbench/index.html +49 -0
  217. package/dist/renderer/entries/workbench-settings/index.html +12 -11
  218. package/dist/service-host/host-env-update.cjs +23 -0
  219. package/dist/service-host/preload.cjs +24 -49
  220. package/dist/service-host/service-message-queue.cjs +71 -0
  221. package/dist/service-host/sync-impls/system-info.js +31 -9
  222. package/dist/shared/compile-mode-state.d.ts +73 -0
  223. package/dist/shared/compile-mode-state.js +100 -0
  224. package/dist/shared/compile-modes.d.ts +152 -0
  225. package/dist/shared/compile-modes.js +237 -0
  226. package/dist/shared/ipc-channels-overlays.d.ts +8 -2
  227. package/dist/shared/ipc-channels-overlays.js +19 -2
  228. package/dist/shared/ipc-channels.d.ts +5 -10
  229. package/dist/shared/ipc-channels.js +17 -14
  230. package/dist/shared/ipc-schemas.d.ts +76 -24
  231. package/dist/shared/ipc-schemas.js +55 -10
  232. package/dist/shared/project-window-types.d.ts +42 -0
  233. package/dist/shared/project-window-types.js +2 -0
  234. package/dist/shared/types.d.ts +43 -17
  235. package/dist/shared/view-ids.d.ts +1 -0
  236. package/dist/shared/view-ids.js +3 -1
  237. package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
  238. package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
  239. package/dist/simulator/assets/device-shell-pxnZZp10.js +431 -0
  240. package/dist/simulator/assets/simulator-Dwp7lnvu.js +10 -0
  241. package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
  242. package/dist/simulator/assets/simulator-ui-B2ddJB1R.js +2 -0
  243. package/dist/simulator/simulator.html +3 -3
  244. package/dist/simulator/temp-files.d.ts +6 -0
  245. package/dist/simulator/temp-files.js +46 -4
  246. package/dist/simulator/types.d.ts +7 -0
  247. package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
  248. package/dist/vscode-workbench/assets/{dist-CMfmHflW.js → dist-CXG-u1VB.js} +3 -3
  249. package/dist/vscode-workbench/assets/{iconv-lite-umd-Bx8CgaZq.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
  250. package/dist/vscode-workbench/assets/{index-CdG6YWhW.js → index-6VJMsaWj.js} +460 -460
  251. package/dist/vscode-workbench/assets/{jschardet-M0qgd2lN.js → jschardet-Ba3D16vt.js} +1 -1
  252. package/dist/vscode-workbench/index.html +1 -1
  253. package/package.json +11 -8
  254. package/dist/renderer/assets/button-D5Q2wkXq.js +0 -2
  255. package/dist/renderer/assets/constants-D7DfVqMp.js +0 -2
  256. package/dist/renderer/assets/constants-D8zsejFq.js +0 -2
  257. package/dist/renderer/assets/dialog-e1TecTOY.js +0 -46
  258. package/dist/renderer/assets/hostSidebarDefault-B3yLfy8j.js +0 -2
  259. package/dist/renderer/assets/index-CsPD68N_.js +0 -5
  260. package/dist/renderer/assets/ipc-channels-overlays-LsDzGAtV.js +0 -2
  261. package/dist/renderer/assets/jsx-runtime-2vXHouwr.css +0 -1
  262. package/dist/renderer/assets/popover-Dix5cm_b.js +0 -2
  263. package/dist/renderer/assets/project-api-k7Oq9iaq.js +0 -2
  264. package/dist/renderer/assets/select-acGb0AmJ.js +0 -2
  265. package/dist/renderer/assets/settings-CQrTMr43.js +0 -2
  266. package/dist/renderer/assets/settings-api-CrUQ9rYV.js +0 -2
  267. package/dist/renderer/assets/settings-tab-bar-Cw1kVIf2.js +0 -2
  268. package/dist/renderer/assets/types-CsEKtFyr.js +0 -2
  269. package/dist/renderer/assets/updateDialog-NsF8223A.js +0 -2
  270. package/dist/renderer/assets/view-api-CLn3rcQE.js +0 -2
  271. package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
  272. package/dist/simulator/assets/device-shell-CmtM1QxP.js +0 -2
  273. package/dist/simulator/assets/device-shell-iFZo1igL.css +0 -1
  274. package/dist/simulator/assets/simulator-CIobn3Mh.js +0 -10
  275. package/dist/simulator/assets/simulator-mini-app-CRe6n9br.js +0 -2
  276. package/dist/simulator/assets/simulator-ui-C_cP602X.js +0 -2
  277. package/dist/vscode-workbench/assets/__vite-browser-external-Bc1vizTf.js +0 -1
@@ -1,338 +1,97 @@
1
1
  import { setupCdpPort, registerDifileScheme, suppressInsecureCspWarnings } from './bootstrap.js';
2
2
  import { installMaxListenersWarningDiagnostic } from './max-listeners-diagnostic.js';
3
+ import { app, session } from 'electron';
4
+ import { rendererDir as defaultRendererDir } from '../utils/paths.js';
5
+ import { createAppServices, registerTrustedWindow } from '../services/app-services.js';
6
+ import { createWindowContextRouter } from '../services/window-contexts/context-router.js';
7
+ import { createInternalDevtoolsWindow } from '../windows/internal-devtools-window/index.js';
8
+ import { registerAppIpc, registerInternalDevtoolsIpc, registerTooltipIpc, registerProjectCreateIpc, registerDevicePickerIpc, registerViewsIpc, } from '../ipc/index.js';
3
9
  import { registerProjectFsIpc } from '../ipc/project-fs.js';
4
- import { app, BrowserWindow, nativeImage, session } from 'electron';
5
- import fs from 'fs';
6
- import path from 'path';
7
- import { HOST_SIDEBAR_DEFAULT_WIDTH } from '../../shared/constants.js';
8
- import { rendererDir as defaultRendererDir, defaultPreloadPath, devtoolsPackageRoot } from '../utils/paths.js';
10
+ import { setupSimulatorSessionPolicy } from '../services/views/simulator-session-policy.js';
11
+ import { setupCompileWorkerStandby } from '../services/compile-standby.js';
9
12
  import { installThemeBackgroundSync } from '../utils/theme.js';
10
- import { createMainWindow, wireMainWindowEvents } from '../windows/main-window/index.js';
11
- import { createInternalDevtoolsWindow } from '../windows/internal-devtools-window/index.js';
12
- import { createGlobalConsoleMirror } from '../services/console-forward/global-console-mirror.js';
13
- import { createGlobalDiagnosticsMirror } from '../services/console-forward/global-diagnostics-mirror.js';
14
13
  import { isAppQuitting } from './lifecycle.js';
15
- import { resolveNativeAppDataKeys, resolveNativeStorageOverview } from './native-overview.js';
16
- // eslint-disable-next-line no-restricted-syntax -- grandfathered(workbench-context): shrink-only
17
- import { createWorkbenchContext } from '../services/workbench-context.js';
18
- import { setupCompileWorkerStandby } from '../services/compile-standby.js';
19
14
  import { loadWorkbenchSettings, applyTheme } from '../services/settings/index.js';
20
- import { installAppMenu } from '../menu/index.js';
21
- import { registerAppIpc, registerInternalDevtoolsIpc, registerTooltipIpc, registerProjectCreateIpc, registerViewsIpc, popoverModule, projectsModule, sessionModule, settingsModule, simulatorModule, } from '../ipc/index.js';
22
- import { startAutomationServer } from '../services/automation/index.js';
23
- import { startMcpServer, setNativeHost, setActiveBridgeId, setNativeOverviewProvider, } from '../services/mcp/index.js';
24
- import { setupSimulatorStorage } from '../services/simulator-storage/index.js';
25
- import { createNetworkForwarder } from '../services/network-forward/index.js';
26
- import { setupSimulatorWxml } from '../services/simulator-wxml/index.js';
27
- import { setupSimulatorAppData } from '../services/simulator-appdata/index.js';
28
- import { setupSimulatorCurrentPage } from '../services/simulator-current-page/index.js';
29
- import { createRenderInspector } from '../services/render-inspect/index.js';
30
- import { setupSimulatorTempFiles } from '../services/simulator-temp-files/index.js';
31
15
  import { SHARED_MINIAPP_PARTITION } from '../services/views/miniapp-partition.js';
32
- import { setupSimulatorSessionPolicy } from '../services/views/simulator-session-policy.js';
33
- import { startWorkbenchCoiServer } from '../services/workbench-coi-server.js';
16
+ import { setupSimulatorTempFiles } from '../services/simulator-temp-files/index.js';
34
17
  import { UpdateManager } from '../services/update/index.js';
35
- import { toDisposable } from '@dimina-kit/electron-deck/main';
18
+ import { toDisposable, DisposableRegistry } from '@dimina-kit/electron-deck/main';
36
19
  import { IpcRegistry } from '../utils/ipc-registry.js';
37
- import { WindowChannel } from '../../shared/ipc-channels.js';
38
- const DEFAULT_MODULES = {
39
- projects: true,
40
- session: true,
41
- simulator: true,
42
- popover: true,
43
- settings: true,
44
- };
45
- const BUILTIN_MODULES = {
46
- projects: projectsModule,
47
- session: sessionModule,
48
- simulator: simulatorModule,
49
- popover: popoverModule,
50
- settings: settingsModule,
51
- };
52
- /**
53
- * Adds `win.webContents` to the context's trusted-sender set and returns a
54
- * Disposable that removes it again.
55
- *
56
- * Trust is reference-counted: registering the SAME window N times keeps it
57
- * trusted until every one of the N returned Disposables has been disposed.
58
- * `trustedWindowSenderIds` is a `Map<webContents.id, refCount>`; each register
59
- * bumps the count, each dispose decrements it, and the window is un-trusted
60
- * only when the count reaches zero.
61
- *
62
- * The window's `closed` event short-circuits the ref-count: it deletes the
63
- * map entry outright (the window is dead, so it must be un-trusted
64
- * immediately regardless of how many Disposables are still outstanding).
65
- * After `closed`, disposing any leftover Disposable is a safe no-op — the
66
- * map entry is already gone, so the decrement finds `undefined` and bails
67
- * without driving the count negative or resurrecting trust.
68
- *
69
- * Each returned Disposable is itself idempotent (`removed` flag), and its
70
- * `closed` listener removes itself once fired so a long-lived context
71
- * doesn't accumulate dead listeners on closed windows.
72
- */
73
- function registerTrustedWindow(context, win) {
74
- const senderId = win.webContents.id;
75
- const counts = context.trustedWindowSenderIds;
76
- counts.set(senderId, (counts.get(senderId) ?? 0) + 1);
77
- let removed = false;
78
- const onClosed = () => {
79
- // The window is gone: zero the ref-count for this sender id outright,
80
- // regardless of any other outstanding registrations for the same window.
81
- counts.delete(senderId);
82
- win.removeListener('closed', onClosed);
83
- };
84
- function remove() {
85
- if (removed)
86
- return;
87
- removed = true;
88
- win.removeListener('closed', onClosed);
89
- const count = counts.get(senderId);
90
- // `undefined` → the entry was already cleared (e.g. by `closed` or by a
91
- // prior sibling's decrement that hit zero). Nothing to do — never go
92
- // negative, never resurrect trust.
93
- if (count === undefined)
94
- return;
95
- if (count <= 1)
96
- counts.delete(senderId);
97
- else
98
- counts.set(senderId, count - 1);
99
- }
100
- win.once('closed', onClosed);
101
- return toDisposable(remove);
102
- }
103
- /** Parse --auto, --auto-port, --project from process.argv. */
104
- function parseAutoArgs() {
105
- const argv = process.argv;
106
- let auto = false;
107
- let autoPort = 9420;
108
- let projectPath = '';
109
- for (let i = 0; i < argv.length; i++) {
110
- if (argv[i] === 'auto' || argv[i] === '--auto')
111
- auto = true;
112
- if ((argv[i] === '--auto-port' || argv[i] === '--auto_port') && argv[i + 1]) {
113
- const parsed = parseInt(argv[i + 1], 10);
114
- // 0 → OS-assigned free port (used by parallel e2e workers)
115
- if (Number.isFinite(parsed) && parsed >= 0)
116
- autoPort = parsed;
117
- }
118
- if (argv[i] === '--project' && argv[i + 1]) {
119
- projectPath = argv[i + 1];
120
- }
121
- }
122
- return { auto, autoPort, projectPath };
123
- }
124
- function resolveModules(config) {
125
- return {
126
- ...DEFAULT_MODULES,
127
- ...config.modules,
128
- };
129
- }
130
- async function disposeContext(ctx) {
131
- await ctx.workspace.closeProject();
132
- await ctx.registry.dispose().catch((err) => {
133
- console.warn('[workbench] dispose registry encountered errors:', err);
134
- });
135
- }
136
- function createConfiguredMainWindow(config, rendererDir) {
137
- const mainWindow = createMainWindow({
138
- title: config.appName ?? 'Dimina DevTools',
139
- indexHtml: path.join(rendererDir, 'entries/main/index.html'),
140
- width: config.window?.width,
141
- height: config.window?.height,
142
- minWidth: config.window?.minWidth,
143
- minHeight: config.window?.minHeight,
144
- autoShow: config.window?.autoShow,
145
- });
146
- // Set window/taskbar icon if provided (Linux/Windows; macOS uses app bundle icon)
147
- if (config.icon) {
148
- const icon = nativeImage.createFromPath(config.icon);
149
- if (!icon.isEmpty())
150
- mainWindow.setIcon(icon);
151
- }
152
- return mainWindow;
153
- }
154
- function createContext(config, mainWindow, rendererDir) {
155
- return createWorkbenchContext({
156
- mainWindow,
157
- adapter: config.adapter,
158
- preloadPath: config.preloadPath ?? defaultPreloadPath,
159
- rendererDir,
160
- appName: config.appName,
161
- apiNamespaces: config.apiNamespaces,
162
- fileTypes: config.fileTypes,
163
- brandingProvider: config.brandingProvider,
164
- // The host-supplied ProjectsProvider / template types in `shared/types`
165
- // are structurally compatible with the main-process equivalents —
166
- // these casts are safe; we re-narrow at the workspace-service /
167
- // create-project-service boundary.
168
- projectsProvider: config.projectsProvider,
169
- projectTemplates: config.projectTemplates,
170
- builtinTemplates: config.builtinTemplates,
171
- customCreateProjectDialog: config.customCreateProjectDialog,
172
- // No cast needed here: the hook receives the project record, so the
173
- // main-process `Project` the context hands it satisfies the structural
174
- // `EditableProject` the config declares.
175
- customEditProjectDialog: config.customEditProjectDialog,
176
- onBeforeOpenProject: config.onBeforeOpenProject,
177
- });
178
- }
179
- function registerBuiltinModules(config, context) {
180
- const modules = resolveModules(config);
181
- Object.keys(modules).forEach((moduleId) => {
182
- if (modules[moduleId])
183
- context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
184
- });
185
- }
20
+ import { createLauncherWindow } from './project-window.js';
21
+ import { createProjectListSurface } from './project-list-surface.js';
22
+ import { createWorkbenchWindowManager } from './workbench-window.js';
23
+ import { registerModuleIpc, setupWindowModules } from './workbench-modules.js';
24
+ import { registerOpenProjectWindowIpc } from './open-project-ipc.js';
25
+ import { createUiExtensionTargets } from './ui-extension-targets.js';
26
+ import { installGlobalMirrors } from './global-mirrors.js';
27
+ import { installHostSidebarDefault } from './host-sidebar-default.js';
28
+ import { installMenu } from './menu-setup.js';
29
+ import { createOpenForMcp, createTargetForMcp, noteActiveMcpWindowChanged, setActiveMcpWindowResolver, } from '../services/mcp/index.js';
30
+ import { setupAutomation, setupMcp } from './servers.js';
31
+ import { enableDevRendererAutoReload } from './window-events.js';
32
+ import { wireMainWindowEvents } from '../windows/main-window/index.js';
33
+ export { setupWindowModules } from './workbench-modules.js';
186
34
  /**
187
- * Build the hand-written narrow `MenuContext` a host menu builder receives
188
- * explicit construction (not clone+delete), so the runtime object carries
189
- * EXACTLY the contract members and nothing else. Every member is a lazy
190
- * closure over the live context: a host monkey-patch of
191
- * `context.workspace.openProject` (the documented permission-gate pattern)
192
- * still intercepts calls made through this menu surface.
35
+ * Registers the workbench's IPC surface against the router, so each message is
36
+ * answered with the context of the window it came from.
193
37
  */
194
- function toMenuContext(context) {
195
- return {
196
- appName: context.appName,
197
- workspace: {
198
- hasActiveSession: () => context.workspace.hasActiveSession(),
199
- getProjectPath: () => context.workspace.getProjectPath(),
200
- openProject: (projectPath) => context.workspace.openProject(projectPath),
201
- closeProject: () => context.workspace.closeProject(),
202
- getSession: () => context.workspace.getSession(),
203
- },
204
- openSettings: () => context.openSettings(),
205
- notify: {
206
- projectStatus: (payload) => context.notify.projectStatus(payload),
207
- windowNavigateBack: () => context.notify.windowNavigateBack(),
208
- },
209
- };
210
- }
211
- function installMenu(config, mainWindow, context) {
212
- // Menu: use host-provided builder or fall back to default
213
- if (config.menuBuilder) {
214
- config.menuBuilder(mainWindow, toMenuContext(context));
215
- }
216
- else {
217
- installAppMenu(context);
218
- }
219
- }
220
- async function setupAutomation(instance) {
221
- // Start automation server if --auto flag is present
222
- const autoArgs = parseAutoArgs();
223
- if (autoArgs.auto) {
224
- const server = await startAutomationServer(instance.context, autoArgs.autoPort);
225
- instance.automationServer = server;
226
- instance.context.registry.add(() => server.close());
227
- // Stable, parseable line for e2e harnesses that scrape stdout.
228
- console.log(`[automation] listening on ws://127.0.0.1:${server.port}`);
229
- }
230
- }
231
- function setupMcp(context) {
232
- const settings = loadWorkbenchSettings();
233
- if (!settings.mcp.enabled)
234
- return null;
235
- const cdpPortSwitch = app.commandLine.getSwitchValue('remote-debugging-port');
236
- const cdpPort = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
237
- return startMcpServer(cdpPort, settings.mcp.port, {
238
- workspace: context.workspace,
239
- sessionStatus: context.sessionStatus,
240
- compileLogs: context.compileLogBuffer,
241
- // The renderer owns the open path (mounting ProjectRuntime compiles and
242
- // attaches the simulator), so project_open pushes it there.
243
- requestOpenInUi: (p) => context.notify.windowOpenProject(p),
244
- requestNavigateBack: () => context.notify.windowNavigateBack(),
245
- });
246
- }
247
- function wireAppWindowEvents(config, instance, isOnProjectScreen) {
248
- const { mainWindow, context } = instance;
249
- // In-flight guard: `closeProject()` is async, and the window stays open
250
- // (preventDefault'd) while it runs — a second close click during that window
251
- // would re-enter and tear the same session down twice. Swallow re-entrancy.
252
- let closing = false;
253
- return wireMainWindowEvents(mainWindow, {
254
- context,
255
- onResize: () => context.views.repositionAll(),
256
- onClose: async (e) => {
257
- // A real application quit (⌘Q / menu "Quit" / app.quit()) fires
258
- // `before-quit` first, then this window's `close`. Let it through so the
259
- // app actually exits — do NOT convert it into "close the project".
260
- // Without this, `hasActiveSession()` would be true and the quit gets
261
- // swallowed into closeProject(), so the app can never be quit while a
262
- // project is open.
263
- if (isAppQuitting())
264
- return;
265
- // A close arriving while a project teardown is already in flight (the
266
- // user rapid-double-clicked the close button) MUST keep the window open.
267
- // This guard runs BEFORE hasActiveSession() on purpose: `closeProject()`
268
- // → `disposeSession()` synchronously nulls the active session before it
269
- // finishes awaiting `session.close()`, so by the time the second close
270
- // arrives `hasActiveSession()` is already false. With the old guard order
271
- // that second close fell straight through the hasActiveSession() check
272
- // WITHOUT `preventDefault()`, so Electron destroyed the last window →
273
- // `window-all-closed` → `app.quit()`, quitting the whole app on a
274
- // double-click. Swallow the re-entrant close and keep the window.
275
- if (closing) {
276
- e.preventDefault();
277
- return;
278
- }
279
- // Keep the app alive on close whenever EITHER a live compiled session
280
- // exists OR the renderer reports it is on a project screen. The renderer
281
- // enters the project screen (and reports it) before openProject resolves,
282
- // so a FAILED open — invalid/non-existent project, no session ever
283
- // created — still parks the renderer there showing the compile-failed
284
- // overlay. Keying off `hasActiveSession()` alone let that close fall
285
- // through with no preventDefault → the last window is destroyed →
286
- // `window-all-closed` → `app.quit()`, quitting the whole app instead of
287
- // returning to the list. The two signals answer one question ("is the
288
- // user inside a project, so close means back-to-list?"); the renderer's
289
- // screen is the authority and hasActiveSession is a resource-safety belt
290
- // (never destroy the window while a live session runs behind it).
291
- if (!context.workspace.hasActiveSession() && !isOnProjectScreen())
292
- return;
293
- // Close button while inside a project: stay in the workbench and surface
294
- // the project list. Tear down only the session — do NOT dispose
295
- // `context.registry`, which owns every IPC handler (projects, dialog,
296
- // settings…). Disposing it would leave the renderer alive but unable to
297
- // invoke anything, so subsequent clicks on Import/etc. would raise
298
- // `No handler registered for ...`.
299
- e.preventDefault();
300
- closing = true;
301
- try {
302
- if (config.onBeforeClose) {
303
- await config.onBeforeClose(instance);
304
- }
305
- await context.workspace.closeProject();
306
- context.notify.windowNavigateBack();
307
- }
308
- finally {
309
- closing = false;
310
- }
311
- },
312
- });
313
- }
314
- function enableDevRendererAutoReload(rendererDir) {
315
- // Auto-reload renderer windows when dist files change during development
316
- if (app.isPackaged) {
317
- return toDisposable(() => { });
318
- }
319
- let reloadTimer = null;
320
- const watcher = fs.watch(rendererDir, { recursive: true }, () => {
321
- if (reloadTimer)
322
- clearTimeout(reloadTimer);
323
- reloadTimer = setTimeout(() => {
324
- for (const win of BrowserWindow.getAllWindows()) {
325
- win.webContents.reload();
326
- }
327
- }, 300);
328
- });
329
- return toDisposable(() => {
330
- if (reloadTimer) {
331
- clearTimeout(reloadTimer);
332
- reloadTimer = null;
333
- }
334
- watcher.close();
335
- });
38
+ function registerWorkbenchIpc(config, router, context, mainWindow, appRegistry) {
39
+ appRegistry.add(registerAppIpc(router));
40
+ // Sandboxed project file-system IPC (the renderer-side project:fs:* surface).
41
+ appRegistry.add(registerProjectFsIpc(router));
42
+ // Standalone internal (app-wide) DevTools debug window controller — the
43
+ // independent floating CDP panel that debugs the whole Electron app (as
44
+ // opposed to the right-panel CDP, which inspects only the user's
45
+ // mini-program). Assembled before the IPC handler below so a request
46
+ // arriving right after boot always finds it.
47
+ // `isAppQuitting` lets the controller stop intercepting 'close' during a
48
+ // real quit — its habitual preventDefault()+hide() would otherwise cancel
49
+ // the quit itself and strand the process with a hidden window.
50
+ // One per window (each debugs its own renderer), so it is parked on the
51
+ // window's context registry, not the app's.
52
+ context.internalDevtoolsWindow = createInternalDevtoolsWindow(mainWindow, { isAppQuitting });
53
+ context.registry.add(toDisposable(() => context.internalDevtoolsWindow?.dispose()));
54
+ // Unconditional (not a toggleable BUILTIN_MODULES entry): it's core dev
55
+ // tooling, not a host-configurable feature.
56
+ appRegistry.add(registerInternalDevtoolsIpc(router));
57
+ // Unconditional (not a toggleable BUILTIN_MODULES entry): the tooltip
58
+ // overlay is core UI chrome (every toolbar in this app relies on it), not a
59
+ // host-configurable feature.
60
+ appRegistry.add(registerTooltipIpc(router));
61
+ // Unconditional: the project-create dialog is core UI chrome (the built-in
62
+ // "新建项目" flow every host falls back to), not a host-configurable feature.
63
+ appRegistry.add(registerProjectCreateIpc(router));
64
+ // Unconditional for the same reason as the tooltip above: the device picker
65
+ // is an overlay WebContentsView the simulator toolbar opens, and `ctx.views`
66
+ // exists regardless of the `simulator` module toggle. A host that turns the
67
+ // simulator off simply never renders the trigger.
68
+ appRegistry.add(registerDevicePickerIpc(router));
69
+ // Unconditional (not a toggleable BUILTIN_MODULES entry): placement/host-
70
+ // slot IPC has no real dependency on the simulator module — `ctx.views`
71
+ // (ViewManager) is constructed unconditionally regardless of
72
+ // `modules.simulator`, and host-sidebar in particular lives on the
73
+ // project-list page, unrelated to the simulator webview. Every renderer
74
+ // entry point also blocks its first render on `AllocatePlacementGeneration`
75
+ // (see renderer-placement-generation.ts) — gating any of this behind the
76
+ // simulator toggle would strand a host that disables it on the fatal
77
+ // boot-failure page, or leave placement silently non-functional.
78
+ appRegistry.add(registerViewsIpc(router));
79
+ // Referer/CORS webRequest policy for the simulator runtime's sessions (shared
80
+ // fallback + every per-project partition). Registered into the app registry so
81
+ // its configurator + per-session listeners are torn down with the app —
82
+ // re-creating the app never leaks a duplicate configurator, and a window
83
+ // closing must not strip the policy off the sessions other windows still use.
84
+ appRegistry.add(setupSimulatorSessionPolicy());
85
+ // One process-wide listener that re-syncs every window's native
86
+ // backgroundColor on theme change windows otherwise keep the stale
87
+ // creation-time color (see installThemeBackgroundSync).
88
+ appRegistry.add(installThemeBackgroundSync());
89
+ // Warm-standby compile worker: only meaningful for the devkit-backed
90
+ // default adapter (a host-injected adapter has no devkit fork to adopt the
91
+ // spare). Registered into the registry so the spare dies with the app.
92
+ if (!config.adapter)
93
+ appRegistry.add(setupCompileWorkerStandby(context));
94
+ registerModuleIpc(config, router, appRegistry);
336
95
  }
337
96
  /**
338
97
  * Pre-ready bootstrap side effects (app name, CDP port, CSP suppression,
@@ -367,10 +126,11 @@ export function runDevtoolsBootstrap(config = {}) {
367
126
  catch { /* electron stub in tests */ }
368
127
  }
369
128
  /**
370
- * Domain runtime assembly — the post-whenReady body. Builds the main window +
371
- * context, registers IPC modules, stands up simulator/storage/CDP/native-host
372
- * services, and returns the fat {@link WorkbenchAppInstance}. Extracted so the
373
- * v2 `RuntimeBackend.assemble` reuses the exact same body (parity by shared
129
+ * Domain runtime assembly — the post-whenReady body. Builds the app-wide
130
+ * services and the context router, opens the project window, registers IPC
131
+ * against the router, stands up simulator/storage/CDP/native-host services, and
132
+ * returns the fat {@link WorkbenchAppInstance}. Extracted so the v2
133
+ * `RuntimeBackend.assemble` reuses the exact same body (parity by shared
374
134
  * implementation, not behavioural re-creation).
375
135
  */
376
136
  export async function createDevtoolsRuntime(config = {},
@@ -390,123 +150,159 @@ onInstanceCreated) {
390
150
  await app.whenReady();
391
151
  applyTheme(loadWorkbenchSettings().theme);
392
152
  const rendererDir = config.rendererDir ?? defaultRendererDir;
393
- const mainWindow = createConfiguredMainWindow(config, rendererDir);
394
- const context = createContext(config, mainWindow, rendererDir);
395
- // Anchor the main window's renderer as the first Connection. Resources
396
- // scoped to the main webContents (acquired by later wiring) tear down with
397
- // it; see packages/electron-deck/docs/foundation.md (teardown paths).
398
- context.connections.acquire(mainWindow.webContents);
399
- context.registry.add(registerAppIpc(context));
400
- // Sandboxed project file-system IPC (the renderer-side project:fs:* surface).
401
- context.registry.add(registerProjectFsIpc(context));
402
- // Standalone internal (app-wide) DevTools debug window controller — the
403
- // independent floating CDP panel that debugs the whole Electron app (as
404
- // opposed to the right-panel CDP, which inspects only the user's
405
- // mini-program). Assembled before the IPC handler below so a request
406
- // arriving right after boot always finds it.
407
- // `isAppQuitting` lets the controller stop intercepting 'close' during a
408
- // real quit its habitual preventDefault()+hide() would otherwise cancel
409
- // the quit itself and strand the process with a hidden window.
410
- context.internalDevtoolsWindow = createInternalDevtoolsWindow(mainWindow, { isAppQuitting });
411
- context.registry.add(toDisposable(() => context.internalDevtoolsWindow?.dispose()));
412
- // Unconditional (not a toggleable BUILTIN_MODULES entry): it's core dev
413
- // tooling, not a host-configurable feature.
414
- context.registry.add(registerInternalDevtoolsIpc(context));
415
- // Unconditional (not a toggleable BUILTIN_MODULES entry): the tooltip
416
- // overlay is core UI chrome (every toolbar in this app relies on it), not a
417
- // host-configurable feature.
418
- context.registry.add(registerTooltipIpc(context));
419
- // Unconditional: the project-create dialog is core UI chrome (the built-in
420
- // "新建项目" flow every host falls back to), not a host-configurable feature.
421
- context.registry.add(registerProjectCreateIpc(context));
422
- // Unconditional (not a toggleable BUILTIN_MODULES entry): placement/host-
423
- // slot IPC has no real dependency on the simulator module — `ctx.views`
424
- // (ViewManager) is constructed unconditionally regardless of
425
- // `modules.simulator`, and host-sidebar in particular lives on the
426
- // project-list page, unrelated to the simulator webview. Every renderer
427
- // entry point also blocks its first render on `AllocatePlacementGeneration`
428
- // (see renderer-placement-generation.ts) — gating any of this behind the
429
- // simulator toggle would strand a host that disables it on the fatal
430
- // boot-failure page, or leave placement silently non-functional.
431
- context.registry.add(registerViewsIpc(context));
432
- // Referer/CORS webRequest policy for the simulator runtime's sessions (shared
433
- // fallback + every per-project partition). Registered into the context
434
- // registry so its configurator + per-session listeners are torn down with the
435
- // context — re-creating the app never leaks a duplicate configurator.
436
- context.registry.add(setupSimulatorSessionPolicy());
437
- // One process-wide listener that re-syncs every window's native
438
- // backgroundColor on theme change — windows otherwise keep the stale
439
- // creation-time color (see installThemeBackgroundSync).
440
- context.registry.add(installThemeBackgroundSync());
441
- // Warm-standby compile worker: only meaningful for the devkit-backed
442
- // default adapter (a host-injected adapter has no devkit fork to adopt the
443
- // spare). Registered into the registry so the spare dies with the context.
444
- if (!config.adapter)
445
- context.registry.add(setupCompileWorkerStandby(context));
446
- registerBuiltinModules(config, context);
447
- // Global console mirror: while the standalone
448
- // internal DevTools window is open, mirror every guest console entry
449
- // (service + render, UNFILTERED — see global-console-mirror.ts) into it —
450
- // each open replays the forwarder's current history buffer first (see that
451
- // module's doc comment for why the subscribe lifecycle is gated on
452
- // onHostChanged rather than subscribed once at construction time).
453
- // `context.consoleForwarder` is assembled by the simulator module's
454
- // installBridgeRouter just above; absent only when that builtin module was
455
- // disabled via config. The Console panel shows the INSPECTED target's own
456
- // console (mainWindow, per internal-devtools-window.ts's
457
- // setDevToolsWebContents relationship) — NOT the front-end host page's
458
- // console, so `target` is always mainWindow.webContents, never the hostWc
459
- // onHostChanged hands the mirror.
460
- if (context.consoleForwarder && context.internalDevtoolsWindow) {
461
- const consoleMirror = createGlobalConsoleMirror(context.consoleForwarder, mainWindow.webContents, context.internalDevtoolsWindow.onHostChanged,
462
- // CDP transport (never executeJavaScript) — see the mirror's inject()
463
- // doc for the setDevToolsWebContents + external-CDP double-attach hang.
464
- { broker: context.cdpSessionBroker });
465
- context.registry.add(toDisposable(() => consoleMirror.dispose()));
466
- }
467
- // Global diagnostics mirror (same wiring, same INSPECTED-side
468
- // target rationale as the console mirror above): every diagnostic —
469
- // including `audience:'internal'` ones console-forward's own service-host
470
- // injection now skips (see compile-standby.ts / index.ts's handleDiagnostic
471
- // gate) — surfaces here instead of vanishing. `context.diagnostics` is
472
- // assembled alongside `context.consoleForwarder` by the same
473
- // installBridgeRouter call.
474
- if (context.diagnostics && context.internalDevtoolsWindow) {
475
- const diagnosticsMirror = createGlobalDiagnosticsMirror(context.diagnostics, mainWindow.webContents, context.internalDevtoolsWindow.onHostChanged, { broker: context.cdpSessionBroker });
476
- context.registry.add(toDisposable(() => diagnosticsMirror.dispose()));
477
- }
153
+ // App-wide, built once: the project list, the templates, the trusted-sender
154
+ // ledger and the host's simulator APIs are properties of the application, not
155
+ // of any single window.
156
+ const appServices = createAppServices({
157
+ // The host-supplied ProjectsProvider / template types in `shared/types`
158
+ // are structurally compatible with the main-process equivalents —
159
+ // these casts are safe; we re-narrow at the workspace-service /
160
+ // create-project-service boundary.
161
+ projectsProvider: config.projectsProvider,
162
+ projectTemplates: config.projectTemplates,
163
+ builtinTemplates: config.builtinTemplates,
164
+ });
165
+ const router = createWindowContextRouter();
166
+ // App-lifetime registry. `ipcMain.handle` is a process-wide singleton per
167
+ // channel, so the IPC surface is registered exactly once and answers
168
+ // whichever window a message came from. Keeping it OFF the launcher
169
+ // context's registry is what lets the user close the project list without
170
+ // stripping every handler out from under the workbench windows still open.
171
+ const appRegistry = new DisposableRegistry();
172
+ const launcher = createLauncherWindow({ config, rendererDir, appServices, router });
173
+ const { window: mainWindow, context } = launcher;
174
+ const projectList = createProjectListSurface({ window: mainWindow, context });
175
+ registerWorkbenchIpc(config, router, context, mainWindow, appRegistry);
176
+ // No `setupWindowModules` here: the only per-window module is the simulator's
177
+ // bridge router, and the project list runs no mini-app — it has no simulator,
178
+ // no service host and no page stack. Installing one here would also claim the
179
+ // process-global `dmb:*` invoke channels before the first workbench window
180
+ // could. `installGlobalMirrors` skips the console/diagnostics mirrors when the
181
+ // router never ran, so the list window keeps its own internal DevTools.
182
+ installGlobalMirrors(context, mainWindow);
478
183
  // Wire the simulator-side difile:// protocol handler + temp-file IPC
479
184
  // before host onSetup so any host-driven simulator boot sees the
480
185
  // protocol live. The module installs its own narrow sender-policy
481
186
  // (simulator-session-only) — see file header — because the default
482
187
  // workbench policy intentionally rejects the simulator <webview>.
483
188
  const simSession = session.fromPartition(SHARED_MINIAPP_PARTITION);
484
- context.registry.add(setupSimulatorTempFiles(simSession));
485
- installMenu(config, mainWindow, context);
486
- // Gated custom-IPC surface for the host. Bound to ctx.senderPolicy so
487
- // host channels go through the same gateway as built-in IPC, and
488
- // registered into ctx.registry so its handlers are torn down with the
489
- // context.
490
- const hostIpc = new IpcRegistry(context.senderPolicy);
491
- context.registry.add(hostIpc);
189
+ appRegistry.add(setupSimulatorTempFiles(simSession));
190
+ // Gated custom-IPC surface for the host. Admits any sender the router places
191
+ // in a live window, so a host channel invoked from a workbench window is
192
+ // answered the same as one invoked from the list binding it to the
193
+ // launcher's own policy would reject every workbench renderer.
194
+ const hostIpc = new IpcRegistry((sender) => router.resolve(sender) !== null);
195
+ appRegistry.add(hostIpc);
196
+ // Host-registered simulator UI extensions are app-level, but an extension can
197
+ // only live in a project window, so the ledger keeps one copy per window.
198
+ const uiExtensions = createUiExtensionTargets({
199
+ projectWindows: () => workbenchWindows.list().map((pw) => pw.context),
200
+ activeWindow: () => workbenchWindows.activeContext(),
201
+ });
202
+ // The open-project IPC is registered below, well before `config.onSetup` is
203
+ // awaited, so a renderer that races ahead can ask for a project window while
204
+ // the host is still wiring its extensions up. Those opens park on `ready`
205
+ // instead of producing a window the host never got to extend.
206
+ let markReady;
207
+ const ready = new Promise((resolve) => { markReady = resolve; });
208
+ // The parked open reports an onSetup failure to whoever asked for the
209
+ // window; this keeps the rejection handled when nobody ever asks.
210
+ void ready.catch(() => { });
211
+ const workbenchWindows = createWorkbenchWindowManager({
212
+ config,
213
+ rendererDir,
214
+ appServices,
215
+ router,
216
+ ready,
217
+ // Not wrapped in try/catch the way `onBeforeClose` is: this hook runs on
218
+ // the way IN, so a host that could not configure the window has no window
219
+ // worth keeping — the manager tears it down and the error reaches the
220
+ // caller.
221
+ setupProjectWindow: async (opened, project) => {
222
+ await config.setupProjectWindow?.(instance, {
223
+ path: project.path,
224
+ name: project.name,
225
+ window: opened.window,
226
+ context: opened.context,
227
+ });
228
+ },
229
+ setupWindowModules: (ctx) => {
230
+ setupWindowModules(config, ctx);
231
+ uiExtensions.attachTo(ctx);
232
+ },
233
+ // MCP's CDP connections are aimed at the active project window and have to
234
+ // follow it when the user moves.
235
+ onActiveContextChanged: noteActiveMcpWindowChanged,
236
+ onBeforeClose: async (closing, project) => {
237
+ try {
238
+ await config.onBeforeClose?.(instance, {
239
+ path: project.path,
240
+ name: project.name,
241
+ window: closing.window,
242
+ context: closing.context,
243
+ });
244
+ }
245
+ catch (err) {
246
+ // The host gets to react to the close, not to cancel it: revealing the
247
+ // list window below is what keeps something on screen, and it must not
248
+ // be skipped because host code threw.
249
+ console.error('[workbench] host onBeforeClose hook failed:', err);
250
+ }
251
+ // The closing window keeps its place in the manager's list until its
252
+ // teardown finishes, so "the last project" means no OTHER window is
253
+ // open. The list window may be hidden behind it (see the close handler
254
+ // below) — leaving it hidden strands the user with a running app and
255
+ // nothing on screen.
256
+ if (workbenchWindows.list().every((pw) => pw === closing))
257
+ projectList.revealProjectList();
258
+ },
259
+ });
260
+ // Menu, automation and MCP drive whatever project the user is working in, so
261
+ // they read the active WORKBENCH context, resolved per call — never the list
262
+ // window, which owns no session. With no project open they fall back to the
263
+ // launcher context, whose `hasActiveSession()` is false, while MCP's CDP
264
+ // targets and native-host state live in the window itself: null instead.
265
+ const activeProjectContext = () => workbenchWindows.activeContext() ?? context;
266
+ setActiveMcpWindowResolver(() => workbenchWindows.activeContext());
267
+ appRegistry.add(toDisposable(() => setActiveMcpWindowResolver(() => null)));
268
+ installMenu(config, mainWindow, activeProjectContext, projectList.revealProjectList);
269
+ appRegistry.add(registerOpenProjectWindowIpc(router, (project) => workbenchWindows.open(project)));
492
270
  const instance = {
493
271
  mainWindow,
494
272
  context,
495
273
  ipc: hostIpc,
274
+ // `awaitReady: false`: `ready` only resolves once `config.onSetup` itself
275
+ // settles (`markReady(setup)` below), so a host that calls
276
+ // `instance.openProjectWindow` FROM WITHIN its own `onSetup` would
277
+ // otherwise deadlock — `open()` awaiting `ready` while `ready` awaits the
278
+ // very `onSetup` call that is awaiting `open()`. The renderer-facing IPC
279
+ // path (`registerOpenProjectWindowIpc` above) keeps the default: a
280
+ // renderer racing ahead of `onSetup` still has to park.
281
+ openProjectWindow: (project) => workbenchWindows.open(project, { awaitReady: false }),
282
+ projectWindows: () => workbenchWindows.list(),
283
+ disposeViews: () => {
284
+ for (const ctx of router.list())
285
+ ctx.views.disposeAll();
286
+ },
496
287
  // Return the registry wrapper, not the raw disposable: disposing the
497
288
  // wrapper splices the registry entry out AND drives the underlying
498
289
  // teardown, so a single dispose leaves no dead entry behind.
499
- registerTrustedWindow: (win) => context.registry.add(registerTrustedWindow(context, win)),
500
- registerSimulatorApi: (name, handler) => context.registry.add(toDisposable(context.simulatorApis.register(name, handler))),
501
- registerSimulatorUiExtension: (registration) => {
502
- const extension = context.simulatorUiExtensions.register(registration);
503
- const owned = context.registry.add(extension);
504
- return {
505
- dispose: () => owned.dispose(),
506
- invoke: (method, params) => extension.invoke(method, params),
507
- };
290
+ // App-level: the trusted-sender ledger and the simulator API map are
291
+ // shared by every window, so they outlive any one of them.
292
+ registerTrustedWindow: (win) => appRegistry.add(registerTrustedWindow(appServices, win)),
293
+ // Simulator APIs live in the ONE registry AppServices owns, which every
294
+ // window's context points at, so a single registration serves every
295
+ // window — including windows opened later, since the service host reads
296
+ // the registered names off it when it spawns. The disposer therefore
297
+ // belongs to the app: parking it on a window would let that window's close
298
+ // delete the handler out from under every other open window.
299
+ registerSimulatorApi: (name, handler) => appRegistry.add(toDisposable(appServices.simulatorApis.register(name, handler))),
300
+ registerSimulatorUiExtension: (registration) => uiExtensions.register(registration),
301
+ dispose: async () => {
302
+ await workbenchWindows.disposeAll();
303
+ await launcher.dispose();
304
+ await appRegistry.dispose();
508
305
  },
509
- dispose: () => disposeContext(context),
510
306
  };
511
307
  onInstanceCreated?.(instance);
512
308
  // Built-in simulator APIs: devtools-supplied wx.* implementations that run
@@ -515,50 +311,17 @@ onInstanceCreated) {
515
311
  instance.registerSimulatorApi('login', async () => {
516
312
  return 'hello';
517
313
  });
518
- // devtools' own default content for the host-sidebar slot: a narrow icon
519
- // rail (logo + 小程序/小游戏 toggle) filtering the project-list grid by
520
- // `Project.type`. Loaded BEFORE `config.onSetup` runs below, so any
521
- // downstream `loadURL`/`loadFile` call inside onSetup necessarily happens
522
- // later and naturally supersedes this default via plain chronological
523
- // ordering — no override flag needed.
524
- context.registry.add(context.views.hostSidebar.onMessage('project-category-selected', (payload) => {
525
- const category = payload?.category;
526
- if (category !== 'miniprogram' && category !== 'minigame')
527
- return;
528
- context.notify.hostSidebarCategorySelected(category);
529
- }));
530
- context.views.hostSidebar
531
- .loadFile(path.join(rendererDir, 'entries/host-sidebar-default/index.html'))
532
- .catch((err) => {
533
- console.warn('[workbench] failed to load host-sidebar default content:', err);
534
- });
535
- // Seed the notified width with the rail's own known intrinsic width
536
- // (`HOST_SIDEBAR_DEFAULT_WIDTH`) instead of leaving it at 0 until the
537
- // rail's in-page advertiser reports back. That report can never arrive on
538
- // its own: the placeholder above only becomes "desired visible" once a
539
- // nonzero width is known (project-list-screen.tsx), attach only happens
540
- // for a "desired visible" view (placement-reconciler.ts), and the rail's
541
- // advertiser (view-anchor's measure-loop.ts) only ever fires via
542
- // `requestAnimationFrame`, which Chromium never schedules for a
543
- // WebContentsView that was never attached — a structural deadlock, not a
544
- // slow-to-resolve race. `setWidthMode({fixed})` pushes the seed
545
- // synchronously (no attach/rAF dependency) and, critically, retains it in
546
- // `getHostSidebarWidth()` too, so a renderer that mounts and pulls before
547
- // any push replays the real seed instead of a stale 0. Flipping back to
548
- // 'auto' immediately after, before anything has attached or advertised, is
549
- // a same-tick no-op past the seed (`setExtentMode`'s reapply branch only
550
- // fires once something has actually been advertised) — it leaves the slot
551
- // in 'auto' for whichever content ends up loaded (this rail's own later
552
- // report, or a downstream replacement's), not pinned.
553
- context.views.hostSidebar.setWidthMode({ fixed: HOST_SIDEBAR_DEFAULT_WIDTH });
554
- context.views.hostSidebar.setWidthMode('auto');
555
- if (config.onSetup) {
556
- await config.onSetup(instance);
557
- }
314
+ installHostSidebarDefault(context, rendererDir);
315
+ // Run inside an async IIFE so a hook that throws SYNCHRONOUSLY still settles
316
+ // `ready` leaving it pending would park every queued open forever, and
317
+ // `disposeAll()` waits on those queues.
318
+ const setup = (async () => { await config.onSetup?.(instance); })();
319
+ markReady(setup);
320
+ await setup;
558
321
  if (config.updateChecker) {
559
322
  instance.updateManager = new UpdateManager({
560
323
  checker: config.updateChecker,
561
- showUpdatePanel: (info) => context.views.showUpdateDialog(info),
324
+ showUpdatePanel: (info) => projectList.showUpdate(info),
562
325
  notifyDownloadProgress: (percent) => context.views.notifyUpdateDownloadProgress(percent),
563
326
  hideUpdatePanel: () => context.views.hideUpdateDialog(),
564
327
  senderPolicy: context.senderPolicy,
@@ -566,219 +329,60 @@ onInstanceCreated) {
566
329
  initialDelay: config.updateOptions?.initialDelay,
567
330
  getCurrentVersion: config.updateOptions?.getCurrentVersion,
568
331
  });
569
- context.registry.add(() => instance.updateManager.dispose());
332
+ appRegistry.add(() => instance.updateManager.dispose());
570
333
  }
571
- await setupAutomation(instance);
572
- const mcp = setupMcp(context);
334
+ const automation = await setupAutomation(instance, activeProjectContext, router);
335
+ if (automation)
336
+ appRegistry.add(automation);
337
+ const mcp = setupMcp(createTargetForMcp({
338
+ list: () => workbenchWindows.list(),
339
+ activeContext: activeProjectContext,
340
+ // `close()` (not `destroy()`) so the window's own close handling runs the
341
+ // same teardown a user-driven close does.
342
+ close: (window) => { if (!window.isDestroyed())
343
+ window.close(); },
344
+ }), createOpenForMcp(workbenchWindows));
573
345
  if (mcp)
574
- context.registry.add(mcp);
575
- // Resolve the active project's appId. Shared by the storage panel filter
576
- // and the native-host WXML/element-inspect services (which scope the
577
- // active render guest by appId).
578
- const getActiveAppId = () => {
579
- const session = context.workspace.getSession();
580
- const appInfo = session?.appInfo;
581
- return appInfo?.appId ?? null;
582
- };
583
- // Native-host: the real mini-app page runs in a nested render-host
584
- // <webview> guest, not the localhost:7788 shell. Point the MCP
585
- // `simulator` CDP target at the active render guest and keep it following
586
- // the visible page across navigation/tab switches. Only wired under
587
- // native-host so the default path stays byte-identical.
588
- if (context.bridge?.isNativeHost()) {
589
- setNativeHost(true);
590
- setNativeOverviewProvider(async () => {
591
- const appId = getActiveAppId();
592
- const stack = context.bridge?.getPageStack?.(appId ?? undefined) ?? [];
593
- const top = stack[stack.length - 1];
594
- const overview = {
595
- currentRoute: top?.pagePath ?? null,
596
- pageStackDepth: stack.length,
597
- storageKeys: [],
598
- storageCount: 0,
599
- appDataKeys: [],
600
- };
601
- if (appId) {
602
- const storage = await resolveNativeStorageOverview(context, appId);
603
- overview.storageKeys = storage.storageKeys;
604
- overview.storageCount = storage.storageCount;
605
- overview.appDataKeys = resolveNativeAppDataKeys(context, appId);
346
+ appRegistry.add(mcp);
347
+ // The list window opens no project, so its close needs none of the workbench
348
+ // teardown: just unregister its context, or a recycled `webContents.id`
349
+ // could later resolve to a destroyed window.
350
+ context.registry.add(wireMainWindowEvents(mainWindow, {
351
+ context,
352
+ onResize: () => context.views.repositionAll(),
353
+ onClose: (e) => {
354
+ if (isAppQuitting())
355
+ return;
356
+ // With projects still open, this window must survive its own close
357
+ // button: it is the only way back to the project list, and the
358
+ // app-level IPC it registered cannot be registered again on a
359
+ // replacement window. Hide it — every path back calls
360
+ // `projectList.revealProjectList()`, including the last project window
361
+ // closing.
362
+ if (workbenchWindows.list().length > 0) {
363
+ e.preventDefault();
364
+ mainWindow.hide();
365
+ return;
606
366
  }
607
- return overview;
608
- });
609
- const off = context.bridge.onRenderEvent((ev) => setActiveBridgeId(ev.bridgeId));
610
- context.registry.add(off);
611
- context.registry.add(() => setNativeHost(false));
612
- context.registry.add(() => setNativeOverviewProvider(null));
613
- }
614
- // Native-host inspector: injects the render-guest IIFE and drives WXML /
615
- // element-highlight against the active render-host <webview>. Reused by
616
- // the storage panel (element inspect) and the WXML panel service.
617
- const renderInspector = createRenderInspector({ connections: context.connections, broker: context.cdpSessionBroker });
618
- const storage = setupSimulatorStorage(mainWindow.webContents, {
619
- senderPolicy: context.senderPolicy,
620
- connections: context.connections,
621
- broker: context.cdpSessionBroker,
622
- // Per-project filter for the simulator-storage panel: the simulator
623
- // uses a fixed `persist:simulator` partition + a fixed simulator.html
624
- // origin, so localStorage is shared across every project that has
625
- // ever opened. The dimina runtime isolates writes with `${appId}_`
626
- // prefixes; this callback lets the storage panel filter the CDP
627
- // snapshot/event stream to the active appId.
628
- getActiveAppId,
629
- // Native-host: route element inspection to the active render guest, and
630
- // read/write storage from the service-host window's file:// store.
631
- bridge: context.bridge,
632
- renderInspector,
633
- });
634
- context.registry.add(storage);
635
- // Native-host: expose the async-storage runtime hook so bridge-router
636
- // routes async wx.setStorage/etc. to the unified service-host store.
637
- if (storage.storageApi) {
638
- context.storageApi = storage.storageApi;
639
- context.registry.add(() => { context.storageApi = undefined; });
640
- // SYNC wx storage writes bypass main (they hit the service-host localStorage
641
- // directly); the service-host posts `storageChanged` and bridge-router routes
642
- // it here so the Storage panel stays live without a manual reload.
643
- context.onServiceStorageChanged = storage.onSyncStorageChange;
644
- context.registry.add(() => { context.onServiceStorageChanged = undefined; });
645
- }
646
- // Native-host WXML + AppData panels: main sources the data (WXML pulled
647
- // from the active render guest; AppData tapped from the service→render
648
- // setData stream in bridge-router) and pushes it to the renderer. Inert on
649
- // the default dimina-fe path (which sources both from the simulator
650
- // miniappSnapshot transport), so only wire them when native-host is on.
651
- if (context.bridge?.isNativeHost()) {
652
- // Native-host: surface the simulator WCV's network (wx.request/download/
653
- // upload run there, not in the service host) in the embedded DevTools by
654
- // injecting its raw Network.* CDP events into the DevTools front-end so the
655
- // native Network tab renders them (service-host console line is the
656
- // fallback). The ViewManager calls the forwarder's attachSimulator +
657
- // setDevtoolsHost from attachNativeSimulator once the simulator WCV +
658
- // DevTools host exist; getServiceWc here is the fallback sink target.
659
- const networkForward = createNetworkForwarder({
660
- getServiceWc: (appId) => context.bridge?.getServiceWc(appId) ?? null,
661
- getResourceServerBaseUrl: () => context.bridge?.getResourceBaseUrl?.() ?? null,
662
- // The simulator shell's own static-asset server (serves simulator.html
663
- // + its JS/CSS, independent from the resource server above — see
664
- // NetworkForwarderBridge.getSimulatorServerBaseUrl's doc). Host is
665
- // always 'localhost' — see shared/simulator-route.ts's
666
- // buildSimulatorUrlFromSpec default. Absent (null port) when no
667
- // project is open.
668
- getSimulatorServerBaseUrl: () => {
669
- const port = context.workspace?.getSession()?.port;
670
- return typeof port === 'number' ? `http://localhost:${port}/` : null;
671
- },
672
- connections: context.connections,
673
- broker: context.cdpSessionBroker,
674
- });
675
- context.networkForward = networkForward;
676
- context.registry.add(networkForward);
677
- context.registry.add(() => { context.networkForward = undefined; });
678
- // Global mirror: once the standalone internal
679
- // DevTools window builds its own front-end host, mirror the full
680
- // unfiltered Network stream into it. Attached AFTER context.networkForward
681
- // is assigned above — the callback re-reads the mutable field on every
682
- // fire, so ordering only matters for readability here, not correctness.
683
- context.registry.add(toDisposable(context.internalDevtoolsWindow?.onHostChanged((hostWc) => {
684
- context.networkForward?.setGlobalDevtoolsHost(hostWc);
685
- }) ?? (() => { })));
686
- // Main-process WebSocket traffic (wx.connectSocket runs on the Node `ws`
687
- // transport, invisible to any webContents debugger): bridge-router fans
688
- // the trace stream out here, and the forwarder synthesizes it into
689
- // Network.webSocket* CDP events for the same Network panel sinks.
690
- context.registry.add(toDisposable(context.bridge?.onNativeWebSocketTrace?.((ownerId, event) => {
691
- context.networkForward?.reportWebSocketTrace(ownerId, event);
692
- }) ?? (() => { })));
693
- context.registry.add(setupSimulatorWxml(mainWindow.webContents, {
694
- senderPolicy: context.senderPolicy,
695
- bridge: context.bridge,
696
- inspector: renderInspector,
697
- getActiveAppId,
698
- }));
699
- const appDataService = setupSimulatorAppData(mainWindow.webContents, {
700
- senderPolicy: context.senderPolicy,
701
- getActiveAppId,
702
- // AppData-panel edit write-back target: the service-host window owning
703
- // the edited page bridge.
704
- bridge: context.bridge,
705
- });
706
- // bridge-router feeds this via ctx.appData (service→render tap + evict).
707
- context.appData = appDataService;
708
- context.registry.add(appDataService);
709
- context.registry.add(() => { context.appData = undefined; });
710
- // Push the visible page route to the toolbar on every navigation (the
711
- // page stack lives in the DeviceShell WCV, invisible to renderer
712
- // <webview> nav events).
713
- context.registry.add(setupSimulatorCurrentPage(mainWindow.webContents, {
714
- bridge: context.bridge,
715
- }));
716
- }
717
- // Embedded workbench editor — the sole devtools editor. Stand up the COI
718
- // http server that serves the workbench bundle with the SharedArrayBuffer
719
- // isolation headers and bridges `/__fs/*` onto the active project, then hand its
720
- // base URL to the view manager so the 'editor' dock slot mounts the workbench
721
- // WebContentsView. Both the server and the WCV tear down with the context
722
- // registry.
723
- //
724
- // Default the bundle dir to the devtools package's OWN `dist/vscode-workbench`
725
- // (resolved from the package root), NOT relative to the caller's rendererDir:
726
- // a host that overrides `rendererDir` but omits `editorViewConfig.bundleDir`
727
- // would otherwise compute a path next to ITS renderer, where no workbench
728
- // bundle exists → 404 / blank editor.
729
- const bundleDir = config.editorViewConfig?.bundleDir ?? path.join(devtoolsPackageRoot, 'dist/vscode-workbench');
730
- // Skip the entire editor assembly when the bundle is missing. Starting the COI
731
- // server and attaching the WCV against a non-existent bundle yields a silent
732
- // blank editor (the WCV loads index.html → 404); a launchable app with no
733
- // editor is strictly better. The view manager never gets a source, so the
734
- // 'editor' slot's lazy attach is a no-op.
735
- if (!fs.existsSync(path.join(bundleDir, 'index.html'))) {
736
- console.warn(`[workbench] editor bundle not found at ${bundleDir} — skipping embedded editor assembly`);
737
- }
738
- else {
739
- const coiServer = await startWorkbenchCoiServer({
740
- rootDir: bundleDir,
741
- getProjectRoot: () => context.workspace.getProjectPath(),
742
- extensionsDir: config.editorViewConfig?.extensionsDir,
743
- getFileTypes: () => context.fileTypes,
744
- // Names the editor's VS Code workspace after the active miniapp, so each
745
- // project gets its own open-editors/view-state bucket instead of all of
746
- // them sharing the one derived from the constant mirror root.
747
- getProjectIdentity: () => ({
748
- appId: getActiveAppId(),
749
- projectPath: context.workspace.getProjectPath(),
750
- }),
751
- });
752
- // Return the close promise so the registry awaits server shutdown on dispose
753
- // instead of fire-and-forgetting it (a dangling http server would keep the
754
- // port + event loop alive past teardown).
755
- context.registry.add(() => coiServer.close());
756
- // The registry disposes LIFO, so this runs BEFORE the context-level
757
- // disposeAll: void any in-flight open's attach hold here too, or a stale
758
- // release / cap firing could rebuild the workbench during the awaited
759
- // coiServer.close() above, before disposeAll's own cancel runs.
760
- context.registry.add(() => {
761
- context.views.cancelWorkbenchAttachHold();
762
- context.views.detachWorkbench();
763
- });
764
- // Only HAND the view manager the COI URL — do NOT load yet. The heavy
765
- // WebContentsView load (10MB bundle + ext-host) is deferred to the first time
766
- // the 'editor' dock slot becomes visible (first non-zero bounds), so it never
767
- // sits on the app boot critical path. Loading it eagerly here delayed
768
- // preload/window-ready enough to trip the e2e launch health check.
769
- context.views.setWorkbenchSource(coiServer.baseUrl);
770
- }
771
- // Main's mirror of the renderer's top-level screen. The renderer's `page`
772
- // state is the single authority; it pushes every change over
773
- // WindowChannel.ScreenState. Main reads this mirror in the window-close
774
- // decision so a close while the renderer is parked on a project screen (even
775
- // a failed open with no session) returns to the list instead of quitting.
776
- let rendererScreen = 'list';
777
- context.registry.add(new IpcRegistry(context.senderPolicy).handle(WindowChannel.ScreenState, (_e, screen) => {
778
- rendererScreen = screen === 'project' ? 'project' : 'list';
367
+ void launcher.dispose();
368
+ },
779
369
  }));
780
- context.registry.add(wireAppWindowEvents(config, instance, () => rendererScreen === 'project'));
781
- context.registry.add(enableDevRendererAutoReload(rendererDir));
370
+ // macOS keeps the app running with no window on screen. Clicking the dock
371
+ // icon then has to bring something back, and the project list is the app's
372
+ // home — a workbench window only exists for a project the user already
373
+ // chose.
374
+ const onActivate = () => {
375
+ if (mainWindow.isDestroyed())
376
+ return;
377
+ if (mainWindow.isVisible())
378
+ return;
379
+ if (workbenchWindows.list().some((pw) => !pw.window.isDestroyed() && pw.window.isVisible()))
380
+ return;
381
+ projectList.revealProjectList();
382
+ };
383
+ app.on('activate', onActivate);
384
+ appRegistry.add(toDisposable(() => { app.removeListener('activate', onActivate); }));
385
+ appRegistry.add(enableDevRendererAutoReload(rendererDir));
782
386
  return instance;
783
387
  }
784
388
  //# sourceMappingURL=app.js.map