@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
@@ -2,14 +2,28 @@ import type { WebContents } from 'electron';
2
2
  import type { ConnectionRegistry } from '@dimina-kit/electron-deck/main';
3
3
  import type { NativeDeviceInfo } from '../../../shared/ipc-channels.js';
4
4
  import { type CdpSessionBroker } from '../cdp-session/index.js';
5
+ /** What the guest's own page contributes to the inset policy: whether the
6
+ * shell draws a tabBar under it, and whether it draws a navigation bar over
7
+ * the notch for it. */
8
+ export interface GuestPageInsetPolicy {
9
+ isTabPage: boolean;
10
+ isCustomNav: boolean;
11
+ }
12
+ /**
13
+ * Read a guest's inset policy off its render-host URL (`isTab`, `navStyle` —
14
+ * both written by `buildRenderHostDocumentUrl`). An unparseable URL degrades to
15
+ * the default-nav, non-tab policy rather than failing the attach.
16
+ */
17
+ export declare function parseGuestPageInsetPolicy(src: string): GuestPageInsetPolicy;
5
18
  export interface SafeAreaController {
6
19
  /** Attach the debugger to a freshly-attached render-host guest and push the
7
- * current device's insets. `isTabPage` selects the bottom-inset policy (0 for
8
- * tab pages, the real inset for full-bleed non-tab pages). No-op (warn) if the
9
- * guest is already claimed by an external CDP client env then stays 0. */
10
- applyToGuest(guestWc: WebContents, device: NativeDeviceInfo | null, isTabPage: boolean): void;
20
+ * current device's insets. `page` selects the top policy (real inset only for
21
+ * a custom-nav page) and the bottom policy (0 for tab pages, the real inset
22
+ * for full-bleed non-tab pages). No-op (warn) if the guest is already claimed
23
+ * by an external CDP client env then stays 0. */
24
+ applyToGuest(guestWc: WebContents, device: NativeDeviceInfo | null, page: GuestPageInsetPolicy): void;
11
25
  /** Re-push insets to every still-attached guest after a device change (each
12
- * guest keeps the page type it attached with). */
26
+ * guest keeps the page policy it attached with). */
13
27
  reapplyAll(device: NativeDeviceInfo | null): void;
14
28
  /** Release this controller's session leases (teardown). Does not itself
15
29
  * detach the shared debugger session — see cdp-session/index.ts. */
@@ -1,12 +1,37 @@
1
1
  import { createCdpSessionBroker } from '../cdp-session/index.js';
2
- function guestInsets(device, isTabPage) {
3
- const top = device?.safeAreaInsets.top ?? 0;
2
+ /**
3
+ * Read a guest's inset policy off its render-host URL (`isTab`, `navStyle` —
4
+ * both written by `buildRenderHostDocumentUrl`). An unparseable URL degrades to
5
+ * the default-nav, non-tab policy rather than failing the attach.
6
+ */
7
+ export function parseGuestPageInsetPolicy(src) {
8
+ try {
9
+ const params = new URL(src).searchParams;
10
+ return {
11
+ isTabPage: params.get('isTab') === '1',
12
+ isCustomNav: params.get('navStyle') === 'custom',
13
+ };
14
+ }
15
+ catch {
16
+ return { isTabPage: false, isCustomNav: false };
17
+ }
18
+ }
19
+ function guestInsets(device, page) {
20
+ // Only a custom-nav (full-bleed) page borders the unsafe top zone; a
21
+ // default-nav page already starts below the shell nav bar, which covers the
22
+ // notch itself.
23
+ const top = page.isCustomNav ? (device?.safeAreaInsets.top ?? 0) : 0;
4
24
  // A tab page's content sits above the shell-drawn tabBar (which fills the
5
25
  // bottom safe area), so it never borders the bottom unsafe zone. A non-tab
6
26
  // page is full-bleed to the device bottom, so surface the real inset for its
7
27
  // own `env(safe-area-inset-bottom)` opt-in.
8
- const bottom = isTabPage ? 0 : (device?.safeAreaInsets.bottom ?? 0);
9
- return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
28
+ const bottom = page.isTabPage ? 0 : (device?.safeAreaInsets.bottom ?? 0);
29
+ // Left/right come straight from the device (e.g. a landscape Dynamic Island
30
+ // rotates into a side notch) — unlike top/bottom they don't depend on page
31
+ // type, since neither shell chrome nor the tabBar reserves horizontal space.
32
+ const right = device?.safeAreaInsets.right ?? 0;
33
+ const left = device?.safeAreaInsets.left ?? 0;
34
+ return { top, topMax: top, right, rightMax: right, bottom, bottomMax: bottom, left, leftMax: left };
10
35
  }
11
36
  export function createSafeAreaController(options = {}) {
12
37
  // Own (and dispose on this controller's own dispose()) a private broker
@@ -17,10 +42,10 @@ export function createSafeAreaController(options = {}) {
17
42
  // passes one; falls back to a private instance so this module stays
18
43
  // independently testable/usable.
19
44
  const broker = options.broker ?? createCdpSessionBroker({ connections: options.connections });
20
- // Each guest's page type, fixed for its life — tracked SEPARATELY from the
45
+ // Each guest's page policy, fixed for its life — tracked SEPARATELY from the
21
46
  // lease so a lost session (external detach) doesn't lose the policy: a
22
47
  // later `override`/`reapplyAll` can reacquire and keep applying the same
23
- // isTabPage this guest attached with.
48
+ // policy this guest attached with.
24
49
  const pageType = new Map();
25
50
  // Current lease per guest, if any. Cleared (not just left stale) on
26
51
  // `lease.onDetach` — an external detach or a real Chrome DevTools window
@@ -39,7 +64,7 @@ export function createSafeAreaController(options = {}) {
39
64
  lease.onDetach(() => { leases.delete(wc); });
40
65
  return lease;
41
66
  }
42
- function override(wc, device, isTabPage) {
67
+ function override(wc, device, page) {
43
68
  if (wc.isDestroyed())
44
69
  return;
45
70
  const lease = ensureLease(wc);
@@ -51,17 +76,17 @@ export function createSafeAreaController(options = {}) {
51
76
  return;
52
77
  }
53
78
  void lease
54
- .send('Emulation.setSafeAreaInsetsOverride', { insets: guestInsets(device, isTabPage) })
79
+ .send('Emulation.setSafeAreaInsetsOverride', { insets: guestInsets(device, page) })
55
80
  .catch((err) => {
56
81
  console.warn('[safe-area] setSafeAreaInsetsOverride failed:', err instanceof Error ? err.message : err);
57
82
  });
58
83
  }
59
84
  return {
60
- applyToGuest: (wc, device, isTabPage) => {
85
+ applyToGuest: (wc, device, page) => {
61
86
  if (!wc || wc.isDestroyed())
62
87
  return;
63
88
  const isFirstTime = !pageType.has(wc);
64
- pageType.set(wc, isTabPage);
89
+ pageType.set(wc, page);
65
90
  if (isFirstTime) {
66
91
  const forget = () => { pageType.delete(wc); leases.delete(wc); };
67
92
  if (options.connections) {
@@ -71,11 +96,11 @@ export function createSafeAreaController(options = {}) {
71
96
  wc.once('destroyed', forget);
72
97
  }
73
98
  }
74
- override(wc, device, isTabPage);
99
+ override(wc, device, page);
75
100
  },
76
101
  reapplyAll: (device) => {
77
- for (const [wc, isTabPage] of pageType)
78
- override(wc, device, isTabPage);
102
+ for (const [wc, page] of pageType)
103
+ override(wc, device, page);
79
104
  },
80
105
  dispose: () => {
81
106
  // Release our leases only — the shared session's actual detach is the
@@ -9,5 +9,13 @@ export interface SimulatorUiExtensionRegistry {
9
9
  export interface SimulatorUiExtensionRegistryOptions {
10
10
  loadSource?: (scriptPath: string) => Promise<string>;
11
11
  }
12
+ /**
13
+ * The rules a registration has to satisfy on its own, with no window and no
14
+ * renderer involved: the id is a lookup key on both sides of the bridge, and
15
+ * the script is read straight from disk with no base to resolve against. They
16
+ * hold wherever a registration is accepted, so whoever accepts one first is
17
+ * where a host learns it is unusable.
18
+ */
19
+ export declare function assertSimulatorUiExtensionShape(registration: SimulatorUiExtensionRegistration): void;
12
20
  export declare function createSimulatorUiExtensionRegistry(options?: SimulatorUiExtensionRegistryOptions): SimulatorUiExtensionRegistry;
13
21
  //# sourceMappingURL=ui-extensions.d.ts.map
@@ -2,17 +2,27 @@ import { readFile } from 'node:fs/promises';
2
2
  import { isAbsolute } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  const EXTENSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
5
+ /**
6
+ * The rules a registration has to satisfy on its own, with no window and no
7
+ * renderer involved: the id is a lookup key on both sides of the bridge, and
8
+ * the script is read straight from disk with no base to resolve against. They
9
+ * hold wherever a registration is accepted, so whoever accepts one first is
10
+ * where a host learns it is unusable.
11
+ */
12
+ export function assertSimulatorUiExtensionShape(registration) {
13
+ if (!EXTENSION_ID_RE.test(registration.id)) {
14
+ throw new Error(`Invalid simulator UI extension id: "${registration.id}"`);
15
+ }
16
+ if (!isAbsolute(registration.rendererScriptPath)) {
17
+ throw new Error('Simulator UI extension rendererScriptPath must be absolute');
18
+ }
19
+ }
5
20
  export function createSimulatorUiExtensionRegistry(options = {}) {
6
21
  const loadSource = options.loadSource ?? (scriptPath => readFile(scriptPath, 'utf8'));
7
22
  const entries = new Map();
8
23
  let active = null;
9
24
  function assertRegistration(registration) {
10
- if (!EXTENSION_ID_RE.test(registration.id)) {
11
- throw new Error(`Invalid simulator UI extension id: "${registration.id}"`);
12
- }
13
- if (!isAbsolute(registration.rendererScriptPath)) {
14
- throw new Error('Simulator UI extension rendererScriptPath must be absolute');
15
- }
25
+ assertSimulatorUiExtensionShape(registration);
16
26
  if (entries.has(registration.id)) {
17
27
  throw new Error(`Simulator UI extension "${registration.id}" is already registered`);
18
28
  }
@@ -19,7 +19,7 @@
19
19
  * opens one, the shared session detaches externally and events stop
20
20
  * flowing until they close it.
21
21
  */
22
- import { type WebContents } from 'electron';
22
+ import { type BrowserWindow, type WebContents } from 'electron';
23
23
  import { type ConnectionRegistry, type Disposable } from '@dimina-kit/electron-deck/main';
24
24
  import { type SyncStorageChange } from '../../../shared/ipc-channels.js';
25
25
  import { type SenderPolicy } from '../../utils/ipc-registry.js';
@@ -96,6 +96,17 @@ export interface SimulatorStorageOptions {
96
96
  * compile and behave unchanged, just without cross-module session sharing.
97
97
  */
98
98
  broker?: CdpSessionBroker;
99
+ /**
100
+ * Owning window (typically `mainWindow`). Scopes every simulator-webview
101
+ * discovery path (initial scan, `web-contents-created`, lazy re-find) to
102
+ * webviews that live inside THIS window — required in a multi-window
103
+ * workbench, where `webContents`/`app.on('web-contents-created')` are
104
+ * process-global and would otherwise let one window's instance attach to
105
+ * (and read/write through) another window's simulator. Optional so
106
+ * existing single-window callers/tests compile and keep their prior
107
+ * unscoped behavior.
108
+ */
109
+ window?: BrowserWindow;
99
110
  }
100
111
  export declare function setupSimulatorStorage(host: WebContents, options: SimulatorStorageOptions): SimulatorStorageHandle;
101
112
  //# sourceMappingURL=index.d.ts.map
@@ -25,6 +25,7 @@ import { SimulatorElementChannel, SimulatorStorageChannel, } from '../../../shar
25
25
  import { IpcRegistry } from '../../utils/ipc-registry.js';
26
26
  import { decodeStorageValue, encodeStorageValue, serviceStorage } from './service-storage-ops.js';
27
27
  import { createCdpSessionBroker } from '../cdp-session/index.js';
28
+ import { windowHostsWebContents } from './window-scope.js';
28
29
  /** Async wx storage API names routed through the unified service-window store. */
29
30
  export const STORAGE_API_NAMES = new Set([
30
31
  'setStorage',
@@ -40,6 +41,17 @@ function isSimulatorWebview(wc) {
40
41
  return false;
41
42
  return wc.getURL().includes('simulator.html');
42
43
  }
44
+ /**
45
+ * `win` undefined means no owning window was configured — the legacy
46
+ * unscoped behavior, kept so single-window callers/tests that don't pass
47
+ * `window` keep compiling and behaving unchanged. Every production call
48
+ * site (window-runtime-services.ts) always passes its window.
49
+ */
50
+ function belongsToWindow(win, wc) {
51
+ if (!win)
52
+ return true;
53
+ return windowHostsWebContents(win, wc);
54
+ }
43
55
  function safeOff(target, event, fn) {
44
56
  try {
45
57
  if (target.isDestroyed?.())
@@ -198,7 +210,7 @@ export function setupSimulatorStorage(host, options) {
198
210
  async function ensureAttached() {
199
211
  if (attachedWc && !attachedWc.isDestroyed())
200
212
  return;
201
- const sim = findSimulatorWebContents();
213
+ const sim = findSimulatorWebContents(options.window);
202
214
  if (!sim)
203
215
  return;
204
216
  await attachToSim(sim);
@@ -392,7 +404,7 @@ export function setupSimulatorStorage(host, options) {
392
404
  // Attach to any simulator webview that already exists (project may have
393
405
  // been opened before this function runs).
394
406
  for (const wc of wcStatic.getAllWebContents()) {
395
- if (isSimulatorWebview(wc)) {
407
+ if (isSimulatorWebview(wc) && belongsToWindow(options.window, wc)) {
396
408
  void attachToSim(wc);
397
409
  break;
398
410
  }
@@ -406,7 +418,7 @@ export function setupSimulatorStorage(host, options) {
406
418
  const sub = new DisposableRegistry();
407
419
  wcSubs.set(wc, sub);
408
420
  const onFinishLoad = () => {
409
- if (isSimulatorWebview(wc))
421
+ if (isSimulatorWebview(wc) && belongsToWindow(options.window, wc))
410
422
  void attachToSim(wc);
411
423
  };
412
424
  wc.on('did-finish-load', onFinishLoad);
@@ -455,7 +467,7 @@ export function setupSimulatorStorage(host, options) {
455
467
  return null;
456
468
  return options.renderInspector.highlight(wc, sid);
457
469
  }
458
- const sim = findSimulatorWebContents();
470
+ const sim = findSimulatorWebContents(options.window);
459
471
  if (!sim)
460
472
  return null;
461
473
  try {
@@ -474,7 +486,7 @@ export function setupSimulatorStorage(host, options) {
474
486
  await options.renderInspector.unhighlight(wc);
475
487
  return;
476
488
  }
477
- const sim = findSimulatorWebContents();
489
+ const sim = findSimulatorWebContents(options.window);
478
490
  if (!sim)
479
491
  return;
480
492
  try {
@@ -591,9 +603,12 @@ export function setupSimulatorStorage(host, options) {
591
603
  // bridge via executeJavaScript. Looked up independently from `attachedWc` so
592
604
  // it keeps working when the user opens Chrome DevTools and the storage
593
605
  // debugger detaches (debugger.attach is mutually exclusive with F12).
594
- function findSimulatorWebContents() {
606
+ // Scoped to `win` for the same reason as the attach paths above — an
607
+ // unscoped scan would resolve to whichever window's simulator comes first
608
+ // in the process-global registry, not necessarily this instance's own.
609
+ function findSimulatorWebContents(win) {
595
610
  for (const wc of wcStatic.getAllWebContents()) {
596
- if (isSimulatorWebview(wc))
611
+ if (isSimulatorWebview(wc) && belongsToWindow(win, wc))
597
612
  return wc;
598
613
  }
599
614
  return null;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Window-ownership judgment for simulator webContents discovery.
3
+ *
4
+ * The workbench runs one `setupSimulatorStorage` instance per project
5
+ * window, but Electron's `webContents`/`app.on('web-contents-created')`
6
+ * APIs are process-global — every instance sees every window's webviews.
7
+ * Without this filter, whichever instance's scan/callback fires first wins
8
+ * the simulator it finds, so opening a second project window can steal the
9
+ * first window's already-attached simulator (its Storage panel then reads
10
+ * and writes the second project's partition).
11
+ *
12
+ * The ownership rule itself (does a webContents sit inside a window's own
13
+ * renderer, its `contentView` tree, or a `<webview>` guest embedded in
14
+ * either) is shared with dimina-electron-runtime's bridge router, which
15
+ * faces the identical process-global-vs-per-window problem for its IPC
16
+ * routing — so it's owned by @dimina-kit/electron-deck/main and re-exported
17
+ * here under the name this package's callers expect.
18
+ */
19
+ export { windowHostsWebContents } from '@dimina-kit/electron-deck/main';
20
+ //# sourceMappingURL=window-scope.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Window-ownership judgment for simulator webContents discovery.
3
+ *
4
+ * The workbench runs one `setupSimulatorStorage` instance per project
5
+ * window, but Electron's `webContents`/`app.on('web-contents-created')`
6
+ * APIs are process-global — every instance sees every window's webviews.
7
+ * Without this filter, whichever instance's scan/callback fires first wins
8
+ * the simulator it finds, so opening a second project window can steal the
9
+ * first window's already-attached simulator (its Storage panel then reads
10
+ * and writes the second project's partition).
11
+ *
12
+ * The ownership rule itself (does a webContents sit inside a window's own
13
+ * renderer, its `contentView` tree, or a `<webview>` guest embedded in
14
+ * either) is shared with dimina-electron-runtime's bridge router, which
15
+ * faces the identical process-global-vs-per-window problem for its IPC
16
+ * routing — so it's owned by @dimina-kit/electron-deck/main and re-exported
17
+ * here under the name this package's callers expect.
18
+ */
19
+ export { windowHostsWebContents } from '@dimina-kit/electron-deck/main';
20
+ //# sourceMappingURL=window-scope.js.map
@@ -50,13 +50,30 @@ import type { WebContents } from 'electron';
50
50
  export interface HostSlotMessageSubscription {
51
51
  dispose(): void;
52
52
  }
53
+ /**
54
+ * Handle returned by `attach`, scoped to the ONE webContents it was made for.
55
+ * `dispose()` removes the listeners that `attach` installed on that wc and, if
56
+ * that wc still owns the live port, closes it. Idempotent, and never touches a
57
+ * successor's attachment: releasing a superseded wc leaves the current one
58
+ * delivering.
59
+ *
60
+ * The owner calls this when it replaces or destroys that wc, so the channel
61
+ * holds no main-side state for a webContents its owner has let go — the
62
+ * channel does not have to be the wc's sole owner for its listeners to be
63
+ * released on time.
64
+ */
65
+ export interface HostSlotAttachment {
66
+ dispose(): void;
67
+ }
53
68
  export interface HostSlotPortChannel {
54
69
  /**
55
70
  * Hook a freshly created slot webContents: registers the `did-finish-load`
56
- * (per-load handshake) and `destroyed` (drop the live port) listeners via
57
- * `wc.on(...)`. Call exactly once per wc, right after the view is created.
71
+ * (per-load handshake), `did-start-navigation` (document-replacing
72
+ * navigation) and `destroyed` (drop the live port) listeners via
73
+ * `wc.on(...)`. Call exactly once per wc, right after the view is created,
74
+ * and `dispose()` the returned handle when that wc is replaced or destroyed.
58
75
  */
59
- attach(wc: WebContents): void;
76
+ attach(wc: WebContents): HostSlotAttachment;
60
77
  /**
61
78
  * Drop the live port NOW (close + clear): the document it belongs to is
62
79
  * being replaced. Called synchronously when the HOST initiates a
@@ -165,7 +165,7 @@ export function createHostSlotPortChannel(opts) {
165
165
  }
166
166
  return {
167
167
  attach(wc) {
168
- wc.on('did-finish-load', () => handshake(wc));
168
+ const onDidFinishLoad = () => handshake(wc);
169
169
  // Page-initiated navigation (location.href / reload): the document the
170
170
  // port lives in is being replaced — invalidate so send() reports false
171
171
  // through the navigation window instead of confirming delivery into a
@@ -178,7 +178,7 @@ export function createHostSlotPortChannel(opts) {
178
178
  // - main frame only: an <iframe> navigating keeps the main document.
179
179
  // Reads the details object (electron >= 12 shape); falls back to the
180
180
  // deprecated positional args (`isInPlace` === details.isSameDocument).
181
- wc.on('did-start-navigation', (details, _url, isInPlace, isMainFramePositional) => {
181
+ const onDidStartNavigation = (details, _url, isInPlace, isMainFramePositional) => {
182
182
  if (activeWc !== wc)
183
183
  return;
184
184
  const isSameDocument = typeof details?.isSameDocument === 'boolean'
@@ -190,11 +190,39 @@ export function createHostSlotPortChannel(opts) {
190
190
  if (isSameDocument || !isMainFrame)
191
191
  return;
192
192
  dropActivePort(true, 'awaitingHandshake');
193
- });
194
- wc.on('destroyed', () => {
193
+ };
194
+ const onDestroyed = () => {
195
195
  if (activeWc === wc)
196
196
  dropActivePort(true, 'absent');
197
- });
197
+ };
198
+ wc.on('did-finish-load', onDidFinishLoad);
199
+ wc.on('did-start-navigation', onDidStartNavigation);
200
+ wc.on('destroyed', onDestroyed);
201
+ let released = false;
202
+ return {
203
+ dispose() {
204
+ if (released)
205
+ return;
206
+ released = true;
207
+ // This wc is going away: if it still owns the live port, the
208
+ // document that port belongs to is going with it, so close it and
209
+ // let send() report false. A superseded wc owns nothing — the
210
+ // successor's port is untouched.
211
+ if (activeWc === wc)
212
+ dropActivePort(true, 'absent');
213
+ // A destroyed webContents still carries its JS-side emitter, so the
214
+ // removals below are meaningful on both live and dead wcs; the
215
+ // try/catch only covers hosts that reject the call outright.
216
+ try {
217
+ wc.off('did-finish-load', onDidFinishLoad);
218
+ wc.off('did-start-navigation', onDidStartNavigation);
219
+ wc.off('destroyed', onDestroyed);
220
+ }
221
+ catch {
222
+ /* wc already gone; its listeners went with it */
223
+ }
224
+ },
225
+ };
198
226
  },
199
227
  invalidate() {
200
228
  dropActivePort(true, 'awaitingHandshake');
@@ -4,6 +4,18 @@ export function createManagedWebContentsView(opts) {
4
4
  let view = null;
5
5
  let preloadOverride = null;
6
6
  let runtimeAcquired = false;
7
+ /** The channel attachment for the CURRENT wc; released when that wc is replaced or destroyed. */
8
+ let attachment = null;
9
+ /**
10
+ * Hand the channel back the wc this manager is done with. This manager owns
11
+ * the wc's whole life, so it is the only thing that knows the wc is gone;
12
+ * without this the channel would keep listeners on a webContents nobody can
13
+ * reach any more.
14
+ */
15
+ function releaseAttachment() {
16
+ attachment?.dispose();
17
+ attachment = null;
18
+ }
7
19
  function liveWebContents() {
8
20
  const wc = view?.webContents;
9
21
  if (!wc || wc.isDestroyed())
@@ -22,6 +34,7 @@ export function createManagedWebContentsView(opts) {
22
34
  if (view !== current)
23
35
  return;
24
36
  console.error(`[${opts.viewId}] ${detail} — destroying so the next ensureView() rebuilds`);
37
+ releaseAttachment();
25
38
  view = null;
26
39
  opts.reconciler.destroyView(opts.viewId, current);
27
40
  opts.onBroken?.();
@@ -30,6 +43,10 @@ export function createManagedWebContentsView(opts) {
30
43
  if (view && liveWebContents()) {
31
44
  return view;
32
45
  }
46
+ // The outgoing wc (if any) is about to be destroyed, so its attachment
47
+ // goes first — a wc this manager no longer owns must not keep the channel
48
+ // wired to it.
49
+ releaseAttachment();
33
50
  if (view) {
34
51
  opts.reconciler.destroyView(opts.viewId, view);
35
52
  }
@@ -48,7 +65,7 @@ export function createManagedWebContentsView(opts) {
48
65
  }
49
66
  const next = new WebContentsView({ webPreferences });
50
67
  view = next;
51
- opts.port.attach(next.webContents);
68
+ attachment = opts.port.attach(next.webContents);
52
69
  try {
53
70
  next.setBackgroundColor('#121212');
54
71
  }
@@ -105,6 +122,9 @@ export function createManagedWebContentsView(opts) {
105
122
  // registry, so a send() racing teardown reports false instead of posting
106
123
  // into a wc that is about to be closed.
107
124
  opts.port.dispose();
125
+ // Then hand back the wc itself, so no listener rests on a webContents
126
+ // this manager is about to close.
127
+ releaseAttachment();
108
128
  // Host-controllable slot view: removed from the contentView + its
109
129
  // WebContents closed (the host's loaded content is torn down on app exit).
110
130
  opts.reconciler.destroyView(opts.viewId, view);
@@ -5,6 +5,7 @@ import { handleWindowOpenExternal } from '../../windows/navigation-hardening.js'
5
5
  import { SimulatorCustomApiBridgeChannel } from '../../../shared/ipc-channels.js';
6
6
  import * as layout from '../layout/index.js';
7
7
  import { handleCustomApiBridgeRequest, } from '../simulator/custom-apis.js';
8
+ import { parseGuestPageInsetPolicy } from '../safe-area/index.js';
8
9
  import { configureMiniappSession, miniappPartition } from './miniapp-partition.js';
9
10
  import { refreshGuestStylesheets } from './refresh-styles.js';
10
11
  import { parseRoute } from '../../../shared/simulator-route.js';
@@ -230,27 +231,23 @@ export function createNativeSimulatorView(ctx, reconciler, deps) {
230
231
  // them with contextIsolation/sandbox off so the render runtime + its preload
231
232
  // share the page realm. (A top-level WebContentsView can host these guests; a
232
233
  // `<webview>` guest cannot — that's the whole point of Option A.)
233
- // Page type (`isTab`) of each attaching guest, captured from its render-host
234
- // URL in will-attach (where `params.src` carries the full URL) and consumed
235
- // FIFO in the matching did-attach — `guestWc.getURL()` is still empty there.
234
+ // Inset policy (`isTab`, `navStyle`) of each attaching guest, captured from
235
+ // its render-host URL in will-attach (where `params.src` carries the full
236
+ // URL) and consumed FIFO in the matching did-attach — `guestWc.getURL()` is
237
+ // still empty there.
236
238
  // Per-attach scope: a fresh simWc + handlers are built on every (re)attach.
237
239
  // (The guest's `bgColor` query param — WeChat/Android/Harmony white-flash
238
240
  // parity — is consumed entirely outside main: device-shell.tsx's `<webview>`
239
241
  // CSS background and render-host/preload.cjs both read it directly, since
240
242
  // `WebContents` has no `setBackgroundColor` for main to call here.)
241
- const pendingGuestIsTab = [];
243
+ const pendingGuestPages = [];
242
244
  simWc.on('will-attach-webview', (_event, webPreferences, params) => {
243
245
  ;
244
246
  webPreferences.partition = partition;
245
247
  params.partition = partition;
246
248
  webPreferences.contextIsolation = false;
247
249
  webPreferences.sandbox = false;
248
- let isTab = false;
249
- try {
250
- isTab = new URL(params.src).searchParams.get('isTab') === '1';
251
- }
252
- catch { /* keep false */ }
253
- pendingGuestIsTab.push(isTab);
250
+ pendingGuestPages.push(parseGuestPageInsetPolicy(params.src));
254
251
  });
255
252
  simWc.on('did-attach-webview', (_event, guestWc) => {
256
253
  // Scale the nested render-host page with the device zoom. The host WCV is
@@ -264,13 +261,13 @@ export function createNativeSimulatorView(ctx, reconciler, deps) {
264
261
  }
265
262
  catch { /* guest not ready; setNativeSimulatorViewBounds re-applies */ }
266
263
  // Simulate this device's CSS env(safe-area-inset-*) on the fresh guest
267
- // before it paints, so notch-aware page layout resolves correctly. The
268
- // bottom inset is page-type-dependent (see services/safe-area): a tab
269
- // page's content sits above the shell tabBar (bottom 0); a non-tab page
270
- // is full-bleed (real bottom inset). The page type was captured from the
264
+ // before it paints, so notch-aware page layout resolves correctly. Both
265
+ // the top and the bottom inset depend on the page (see services/safe-area):
266
+ // only a custom-nav page borders the unsafe top zone, and only a non-tab
267
+ // page borders the bottom one. The policy was captured from the
271
268
  // render-host URL in will-attach (FIFO).
272
- const isTabGuest = pendingGuestIsTab.shift() ?? false;
273
- safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null, isTabGuest);
269
+ const guestPage = pendingGuestPages.shift() ?? { isTabPage: false, isCustomNav: false };
270
+ safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null, guestPage);
274
271
  // Page-level resource loads (images/fonts/page fetch) run in THIS guest's
275
272
  // network stack, never the simulator's — without this, only wx.request
276
273
  // (forwarded to the simulator) shows in the Network panel and everything
@@ -16,6 +16,10 @@ export interface ProjectCreateShowPayload {
16
16
  templates: unknown[];
17
17
  defaultBaseDir: string;
18
18
  }
19
+ /** Payload shown by the device-picker overlay panel (`devicePicker:show`). */
20
+ export interface DevicePickerShowPayload {
21
+ deviceName: string;
22
+ }
19
23
  /**
20
24
  * The main-owned overlay panels: the settings sheet (right-side panel over a
21
25
  * transparent backdrop), the transient compile-mode popover, and the tooltip.
@@ -65,8 +69,17 @@ export interface OverlayPanelsView {
65
69
  hideUpdateDialog(): void;
66
70
  /** Forward a download-progress tick into the (already-shown) update overlay. */
67
71
  notifyUpdateDownloadProgress(percent: number): void;
72
+ /**
73
+ * Show the simulator toolbar's device picker (VIEW_LAYER.dialog). Same
74
+ * reason as `showProjectCreateDialog`: the simulator WCV overlaps the
75
+ * centred search panel, so it cannot be a DOM dialog in the toolbar's own
76
+ * renderer.
77
+ */
78
+ showDevicePicker(data: DevicePickerShowPayload): void;
79
+ hideDevicePicker(): void;
68
80
  getProjectCreateDialogWebContentsId(): number | null;
69
81
  getUpdateDialogWebContentsId(): number | null;
82
+ getDevicePickerWebContentsId(): number | null;
70
83
  }
71
84
  export declare function createOverlayPanelsView(ctx: ViewManagerContext, reconciler: PlacementReconciler, deps: {
72
85
  /** The host-toolbar strip height that offsets the overlay top edge. */