@dimina-kit/devtools 0.4.0-dev.20260701083540 → 0.4.0-dev.20260702145840

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 (81) hide show
  1. package/dist/main/app/app.js +11 -0
  2. package/dist/main/app/max-listeners-diagnostic.d.ts +32 -0
  3. package/dist/main/app/max-listeners-diagnostic.js +63 -0
  4. package/dist/main/index.bundle.js +1657 -1158
  5. package/dist/main/ipc/bridge-router.d.ts +33 -4
  6. package/dist/main/ipc/bridge-router.js +200 -51
  7. package/dist/main/ipc/session-listener-bag.d.ts +41 -0
  8. package/dist/main/ipc/session-listener-bag.js +40 -0
  9. package/dist/main/ipc/simulator.js +5 -5
  10. package/dist/main/ipc/views.js +8 -19
  11. package/dist/main/services/automation/wait-active-page.d.ts +4 -2
  12. package/dist/main/services/elements-forward/index.js +19 -0
  13. package/dist/main/services/network-forward/index.js +13 -0
  14. package/dist/main/services/render-inspect/index.d.ts +9 -0
  15. package/dist/main/services/render-inspect/index.js +13 -1
  16. package/dist/main/services/simulator-storage/index.d.ts +9 -0
  17. package/dist/main/services/simulator-storage/index.js +26 -1
  18. package/dist/main/services/simulator-wxml/index.js +117 -11
  19. package/dist/main/services/views/apply-view-ops.d.ts +11 -0
  20. package/dist/main/services/views/apply-view-ops.js +22 -0
  21. package/dist/main/services/views/destroy-child-view.d.ts +9 -0
  22. package/dist/main/services/views/destroy-child-view.js +23 -0
  23. package/dist/main/services/views/host-toolbar-view.d.ts +21 -0
  24. package/dist/main/services/views/host-toolbar-view.js +249 -0
  25. package/dist/main/services/views/inject-when-ready.d.ts +41 -0
  26. package/dist/main/services/views/inject-when-ready.js +62 -0
  27. package/dist/main/services/views/native-simulator-devtools-host.d.ts +26 -0
  28. package/dist/main/services/views/native-simulator-devtools-host.js +484 -0
  29. package/dist/main/services/views/native-simulator-view.d.ts +28 -0
  30. package/dist/main/services/views/native-simulator-view.js +428 -0
  31. package/dist/main/services/views/overlay-panels-view.d.ts +29 -0
  32. package/dist/main/services/views/overlay-panels-view.js +150 -0
  33. package/dist/main/services/views/placement-reconciler.d.ts +73 -0
  34. package/dist/main/services/views/placement-reconciler.js +120 -0
  35. package/dist/main/services/views/placement-test-driver.d.ts +25 -0
  36. package/dist/main/services/views/placement-test-driver.js +48 -0
  37. package/dist/main/services/views/resolve-project-editor-target.d.ts +13 -0
  38. package/dist/main/services/views/resolve-project-editor-target.js +33 -0
  39. package/dist/main/services/views/view-manager.d.ts +28 -70
  40. package/dist/main/services/views/view-manager.js +66 -1514
  41. package/dist/main/services/views/workbench-view.d.ts +15 -0
  42. package/dist/main/services/views/workbench-view.js +170 -0
  43. package/dist/main/services/workbench-context.d.ts +10 -0
  44. package/dist/preload/windows/host-toolbar-runtime.cjs +6 -13
  45. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  46. package/dist/preload/windows/main.cjs +10 -1
  47. package/dist/preload/windows/main.cjs.map +2 -2
  48. package/dist/preload/windows/simulator.cjs +6 -3
  49. package/dist/preload/windows/simulator.cjs.map +2 -2
  50. package/dist/preload/windows/simulator.js +6 -3
  51. package/dist/render-host/render-inspect.js +35 -1
  52. package/dist/renderer/assets/index-ChF9V4r-.js +49 -0
  53. package/dist/renderer/assets/{input-DR7bfyQE.js → input-Cjk0wSEh.js} +2 -2
  54. package/dist/renderer/assets/ipc-transport-D5dKIti1.css +1 -0
  55. package/dist/renderer/assets/{ipc-transport-3Arg8jPC.js → ipc-transport-OZS-KmOW.js} +2 -2
  56. package/dist/renderer/assets/{popover-CPZX3MrT.js → popover-CQVvOe90.js} +2 -2
  57. package/dist/renderer/assets/{select-BxyPlVyJ.js → select-BQDXi5ih.js} +2 -2
  58. package/dist/renderer/assets/{settings-api-CtPPJF-F.js → settings-api-B6x2mhaD.js} +2 -2
  59. package/dist/renderer/assets/{settings-CG_OR03F.js → settings-tP3Px2KR.js} +2 -2
  60. package/dist/renderer/assets/{workbenchSettings-ClqCBGOa.js → workbenchSettings-Bim9ol9R.js} +2 -2
  61. package/dist/renderer/entries/main/index.html +6 -6
  62. package/dist/renderer/entries/popover/index.html +5 -5
  63. package/dist/renderer/entries/settings/index.html +5 -5
  64. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  65. package/dist/service-host/sync-api-patch.js +33 -3
  66. package/dist/shared/bridge-channels.d.ts +17 -0
  67. package/dist/shared/bridge-channels.js +9 -0
  68. package/dist/shared/ipc-channels.d.ts +29 -14
  69. package/dist/shared/ipc-channels.js +16 -17
  70. package/dist/shared/ipc-schemas.d.ts +25 -17
  71. package/dist/shared/ipc-schemas.js +38 -22
  72. package/dist/shared/view-ids.d.ts +19 -0
  73. package/dist/shared/view-ids.js +25 -0
  74. package/dist/simulator/assets/{bridge-channels-BUQ5AbvJ.js → bridge-channels-zhsumfky.js} +2 -2
  75. package/dist/simulator/assets/{device-shell-CT3LRnTB.js → device-shell-CUl0ILfn.js} +2 -2
  76. package/dist/simulator/assets/{simulator-BizhGGE3.js → simulator-Dz8iGcgy.js} +5 -5
  77. package/dist/simulator/assets/{simulator-mini-app-BfNuDF3R.js → simulator-mini-app-Bt639XL_.js} +2 -2
  78. package/dist/simulator/simulator.html +2 -1
  79. package/package.json +4 -4
  80. package/dist/renderer/assets/index-HE9Xr9bQ.js +0 -49
  81. package/dist/renderer/assets/ipc-transport-Dt0jR7Ir.css +0 -1
@@ -108,6 +108,39 @@ function setupCdpPort() {
108
108
  }
109
109
  }
110
110
 
111
+ // src/main/app/max-listeners-diagnostic.ts
112
+ function callSafe(fn) {
113
+ try {
114
+ return fn();
115
+ } catch {
116
+ return void 0;
117
+ }
118
+ }
119
+ function describeMaxListenersWarning(warning) {
120
+ if (warning.name !== "MaxListenersExceededWarning") return null;
121
+ const w = warning;
122
+ const emitter = w.emitter ?? void 0;
123
+ return {
124
+ event: w.type,
125
+ count: w.count,
126
+ wcId: typeof emitter?.id === "number" ? emitter.id : void 0,
127
+ wcType: callSafe(() => emitter?.getType?.()),
128
+ url: callSafe(() => emitter?.getURL?.()),
129
+ destroyed: callSafe(() => emitter?.isDestroyed?.()),
130
+ stack: warning.stack
131
+ };
132
+ }
133
+ function installMaxListenersWarningDiagnostic(log3 = (r) => console.warn("[max-listeners]", JSON.stringify(r))) {
134
+ const onWarning = (warning) => {
135
+ const report = describeMaxListenersWarning(warning);
136
+ if (report) log3(report);
137
+ };
138
+ process.on("warning", onWarning);
139
+ return () => {
140
+ process.removeListener("warning", onWarning);
141
+ };
142
+ }
143
+
111
144
  // src/main/ipc/project-fs.ts
112
145
  import fs2 from "node:fs/promises";
113
146
  import nodeFs from "node:fs";
@@ -306,15 +339,18 @@ var SimulatorChannel = {
306
339
  // Ask main to create the simulator as a top-level WebContentsView (so nested
307
340
  // render-host <webview>s can attach). Native-host is the sole runtime.
308
341
  AttachNative: "simulator:attach-native",
309
- // Renderer reports the device-bezel inner-screen rect (CSS px from content
310
- // top-left) + zoom so main can overlay the simulator WCV on it.
311
- SetNativeBounds: "simulator:set-native-bounds",
312
342
  // Renderer pushes the selected device's LOGICAL metrics (screen size,
313
343
  // pixelRatio, statusBarHeight, …) when the device dropdown changes. Main
314
344
  // maps it to a HostEnvSnapshot and live-updates the running service-host
315
345
  // window — the authoritative `wx.getSystemInfoSync()` source — so the
316
346
  // mini-app sees the selected device without a relaunch.
317
347
  SetDeviceInfo: "simulator:set-device-info",
348
+ // Ask main to soft-reload the LIVE simulator WCV after a watcher rebuild:
349
+ // main forwards a SIMULATOR_EVENTS.RELAUNCH into the shell (which boots a
350
+ // new app session and swaps when ready) instead of destroying the view.
351
+ // Resolves false when there is no live+ready shell — the renderer then falls
352
+ // back to the hard AttachNative rebuild.
353
+ SoftReload: "simulator:soft-reload",
318
354
  Detach: "simulator:detach",
319
355
  Console: "simulator:console",
320
356
  // Main → renderer push of the visible top-of-stack page route whenever the
@@ -353,7 +389,11 @@ var SimulatorElementChannel = {
353
389
  };
354
390
  var SimulatorWxmlChannel = {
355
391
  GetSnapshot: "simulator:wxml:snapshot",
356
- Event: "simulator:wxml:event"
392
+ Event: "simulator:wxml:event",
393
+ // renderer→main: whether the WXML panel is currently visible/active. Main
394
+ // only installs the render-guest DOM MutationObserver + pushes live tree
395
+ // updates while active, so an unseen panel never drives a full Vue-tree walk.
396
+ SetActive: "simulator:wxml:setActive"
357
397
  };
358
398
  var SimulatorAppDataChannel = {
359
399
  GetSnapshot: "simulator:appdata:snapshot",
@@ -423,14 +463,6 @@ var DialogChannel = {
423
463
  OpenDirectory: "dialog:openDirectory"
424
464
  };
425
465
  var ViewChannel = {
426
- /** Update the simulator DevTools view's bounds (or hide if w/h = 0). */
427
- SimulatorDevtoolsBounds: "view:simulator:devtools-bounds",
428
- /**
429
- * Host-controllable toolbar WebContentsView (sits ABOVE the devtools built-in
430
- * header). Forward anchor: the main renderer reports the toolbar placeholder's
431
- * bounds (or w/h=0 to hide). invoke.
432
- */
433
- HostToolbarBounds: "view:host-toolbar:bounds",
434
466
  /**
435
467
  * Reverse size-advertiser: the host-toolbar WCV's OWN renderer advertises its
436
468
  * intrinsic content height so main reserves exactly that much. Payload
@@ -463,12 +495,13 @@ var ViewChannel = {
463
495
  */
464
496
  HostToolbarGetHeight: "view:host-toolbar:get-height",
465
497
  /**
466
- * Update the embedded workbench editor view's bounds (or hide if w/h=0).
467
- * Forward anchor mirroring `SimulatorDevtoolsBounds`: the main renderer
468
- * measures the 'editor' dock slot's placeholder rect and publishes it so
469
- * main overlays the workbench WebContentsView precisely.
498
+ * Renderer main: the window-level placement snapshot (one monotonic epoch
499
+ * per commit tick, one generation per renderer lifetime) that drives the view
500
+ * reconciler. The single source of truth for every managed native view's
501
+ * bounds/visibility/z-order supersedes the per-view bounds channels above.
502
+ * invoke.
470
503
  */
471
- WorkbenchBounds: "view:workbench-bounds"
504
+ PlacementSnapshot: "view:placement-snapshot"
472
505
  };
473
506
  var PopoverChannel = {
474
507
  Show: "popover:show",
@@ -820,7 +853,7 @@ function registerProjectFsIpc(ctx) {
820
853
  // src/main/app/app.ts
821
854
  import { app as app15, BrowserWindow as BrowserWindow8, nativeImage, session as session4 } from "electron";
822
855
  import fs12 from "fs";
823
- import path22 from "path";
856
+ import path23 from "path";
824
857
 
825
858
  // src/main/utils/paths.ts
826
859
  import fs3 from "fs";
@@ -1107,10 +1140,341 @@ function createRendererNotifier(ctx) {
1107
1140
  };
1108
1141
  }
1109
1142
 
1110
- // src/main/services/views/view-manager.ts
1111
- import { ipcMain as ipcMain2, nativeTheme as nativeTheme3, shell as shell2, WebContentsView as WebContentsView2, webContents } from "electron";
1112
- import fs4 from "node:fs";
1113
- import path7 from "path";
1143
+ // src/main/services/safe-area/index.ts
1144
+ function guestInsets(device, isTabPage2) {
1145
+ const top = device?.safeAreaInsets.top ?? 0;
1146
+ const bottom = isTabPage2 ? 0 : device?.safeAreaInsets.bottom ?? 0;
1147
+ return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
1148
+ }
1149
+ function createSafeAreaController(options = {}) {
1150
+ const attached = /* @__PURE__ */ new Map();
1151
+ function override(wc, device, isTabPage2) {
1152
+ if (wc.isDestroyed()) return;
1153
+ void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device, isTabPage2) }).catch((err) => {
1154
+ console.warn("[safe-area] setSafeAreaInsetsOverride failed:", err instanceof Error ? err.message : err);
1155
+ });
1156
+ }
1157
+ return {
1158
+ applyToGuest: (wc, device, isTabPage2) => {
1159
+ if (!wc || wc.isDestroyed() || attached.has(wc)) {
1160
+ if (wc && !wc.isDestroyed() && attached.has(wc)) override(wc, device, isTabPage2);
1161
+ return;
1162
+ }
1163
+ try {
1164
+ wc.debugger.attach("1.3");
1165
+ } catch (err) {
1166
+ console.warn("[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:", err instanceof Error ? err.message : err);
1167
+ return;
1168
+ }
1169
+ attached.set(wc, isTabPage2);
1170
+ if (options.connections) {
1171
+ options.connections.acquire(wc).own(() => attached.delete(wc));
1172
+ } else {
1173
+ wc.once("destroyed", () => attached.delete(wc));
1174
+ }
1175
+ override(wc, device, isTabPage2);
1176
+ },
1177
+ reapplyAll: (device) => {
1178
+ for (const [wc, isTabPage2] of attached) override(wc, device, isTabPage2);
1179
+ },
1180
+ dispose: () => {
1181
+ for (const wc of attached.keys()) {
1182
+ try {
1183
+ if (!wc.isDestroyed()) wc.debugger.detach();
1184
+ } catch {
1185
+ }
1186
+ }
1187
+ attached.clear();
1188
+ }
1189
+ };
1190
+ }
1191
+
1192
+ // src/main/services/views/placement-reconciler.ts
1193
+ import { reconcile, createInitialState } from "@dimina-kit/electron-deck/layout";
1194
+
1195
+ // src/main/services/views/apply-view-ops.ts
1196
+ function applyViewOps(ops, target) {
1197
+ for (const op of ops) {
1198
+ switch (op.kind) {
1199
+ case "detach":
1200
+ target.detach(op.viewId);
1201
+ break;
1202
+ case "setVisible":
1203
+ target.setVisible(op.viewId, op.visible);
1204
+ break;
1205
+ case "setBounds":
1206
+ target.setBounds(op.viewId, op.bounds, op.extra);
1207
+ break;
1208
+ case "attach":
1209
+ target.attach(op.viewId);
1210
+ break;
1211
+ case "reorder":
1212
+ target.reorder(op.order);
1213
+ break;
1214
+ }
1215
+ }
1216
+ }
1217
+
1218
+ // src/main/services/views/placement-reconciler.ts
1219
+ function createPlacementReconciler(ctx) {
1220
+ let placementState = createInitialState();
1221
+ let epochCounter = 0;
1222
+ let rendererGeneration = 0;
1223
+ const baseDesired = /* @__PURE__ */ new Map();
1224
+ const overlayDesired = /* @__PURE__ */ new Map();
1225
+ const slots = /* @__PURE__ */ new Map();
1226
+ function gateReadiness(v) {
1227
+ const slot = slots.get(v.viewId);
1228
+ if (slot?.gateHidden?.()) return { ...v, placement: { visible: false } };
1229
+ return v;
1230
+ }
1231
+ const viewTarget = {
1232
+ attach(viewId) {
1233
+ if (ctx.windows.mainWindow.isDestroyed()) return;
1234
+ const slot = slots.get(viewId);
1235
+ if (!slot) return;
1236
+ if (slot.beforeAttach?.()) return;
1237
+ const view = slot.ensureView ? slot.ensureView() : slot.getView();
1238
+ if (!view) return;
1239
+ ctx.windows.mainWindow.contentView.addChildView(view);
1240
+ slot.setAdded?.(true);
1241
+ },
1242
+ detach(viewId) {
1243
+ const slot = slots.get(viewId);
1244
+ const view = slot?.getView() ?? null;
1245
+ if (view && !ctx.windows.mainWindow.isDestroyed()) {
1246
+ try {
1247
+ ctx.windows.mainWindow.contentView.removeChildView(view);
1248
+ } catch {
1249
+ }
1250
+ }
1251
+ slot?.setAdded?.(false);
1252
+ },
1253
+ setBounds(viewId, bounds, extra) {
1254
+ const slot = slots.get(viewId);
1255
+ const view = slot?.getView() ?? null;
1256
+ if (!view || view.webContents.isDestroyed()) return;
1257
+ if (slot?.applyBounds) {
1258
+ slot.applyBounds(view, bounds, extra);
1259
+ return;
1260
+ }
1261
+ view.setBounds(bounds);
1262
+ },
1263
+ setVisible(viewId, visible) {
1264
+ const view = slots.get(viewId)?.getView() ?? null;
1265
+ if (!view) return;
1266
+ try {
1267
+ view.setVisible(visible);
1268
+ } catch {
1269
+ }
1270
+ },
1271
+ reorder(order) {
1272
+ if (order.length <= 1 || ctx.windows.mainWindow.isDestroyed()) return;
1273
+ const cv = ctx.windows.mainWindow.contentView;
1274
+ for (const viewId of order) {
1275
+ const view = slots.get(viewId)?.getView() ?? null;
1276
+ if (view) {
1277
+ try {
1278
+ cv.addChildView(view);
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+ };
1285
+ function ensureLazyViews() {
1286
+ for (const [viewId, slot] of slots) {
1287
+ slot.ensureLazy?.(baseDesired.get(viewId));
1288
+ }
1289
+ }
1290
+ function reconcileNow() {
1291
+ ensureLazyViews();
1292
+ const views = [];
1293
+ for (const v of baseDesired.values()) views.push(gateReadiness(v));
1294
+ for (const v of overlayDesired.values()) views.push(v);
1295
+ const result = reconcile(placementState, {
1296
+ generation: rendererGeneration,
1297
+ epoch: ++epochCounter,
1298
+ views
1299
+ });
1300
+ placementState = result.state;
1301
+ applyViewOps(result.ops, viewTarget);
1302
+ }
1303
+ function setPlacementSnapshot(snapshot) {
1304
+ rendererGeneration = snapshot.generation;
1305
+ baseDesired.clear();
1306
+ for (const v of snapshot.views) baseDesired.set(v.viewId, v);
1307
+ reconcileNow();
1308
+ }
1309
+ return {
1310
+ registerView: (viewId, slot) => {
1311
+ slots.set(viewId, slot);
1312
+ },
1313
+ reconcileNow,
1314
+ setPlacementSnapshot,
1315
+ forgetActual: (viewId) => {
1316
+ placementState.actual.delete(viewId);
1317
+ },
1318
+ setBaseDesired: (viewId, desired) => {
1319
+ baseDesired.set(viewId, desired);
1320
+ },
1321
+ deleteBaseDesired: (viewId) => {
1322
+ baseDesired.delete(viewId);
1323
+ },
1324
+ setOverlayDesired: (viewId, desired) => {
1325
+ overlayDesired.set(viewId, desired);
1326
+ },
1327
+ deleteOverlayDesired: (viewId) => {
1328
+ overlayDesired.delete(viewId);
1329
+ },
1330
+ hasOverlayDesired: (viewId) => overlayDesired.has(viewId)
1331
+ };
1332
+ }
1333
+
1334
+ // src/main/services/views/workbench-view.ts
1335
+ import { nativeTheme as nativeTheme3, WebContentsView as WebContentsView2 } from "electron";
1336
+
1337
+ // src/shared/view-ids.ts
1338
+ var VIEW_ID = {
1339
+ simulator: "simulator",
1340
+ simulatorDevtools: "simulator-devtools",
1341
+ workbench: "workbench",
1342
+ hostToolbar: "host-toolbar",
1343
+ settings: "settings",
1344
+ popover: "popover"
1345
+ };
1346
+ var VIEW_LAYER = {
1347
+ base: 0,
1348
+ hostToolbar: 5,
1349
+ settings: 10,
1350
+ popover: 20
1351
+ };
1352
+
1353
+ // src/main/services/views/destroy-child-view.ts
1354
+ function destroyChildView(mainWindow, view) {
1355
+ if (!view) return;
1356
+ if (!mainWindow.isDestroyed()) {
1357
+ try {
1358
+ mainWindow.contentView.removeChildView(view);
1359
+ } catch {
1360
+ }
1361
+ }
1362
+ try {
1363
+ if (!view.webContents.isDestroyed()) {
1364
+ view.webContents.close();
1365
+ }
1366
+ } catch {
1367
+ }
1368
+ }
1369
+
1370
+ // src/main/services/views/workbench-view.ts
1371
+ function createWorkbenchView(ctx, reconciler) {
1372
+ let workbenchView = null;
1373
+ let workbenchThemeSyncBound = false;
1374
+ let workbenchUrl = null;
1375
+ function workbenchThemeScheme() {
1376
+ return nativeTheme3.shouldUseDarkColors ? "dark" : "light";
1377
+ }
1378
+ function pushWorkbenchTheme() {
1379
+ if (!workbenchView || workbenchView.webContents.isDestroyed()) return;
1380
+ const wc = workbenchView.webContents;
1381
+ if (typeof wc.executeJavaScript !== "function") return;
1382
+ const script = `window.__WB_SET_THEME && window.__WB_SET_THEME(${JSON.stringify(workbenchThemeScheme())})`;
1383
+ wc.executeJavaScript(script, true).catch(() => {
1384
+ });
1385
+ }
1386
+ async function attachWorkbench(url) {
1387
+ if (workbenchView) return;
1388
+ const view = new WebContentsView2({
1389
+ webPreferences: {
1390
+ nodeIntegration: false,
1391
+ contextIsolation: true,
1392
+ sandbox: false
1393
+ }
1394
+ });
1395
+ workbenchView = view;
1396
+ if (!workbenchThemeSyncBound) {
1397
+ nativeTheme3.on("updated", pushWorkbenchTheme);
1398
+ workbenchThemeSyncBound = true;
1399
+ }
1400
+ try {
1401
+ view.webContents.setWindowOpenHandler(({ url: target }) => handleWindowOpenExternal(target));
1402
+ } catch {
1403
+ }
1404
+ ctx.windows.mainWindow.contentView.addChildView(view);
1405
+ const loadUrl = `${url}index.html?theme=${workbenchThemeScheme()}`;
1406
+ await view.webContents.loadURL(loadUrl).catch((err) => {
1407
+ console.error("[workbench] attachWorkbench \u2014 loadURL failed", err);
1408
+ });
1409
+ }
1410
+ function setWorkbenchSource(url) {
1411
+ workbenchUrl = url;
1412
+ }
1413
+ function detachWorkbench() {
1414
+ if (workbenchThemeSyncBound) {
1415
+ nativeTheme3.removeListener("updated", pushWorkbenchTheme);
1416
+ workbenchThemeSyncBound = false;
1417
+ }
1418
+ destroyChildView(ctx.windows.mainWindow, workbenchView);
1419
+ workbenchView = null;
1420
+ reconciler.deleteBaseDesired(VIEW_ID.workbench);
1421
+ reconciler.reconcileNow();
1422
+ }
1423
+ function workspaceUriFor(rel) {
1424
+ const encoded = rel.split("/").map(encodeURIComponent).join("/");
1425
+ return `file:///workspace/${encoded}`;
1426
+ }
1427
+ function tryRevealInWorkbench(uri, zeroLine, zeroCol) {
1428
+ if (!workbenchView || workbenchView.webContents.isDestroyed()) {
1429
+ return Promise.resolve(false);
1430
+ }
1431
+ const script = `(async () => {
1432
+ try {
1433
+ const P = window.__WB_PROBE; if (!P) return false
1434
+ const vscode = P.vscode
1435
+ const uri = vscode.Uri.parse(${JSON.stringify(uri)})
1436
+ const doc = await vscode.workspace.openTextDocument(uri)
1437
+ const pos = new vscode.Position(${zeroLine}, ${zeroCol})
1438
+ await vscode.window.showTextDocument(doc, { selection: new vscode.Range(pos, pos) })
1439
+ return true
1440
+ } catch (e) { return false }
1441
+ })()`;
1442
+ return workbenchView.webContents.executeJavaScript(script, true).then((ok) => ok === true).catch(() => false);
1443
+ }
1444
+ function openFileInWorkbench(relPath, line, column) {
1445
+ if (!workbenchView || workbenchView.webContents.isDestroyed()) return false;
1446
+ const uri = workspaceUriFor(relPath.replace(/^\/+/, ""));
1447
+ const zeroLine = Math.max(0, Math.floor(line) - 1);
1448
+ const zeroCol = Math.max(0, Math.floor(column) - 1);
1449
+ void (async () => {
1450
+ for (let attempt = 0; attempt < 10; attempt++) {
1451
+ if (!workbenchView || workbenchView.webContents.isDestroyed()) return;
1452
+ if (await tryRevealInWorkbench(uri, zeroLine, zeroCol)) return;
1453
+ await new Promise((resolve) => setTimeout(resolve, 150));
1454
+ }
1455
+ console.error("[workbench] openFileInWorkbench: workbench never became ready for", uri);
1456
+ })();
1457
+ return true;
1458
+ }
1459
+ reconciler.registerView(VIEW_ID.workbench, {
1460
+ getView: () => workbenchView,
1461
+ gateHidden: () => !workbenchView && !workbenchUrl,
1462
+ beforeAttach: () => {
1463
+ if (!workbenchView && workbenchUrl) {
1464
+ void attachWorkbench(workbenchUrl);
1465
+ return true;
1466
+ }
1467
+ return false;
1468
+ },
1469
+ ensureLazy: (desired) => {
1470
+ if (desired?.placement.visible && !workbenchView && workbenchUrl) void attachWorkbench(workbenchUrl);
1471
+ }
1472
+ });
1473
+ return { attachWorkbench, setWorkbenchSource, detachWorkbench, openFileInWorkbench };
1474
+ }
1475
+
1476
+ // src/main/services/views/native-simulator-devtools-host.ts
1477
+ import { WebContentsView as WebContentsView3 } from "electron";
1114
1478
 
1115
1479
  // src/shared/open-in-editor.ts
1116
1480
  var OPEN_IN_EDITOR_SCHEME = "dimina-open-in-editor";
@@ -1484,55 +1848,6 @@ function buildDevtoolsProjectSourceLinksScript(context) {
1484
1848
  `;
1485
1849
  }
1486
1850
 
1487
- // src/main/services/safe-area/index.ts
1488
- function guestInsets(device, isTabPage2) {
1489
- const top = device?.safeAreaInsets.top ?? 0;
1490
- const bottom = isTabPage2 ? 0 : device?.safeAreaInsets.bottom ?? 0;
1491
- return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
1492
- }
1493
- function createSafeAreaController(options = {}) {
1494
- const attached = /* @__PURE__ */ new Map();
1495
- function override(wc, device, isTabPage2) {
1496
- if (wc.isDestroyed()) return;
1497
- void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device, isTabPage2) }).catch((err) => {
1498
- console.warn("[safe-area] setSafeAreaInsetsOverride failed:", err instanceof Error ? err.message : err);
1499
- });
1500
- }
1501
- return {
1502
- applyToGuest: (wc, device, isTabPage2) => {
1503
- if (!wc || wc.isDestroyed() || attached.has(wc)) {
1504
- if (wc && !wc.isDestroyed() && attached.has(wc)) override(wc, device, isTabPage2);
1505
- return;
1506
- }
1507
- try {
1508
- wc.debugger.attach("1.3");
1509
- } catch (err) {
1510
- console.warn("[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:", err instanceof Error ? err.message : err);
1511
- return;
1512
- }
1513
- attached.set(wc, isTabPage2);
1514
- if (options.connections) {
1515
- options.connections.acquire(wc).own(() => attached.delete(wc));
1516
- } else {
1517
- wc.once("destroyed", () => attached.delete(wc));
1518
- }
1519
- override(wc, device, isTabPage2);
1520
- },
1521
- reapplyAll: (device) => {
1522
- for (const [wc, isTabPage2] of attached) override(wc, device, isTabPage2);
1523
- },
1524
- dispose: () => {
1525
- for (const wc of attached.keys()) {
1526
- try {
1527
- if (!wc.isDestroyed()) wc.debugger.detach();
1528
- } catch {
1529
- }
1530
- }
1531
- attached.clear();
1532
- }
1533
- };
1534
- }
1535
-
1536
1851
  // src/main/services/views/devtools-tabs.ts
1537
1852
  var DEVTOOLS_KEPT_VIEW_IDS = ["elements", "console", "network", "sources"];
1538
1853
  function buildCustomizeTabsScript(keptIds = DEVTOOLS_KEPT_VIEW_IDS) {
@@ -1602,6 +1917,44 @@ function buildCustomizeTabsScript(keptIds = DEVTOOLS_KEPT_VIEW_IDS) {
1602
1917
 
1603
1918
  // src/main/services/elements-forward/index.ts
1604
1919
  import { webContents as electronWebContents } from "electron";
1920
+
1921
+ // src/main/services/views/inject-when-ready.ts
1922
+ function isFrontendSettled(wc) {
1923
+ try {
1924
+ if (wc.isDestroyed()) return false;
1925
+ const url = wc.getURL();
1926
+ const mainFrameLoading = typeof wc.isLoadingMainFrame === "function" ? wc.isLoadingMainFrame() : wc.isLoading();
1927
+ return !mainFrameLoading && url !== "" && url !== "about:blank";
1928
+ } catch {
1929
+ return false;
1930
+ }
1931
+ }
1932
+ function createLoadDeferredInjector() {
1933
+ const pending = /* @__PURE__ */ new WeakMap();
1934
+ return function injectWhenReady(wc, kind, run) {
1935
+ if (wc.isDestroyed()) return;
1936
+ if (isFrontendSettled(wc)) {
1937
+ run();
1938
+ return;
1939
+ }
1940
+ let kinds = pending.get(wc);
1941
+ if (!kinds) {
1942
+ kinds = /* @__PURE__ */ new Map();
1943
+ pending.set(wc, kinds);
1944
+ }
1945
+ const alreadyHooked = kinds.has(kind);
1946
+ kinds.set(kind, run);
1947
+ if (alreadyHooked) return;
1948
+ wc.once("did-stop-loading", () => {
1949
+ const runner = kinds.get(kind);
1950
+ kinds.delete(kind);
1951
+ if (!runner || wc.isDestroyed()) return;
1952
+ runner();
1953
+ });
1954
+ };
1955
+ }
1956
+
1957
+ // src/main/services/elements-forward/index.ts
1605
1958
  var RENDER_DOMAIN_PREFIXES = [
1606
1959
  "DOM.",
1607
1960
  "CSS.",
@@ -1718,6 +2071,7 @@ function installElementsForward(deps) {
1718
2071
  }
1719
2072
  function dispatchToFrontend(message) {
1720
2073
  if (disposed || devtoolsWc.isDestroyed()) return;
2074
+ if (!isFrontendSettled(devtoolsWc)) return;
1721
2075
  let json;
1722
2076
  try {
1723
2077
  json = JSON.stringify(message);
@@ -1750,6 +2104,7 @@ function installElementsForward(deps) {
1750
2104
  }
1751
2105
  function pushDocumentUpdated() {
1752
2106
  if (disposed || devtoolsWc.isDestroyed()) return;
2107
+ if (!isFrontendSettled(devtoolsWc)) return;
1753
2108
  let script;
1754
2109
  try {
1755
2110
  script = buildDocumentUpdatedScript();
@@ -1922,6 +2277,7 @@ function installElementsForward(deps) {
1922
2277
  stop();
1923
2278
  return;
1924
2279
  }
2280
+ if (!isFrontendSettled(devtoolsWc)) return;
1925
2281
  devtoolsWc.executeJavaScript(buildReconcileScript()).then((res) => {
1926
2282
  if (disposed) return;
1927
2283
  const r = res ?? {};
@@ -2106,191 +2462,380 @@ function installServiceConsoleForward(deps) {
2106
2462
  return { stop };
2107
2463
  }
2108
2464
 
2109
- // src/main/services/layout/index.ts
2110
- function computeNativeSimulatorViewParams(rect, zoomPercent) {
2111
- const zoomFactor = zoomPercent / 100;
2112
- return {
2113
- bounds: {
2114
- x: Math.round(rect.x),
2115
- y: Math.round(rect.y),
2116
- width: Math.max(0, Math.round(rect.width)),
2117
- height: Math.max(0, Math.round(rect.height))
2118
- },
2119
- zoomFactor
2120
- };
2121
- }
2122
- function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
2123
- return {
2124
- x: 0,
2125
- y: headerHeight,
2126
- width: Math.max(1, contentWidth),
2127
- height: Math.max(1, contentHeight - headerHeight)
2128
- };
2129
- }
2130
- function computePopoverBounds(contentWidth, contentHeight, headerHeight) {
2131
- return {
2132
- x: 0,
2133
- y: headerHeight,
2134
- width: Math.max(1, contentWidth),
2135
- height: contentHeight - headerHeight
2136
- };
2137
- }
2138
-
2139
- // src/main/services/simulator/custom-apis.ts
2140
- async function handleCustomApiBridgeRequest(apis, req) {
2465
+ // src/main/services/views/resolve-project-editor-target.ts
2466
+ import fs4 from "node:fs";
2467
+ import path7 from "path";
2468
+ function resolveProjectEditorTarget(serviceHostUrl, activeProjectRoot, req, isFile = (absolutePath) => fs4.statSync(absolutePath).isFile()) {
2469
+ const sourceContext = projectSourceContextFromServiceHostUrl(
2470
+ serviceHostUrl,
2471
+ activeProjectRoot
2472
+ );
2473
+ if (!sourceContext) return null;
2474
+ const rel = resourceUrlToProjectRelativePath(req.url, sourceContext);
2475
+ if (!rel) return null;
2476
+ const absolute = path7.resolve(sourceContext.projectRoot, ...rel.split("/"));
2477
+ const fromRoot = path7.relative(path7.resolve(sourceContext.projectRoot), absolute);
2478
+ if (!fromRoot || fromRoot.startsWith("..") || path7.isAbsolute(fromRoot)) return null;
2141
2479
  try {
2142
- const result = req.op === "list" ? apis.list() : await apis.invoke(req.name, req.params);
2143
- return { id: req.id, result };
2144
- } catch (err) {
2145
- return { id: req.id, error: err instanceof Error ? err.message : String(err) };
2480
+ if (!isFile(absolute)) return null;
2481
+ } catch {
2482
+ return null;
2146
2483
  }
2147
- }
2148
- function createSimulatorApiRegistry() {
2149
- const handlers2 = /* @__PURE__ */ new Map();
2150
2484
  return {
2151
- register(name, handler) {
2152
- handlers2.set(name, handler);
2153
- return () => {
2154
- if (handlers2.get(name) === handler) handlers2.delete(name);
2155
- };
2156
- },
2157
- list() {
2158
- return Array.from(handlers2.keys());
2159
- },
2160
- has(name) {
2161
- return handlers2.has(name);
2162
- },
2163
- async invoke(name, params) {
2164
- const handler = handlers2.get(name);
2165
- if (!handler) throw new Error(`Simulator API "${name}" is not registered`);
2166
- return await handler(params);
2167
- },
2168
- clear() {
2169
- handlers2.clear();
2170
- }
2485
+ path: rel,
2486
+ line: typeof req.line === "number" ? req.line + 1 : void 0,
2487
+ column: typeof req.column === "number" ? req.column + 1 : void 0
2171
2488
  };
2172
2489
  }
2173
2490
 
2174
- // src/main/services/views/miniapp-partition.ts
2175
- import * as electron from "electron";
2176
- var SHARED_MINIAPP_PARTITION = "persist:simulator";
2177
- var PARTITION_PREFIX = "persist:miniapp-";
2178
- function miniappPartitionKey(appId, projectPath) {
2179
- const safe = appId.replace(/[^A-Za-z0-9_-]/g, "");
2180
- const base = safe === appId && safe.length > 0 ? safe : safe.length > 0 ? `${safe}-${djb2(appId).toString(36)}` : djb2(appId).toString(36);
2181
- if (!projectPath) return base;
2182
- return `${base}-p${djb2(projectPath).toString(36)}`;
2183
- }
2184
- function miniappPartition(appId, projectPath) {
2185
- if (!appId) return SHARED_MINIAPP_PARTITION;
2186
- return `${PARTITION_PREFIX}${miniappPartitionKey(appId, projectPath)}`;
2187
- }
2188
- function djb2(input) {
2189
- let h = 5381;
2190
- for (let i = 0; i < input.length; i++) {
2191
- h = (h << 5) + h + input.charCodeAt(i) >>> 0;
2491
+ // src/main/services/views/native-simulator-devtools-host.ts
2492
+ var DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS = 50;
2493
+ function raiseExecuteJavaScriptListenerCeiling(wc) {
2494
+ try {
2495
+ if (wc.isDestroyed()) return;
2496
+ if (wc.getMaxListeners() >= DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS) return;
2497
+ wc.setMaxListeners(DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS);
2498
+ } catch {
2192
2499
  }
2193
- return h;
2194
2500
  }
2195
- var configurators = /* @__PURE__ */ new Set();
2196
- var configuredPartitions = /* @__PURE__ */ new Set();
2197
- function registerMiniappSessionConfigurator(fn) {
2198
- configurators.add(fn);
2199
- for (const partition of configuredPartitions) {
2200
- try {
2201
- fn(electron.session.fromPartition(partition), partition);
2202
- } catch (err) {
2203
- console.warn("[miniapp-partition] configurator failed for", partition, err);
2501
+ function createDevtoolsHost(ctx, reconciler, deps) {
2502
+ let simulatorView = null;
2503
+ let simulatorViewAdded = false;
2504
+ let nativeDevtoolsSourceWc = null;
2505
+ let devtoolsHostUsed = false;
2506
+ let unsubscribeNativeRenderEvents = null;
2507
+ let stopElementsForward = null;
2508
+ let stopServiceConsole = null;
2509
+ let nativeDevtoolsRetryTimer = null;
2510
+ let nativeDevtoolsRetryToken = 0;
2511
+ const injectWhenReady = createLoadDeferredInjector();
2512
+ const openInEditorWiredWcIds = /* @__PURE__ */ new Set();
2513
+ function clearNativeDevtoolsRetry() {
2514
+ nativeDevtoolsRetryToken++;
2515
+ if (nativeDevtoolsRetryTimer) {
2516
+ clearTimeout(nativeDevtoolsRetryTimer);
2517
+ nativeDevtoolsRetryTimer = null;
2204
2518
  }
2205
2519
  }
2206
- return () => {
2207
- configurators.delete(fn);
2208
- };
2209
- }
2210
- function configureMiniappSession(partition) {
2211
- const alreadyConfigured = configuredPartitions.has(partition);
2212
- configuredPartitions.add(partition);
2213
- if (configurators.size === 0) return null;
2214
- const sess = electron.session.fromPartition(partition);
2215
- if (alreadyConfigured) return sess;
2216
- for (const fn of configurators) {
2520
+ function closeNativeDevtoolsSource() {
2521
+ const source = nativeDevtoolsSourceWc;
2522
+ nativeDevtoolsSourceWc = null;
2217
2523
  try {
2218
- fn(sess, partition);
2219
- } catch (err) {
2220
- console.warn("[miniapp-partition] configurator failed for", partition, err);
2524
+ stopServiceConsole?.();
2525
+ } catch {
2221
2526
  }
2222
- }
2223
- return sess;
2224
- }
2225
-
2226
- // src/main/services/views/host-toolbar-session-runtime.ts
2227
- import { session as session2 } from "electron";
2228
- var refCount = 0;
2229
- var registrationId = null;
2230
- function acquireHostToolbarSessionRuntime() {
2231
- if (refCount === 0) {
2232
- registrationId = session2.defaultSession.registerPreloadScript({
2233
- type: "frame",
2234
- filePath: hostToolbarRuntimePreloadPath
2235
- });
2236
- }
2237
- refCount++;
2238
- }
2239
- function releaseHostToolbarSessionRuntime() {
2240
- if (refCount === 0) return;
2241
- refCount--;
2242
- if (refCount === 0 && registrationId !== null) {
2243
- session2.defaultSession.unregisterPreloadScript(registrationId);
2244
- registrationId = null;
2245
- }
2246
- }
2247
-
2248
- // src/main/services/views/host-toolbar-port-channel.ts
2249
- import { MessageChannelMain } from "electron";
2250
- function createHostToolbarPortChannel(opts) {
2251
- let activePort = null;
2252
- let activeWc = null;
2253
- let state = "absent";
2254
- let generation = 0;
2255
- const handlers2 = [];
2256
- const readyHandlers = [];
2257
- function dispatch(data) {
2258
- if (typeof data !== "object" || data === null) return;
2259
- const { channel, payload } = data;
2260
- if (typeof channel !== "string") return;
2261
- for (const entry of [...handlers2]) {
2262
- if (entry.channel === channel) entry.handler(payload);
2527
+ stopServiceConsole = null;
2528
+ if (!source || source.isDestroyed()) return;
2529
+ try {
2530
+ if (source.isDevToolsOpened()) {
2531
+ source.closeDevTools();
2532
+ }
2533
+ } catch {
2263
2534
  }
2264
2535
  }
2265
- function dropActivePort(close, next) {
2266
- const port = activePort;
2267
- activePort = null;
2268
- activeWc = null;
2269
- if (state !== "disposed") state = next;
2270
- if (close && port) {
2536
+ function stopFollowingNativeServiceHost() {
2537
+ clearNativeDevtoolsRetry();
2538
+ if (unsubscribeNativeRenderEvents) {
2271
2539
  try {
2272
- port.close();
2540
+ unsubscribeNativeRenderEvents();
2273
2541
  } catch {
2274
2542
  }
2543
+ unsubscribeNativeRenderEvents = null;
2275
2544
  }
2545
+ closeNativeDevtoolsSource();
2276
2546
  }
2277
- function invokeReadyHandler(handler) {
2547
+ function injectOpenResourceHandler(serviceWc, devtoolsWc) {
2548
+ if (serviceWc.isDestroyed() || devtoolsWc.isDestroyed()) return;
2278
2549
  try {
2279
- handler();
2280
- } catch (err) {
2281
- console.error("[host-toolbar] onReady handler threw:", err);
2550
+ const sourceContext = projectSourceContextFromServiceHostUrl(
2551
+ serviceWc.getURL(),
2552
+ ctx.workspace?.getProjectPath?.()
2553
+ );
2554
+ if (!sourceContext) return;
2555
+ devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => {
2556
+ });
2557
+ } catch {
2282
2558
  }
2283
2559
  }
2284
- function fireReadyHandlers() {
2285
- for (const entry of [...readyHandlers]) {
2286
- if (readyHandlers.includes(entry)) invokeReadyHandler(entry.handler);
2560
+ function customizeDevtoolsTabs(devtoolsWc) {
2561
+ try {
2562
+ if (devtoolsWc.isDestroyed()) return;
2563
+ const inject = () => {
2564
+ if (devtoolsWc.isDestroyed()) return;
2565
+ try {
2566
+ void devtoolsWc.executeJavaScript(buildCustomizeTabsScript()).catch(() => {
2567
+ });
2568
+ } catch {
2569
+ }
2570
+ };
2571
+ injectWhenReady(devtoolsWc, "customize-tabs", inject);
2572
+ } catch {
2287
2573
  }
2288
2574
  }
2289
- function handshake(wc) {
2290
- if (state === "disposed") return;
2291
- if (!opts.isCurrent(wc)) return;
2292
- dropActivePort(true, "awaitingHandshake");
2293
- const { port1, port2 } = new MessageChannelMain();
2575
+ function wireOpenInEditor(serviceWc, devtoolsWc) {
2576
+ if (!devtoolsWc.isDestroyed()) {
2577
+ injectWhenReady(devtoolsWc, "open-in-editor", () => injectOpenResourceHandler(serviceWc, devtoolsWc));
2578
+ }
2579
+ if (openInEditorWiredWcIds.has(serviceWc.id)) return;
2580
+ openInEditorWiredWcIds.add(serviceWc.id);
2581
+ const onOpenUrl = (_event, url) => {
2582
+ const req = decodeOpenInEditorUrl(url);
2583
+ if (!req) return;
2584
+ const target = resolveProjectEditorTarget(
2585
+ serviceWc.getURL(),
2586
+ ctx.workspace?.getProjectPath?.(),
2587
+ req
2588
+ );
2589
+ if (!target) return;
2590
+ deps.openFileInWorkbench(target.path, target.line ?? 1, target.column ?? 1);
2591
+ ctx.notify.editorOpenFile(target);
2592
+ };
2593
+ serviceWc.on("devtools-open-url", onOpenUrl);
2594
+ const conn = ctx.connections.acquire(serviceWc);
2595
+ conn.on("closed", () => {
2596
+ openInEditorWiredWcIds.delete(serviceWc.id);
2597
+ try {
2598
+ serviceWc.removeListener("devtools-open-url", onOpenUrl);
2599
+ } catch {
2600
+ }
2601
+ });
2602
+ }
2603
+ function pointNativeDevtoolsAtServiceWc(next) {
2604
+ if (!simulatorView || simulatorView.webContents.isDestroyed()) return true;
2605
+ if (nativeDevtoolsSourceWc?.id === next.id && !nativeDevtoolsSourceWc.isDestroyed()) {
2606
+ return true;
2607
+ }
2608
+ closeNativeDevtoolsSource();
2609
+ nativeDevtoolsSourceWc = next;
2610
+ if (devtoolsHostUsed) {
2611
+ rebuildDevtoolsHostView();
2612
+ }
2613
+ if (!simulatorView || simulatorView.webContents.isDestroyed()) return true;
2614
+ try {
2615
+ next.setDevToolsWebContents(simulatorView.webContents);
2616
+ next.openDevTools({ mode: "detach", activate: false });
2617
+ devtoolsHostUsed = true;
2618
+ wireOpenInEditor(next, simulatorView.webContents);
2619
+ customizeDevtoolsTabs(simulatorView.webContents);
2620
+ try {
2621
+ stopServiceConsole?.();
2622
+ } catch {
2623
+ }
2624
+ stopServiceConsole = installServiceConsoleForward({
2625
+ serviceWc: next,
2626
+ connections: ctx.connections,
2627
+ emit: (entry) => ctx.consoleForwarder?.emit(entry)
2628
+ }).stop;
2629
+ return true;
2630
+ } catch (err) {
2631
+ console.warn("[devtools-host] point-at-service-wc failed; retry will rebuild:", err);
2632
+ if (nativeDevtoolsSourceWc?.id === next.id) {
2633
+ nativeDevtoolsSourceWc = null;
2634
+ }
2635
+ return false;
2636
+ }
2637
+ }
2638
+ function pointNativeDevtoolsAtActiveServiceHost(appId) {
2639
+ if (!ctx.bridge?.isNativeHost()) return true;
2640
+ if (!simulatorView || simulatorView.webContents.isDestroyed()) return true;
2641
+ const wc = ctx.bridge.getServiceWc(appId);
2642
+ if (!wc || wc.isDestroyed()) return false;
2643
+ return pointNativeDevtoolsAtServiceWc(wc);
2644
+ }
2645
+ function scheduleNativeDevtoolsFollow(appId, attempt = 0) {
2646
+ if (attempt >= 20) return;
2647
+ if (!ctx.bridge?.isNativeHost()) return;
2648
+ const token = nativeDevtoolsRetryToken;
2649
+ if (nativeDevtoolsRetryTimer) clearTimeout(nativeDevtoolsRetryTimer);
2650
+ nativeDevtoolsRetryTimer = setTimeout(() => {
2651
+ nativeDevtoolsRetryTimer = null;
2652
+ if (token !== nativeDevtoolsRetryToken) return;
2653
+ if (pointNativeDevtoolsAtActiveServiceHost(appId)) return;
2654
+ scheduleNativeDevtoolsFollow(appId, attempt + 1);
2655
+ }, 50);
2656
+ }
2657
+ function followNativeDevtoolsServiceHost(appId) {
2658
+ clearNativeDevtoolsRetry();
2659
+ if (pointNativeDevtoolsAtActiveServiceHost(appId)) return;
2660
+ scheduleNativeDevtoolsFollow(appId);
2661
+ }
2662
+ function onNativeRenderEvent(event) {
2663
+ if (event.kind !== "activePage" && event.kind !== "domReady") return;
2664
+ followNativeDevtoolsServiceHost(event.appId);
2665
+ }
2666
+ function rebuildDevtoolsHostView() {
2667
+ if (simulatorView) {
2668
+ removeSimulatorDevtoolsView();
2669
+ try {
2670
+ if (!simulatorView.webContents.isDestroyed()) {
2671
+ simulatorView.webContents.close();
2672
+ }
2673
+ } catch {
2674
+ }
2675
+ simulatorView = null;
2676
+ }
2677
+ simulatorView = new WebContentsView3();
2678
+ const devtoolsWc = simulatorView.webContents;
2679
+ raiseExecuteJavaScriptListenerCeiling(devtoolsWc);
2680
+ ctx.networkForward?.setDevtoolsHost(devtoolsWc);
2681
+ if (ctx.bridge) {
2682
+ try {
2683
+ stopElementsForward?.();
2684
+ } catch {
2685
+ }
2686
+ const thisForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
2687
+ stopElementsForward = thisForward;
2688
+ devtoolsWc.once("destroyed", () => {
2689
+ try {
2690
+ thisForward();
2691
+ } catch {
2692
+ }
2693
+ if (stopElementsForward === thisForward) stopElementsForward = null;
2694
+ });
2695
+ }
2696
+ injectWhenReady(devtoolsWc, "console-default", () => {
2697
+ devtoolsWc.executeJavaScript(`
2698
+ (function() {
2699
+ try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
2700
+ let tries = 0
2701
+ const timer = setInterval(() => {
2702
+ tries++
2703
+ try {
2704
+ const UI = globalThis.UI
2705
+ const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
2706
+ ? UI.ViewManager.instance()
2707
+ : null
2708
+ if (vm && typeof vm.showView === 'function') {
2709
+ vm.showView('console')
2710
+ clearInterval(timer)
2711
+ return
2712
+ }
2713
+ } catch {}
2714
+ if (tries > 80) clearInterval(timer)
2715
+ }, 50)
2716
+ })()
2717
+ `).catch(() => {
2718
+ });
2719
+ });
2720
+ reconciler.reconcileNow();
2721
+ devtoolsHostUsed = false;
2722
+ }
2723
+ function attachNativeSimulatorDevtoolsHost() {
2724
+ stopFollowingNativeServiceHost();
2725
+ rebuildDevtoolsHostView();
2726
+ if (ctx.bridge?.isNativeHost()) {
2727
+ unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
2728
+ followNativeDevtoolsServiceHost();
2729
+ }
2730
+ }
2731
+ function removeSimulatorDevtoolsView() {
2732
+ if (simulatorView && simulatorViewAdded) {
2733
+ try {
2734
+ ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
2735
+ } catch (e) {
2736
+ console.error("[workbench] removeSimulatorDevtoolsView error", e);
2737
+ }
2738
+ simulatorViewAdded = false;
2739
+ }
2740
+ reconciler.forgetActual(VIEW_ID.simulatorDevtools);
2741
+ }
2742
+ reconciler.registerView(VIEW_ID.simulatorDevtools, {
2743
+ getView: () => simulatorView,
2744
+ setAdded: (added) => {
2745
+ simulatorViewAdded = added;
2746
+ },
2747
+ gateHidden: () => !simulatorView
2748
+ });
2749
+ return {
2750
+ attach: attachNativeSimulatorDevtoolsHost,
2751
+ followServiceHost: followNativeDevtoolsServiceHost,
2752
+ stopFollowing: stopFollowingNativeServiceHost,
2753
+ stopElementsForwarding: () => {
2754
+ try {
2755
+ stopElementsForward?.();
2756
+ } catch {
2757
+ }
2758
+ stopElementsForward = null;
2759
+ },
2760
+ destroyHostView: () => {
2761
+ destroyChildView(ctx.windows.mainWindow, simulatorView);
2762
+ simulatorView = null;
2763
+ simulatorViewAdded = false;
2764
+ }
2765
+ };
2766
+ }
2767
+
2768
+ // src/main/services/views/host-toolbar-view.ts
2769
+ import { WebContentsView as WebContentsView4 } from "electron";
2770
+
2771
+ // src/main/services/views/host-toolbar-session-runtime.ts
2772
+ import { session } from "electron";
2773
+ var refCount = 0;
2774
+ var registrationId = null;
2775
+ function acquireHostToolbarSessionRuntime() {
2776
+ if (refCount === 0) {
2777
+ registrationId = session.defaultSession.registerPreloadScript({
2778
+ type: "frame",
2779
+ filePath: hostToolbarRuntimePreloadPath
2780
+ });
2781
+ }
2782
+ refCount++;
2783
+ }
2784
+ function releaseHostToolbarSessionRuntime() {
2785
+ if (refCount === 0) return;
2786
+ refCount--;
2787
+ if (refCount === 0 && registrationId !== null) {
2788
+ session.defaultSession.unregisterPreloadScript(registrationId);
2789
+ registrationId = null;
2790
+ }
2791
+ }
2792
+
2793
+ // src/main/services/views/host-toolbar-port-channel.ts
2794
+ import { MessageChannelMain } from "electron";
2795
+ function createHostToolbarPortChannel(opts) {
2796
+ let activePort = null;
2797
+ let activeWc = null;
2798
+ let state = "absent";
2799
+ let generation = 0;
2800
+ const handlers2 = [];
2801
+ const readyHandlers = [];
2802
+ function dispatch(data) {
2803
+ if (typeof data !== "object" || data === null) return;
2804
+ const { channel, payload } = data;
2805
+ if (typeof channel !== "string") return;
2806
+ for (const entry of [...handlers2]) {
2807
+ if (entry.channel === channel) entry.handler(payload);
2808
+ }
2809
+ }
2810
+ function dropActivePort(close, next) {
2811
+ const port = activePort;
2812
+ activePort = null;
2813
+ activeWc = null;
2814
+ if (state !== "disposed") state = next;
2815
+ if (close && port) {
2816
+ try {
2817
+ port.close();
2818
+ } catch {
2819
+ }
2820
+ }
2821
+ }
2822
+ function invokeReadyHandler(handler) {
2823
+ try {
2824
+ handler();
2825
+ } catch (err) {
2826
+ console.error("[host-toolbar] onReady handler threw:", err);
2827
+ }
2828
+ }
2829
+ function fireReadyHandlers() {
2830
+ for (const entry of [...readyHandlers]) {
2831
+ if (readyHandlers.includes(entry)) invokeReadyHandler(entry.handler);
2832
+ }
2833
+ }
2834
+ function handshake(wc) {
2835
+ if (state === "disposed") return;
2836
+ if (!opts.isCurrent(wc)) return;
2837
+ dropActivePort(true, "awaitingHandshake");
2838
+ const { port1, port2 } = new MessageChannelMain();
2294
2839
  port1.on("message", (event) => {
2295
2840
  dispatch(event.data);
2296
2841
  });
@@ -2376,90 +2921,8 @@ function createHostToolbarPortChannel(opts) {
2376
2921
  };
2377
2922
  }
2378
2923
 
2379
- // src/shared/simulator-route.ts
2380
- function decodePageSpec(value) {
2381
- const qIdx = value.indexOf("?");
2382
- if (qIdx < 0) return { pagePath: value, query: {} };
2383
- const pagePath = value.slice(0, qIdx);
2384
- const query = {};
2385
- for (const pair of value.slice(qIdx + 1).split("&")) {
2386
- if (!pair) continue;
2387
- const eqIdx = pair.indexOf("=");
2388
- const k = eqIdx >= 0 ? pair.slice(0, eqIdx) : pair;
2389
- const v = eqIdx >= 0 ? pair.slice(eqIdx + 1) : "";
2390
- if (k) query[decodeURIComponent(k)] = decodeURIComponent(v);
2391
- }
2392
- return { pagePath, query };
2393
- }
2394
- function parseLocationRoute(search) {
2395
- const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
2396
- const appId = params.get("appId");
2397
- const entryRaw = params.get("entry");
2398
- if (!appId || !entryRaw) return null;
2399
- const entry = decodePageSpec(entryRaw);
2400
- const pageRaw = params.get("page");
2401
- const current = pageRaw && pageRaw !== entryRaw ? decodePageSpec(pageRaw) : entry;
2402
- return { appId, entry, current };
2403
- }
2404
- function parseRoute(url) {
2405
- if (!url) return null;
2406
- try {
2407
- const u = new URL(url);
2408
- return parseLocationRoute(u.search);
2409
- } catch {
2410
- return null;
2411
- }
2412
- }
2413
-
2414
- // src/main/services/views/view-manager.ts
2415
- function resolveProjectEditorTarget(serviceHostUrl, activeProjectRoot, req, isFile = (absolutePath) => fs4.statSync(absolutePath).isFile()) {
2416
- const sourceContext = projectSourceContextFromServiceHostUrl(
2417
- serviceHostUrl,
2418
- activeProjectRoot
2419
- );
2420
- if (!sourceContext) return null;
2421
- const rel = resourceUrlToProjectRelativePath(req.url, sourceContext);
2422
- if (!rel) return null;
2423
- const absolute = path7.resolve(sourceContext.projectRoot, ...rel.split("/"));
2424
- const fromRoot = path7.relative(path7.resolve(sourceContext.projectRoot), absolute);
2425
- if (!fromRoot || fromRoot.startsWith("..") || path7.isAbsolute(fromRoot)) return null;
2426
- try {
2427
- if (!isFile(absolute)) return null;
2428
- } catch {
2429
- return null;
2430
- }
2431
- return {
2432
- path: rel,
2433
- line: typeof req.line === "number" ? req.line + 1 : void 0,
2434
- column: typeof req.column === "number" ? req.column + 1 : void 0
2435
- };
2436
- }
2437
- function createViewManager(ctx) {
2438
- const safeArea = createSafeAreaController({ connections: ctx.connections });
2439
- let simulatorView = null;
2440
- let simulatorViewAdded = false;
2441
- let nativeSimulatorView = null;
2442
- let nativeSimulatorViewAdded = false;
2443
- let nativeSimulatorProjectPath = null;
2444
- let settleNativeSimulatorReady = null;
2445
- let lastRendererRect = null;
2446
- let nativeCustomApiBridgeHandler = null;
2447
- let currentZoomFactor = 1;
2448
- let settingsView = null;
2449
- let settingsViewAdded = false;
2450
- let popoverView = null;
2451
- let simulatorWebContentsId = null;
2452
- let nativeDevtoolsSourceWc = null;
2453
- let unsubscribeNativeRenderEvents = null;
2454
- let stopElementsForward = null;
2455
- let stopServiceConsole = null;
2456
- let nativeDevtoolsRetryTimer = null;
2457
- let nativeDevtoolsRetryToken = 0;
2458
- let simulatorBoundsOverride = null;
2459
- let workbenchView = null;
2460
- let workbenchViewAdded = false;
2461
- let workbenchThemeSyncBound = false;
2462
- let workbenchUrl = null;
2924
+ // src/main/services/views/host-toolbar-view.ts
2925
+ function createHostToolbarView(ctx, reconciler, deps) {
2463
2926
  let hostToolbarView = null;
2464
2927
  let hostToolbarPreloadOverride = null;
2465
2928
  let hostToolbarViewAdded = false;
@@ -2469,153 +2932,6 @@ function createViewManager(ctx) {
2469
2932
  const hostToolbarPort = createHostToolbarPortChannel({
2470
2933
  isCurrent: (wc) => liveHostToolbarWebContents() === wc
2471
2934
  });
2472
- function destroyViewInternal(view) {
2473
- if (!view) return;
2474
- if (!ctx.windows.mainWindow.isDestroyed()) {
2475
- try {
2476
- ctx.windows.mainWindow.contentView.removeChildView(view);
2477
- } catch {
2478
- }
2479
- }
2480
- try {
2481
- if (!view.webContents.isDestroyed()) {
2482
- view.webContents.close();
2483
- }
2484
- } catch {
2485
- }
2486
- }
2487
- function isHidden(b) {
2488
- return b.width <= 0 || b.height <= 0;
2489
- }
2490
- function setSimulatorDevtoolsBounds(bounds) {
2491
- simulatorBoundsOverride = bounds;
2492
- if (!simulatorView || simulatorView.webContents.isDestroyed()) return;
2493
- if (ctx.windows.mainWindow.isDestroyed()) return;
2494
- if (isHidden(bounds)) {
2495
- if (simulatorViewAdded) {
2496
- try {
2497
- ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
2498
- } catch {
2499
- }
2500
- simulatorViewAdded = false;
2501
- }
2502
- return;
2503
- }
2504
- if (!simulatorViewAdded) {
2505
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
2506
- simulatorViewAdded = true;
2507
- raiseTopOverlays();
2508
- }
2509
- simulatorView.setBounds(bounds);
2510
- }
2511
- function workbenchThemeScheme() {
2512
- return nativeTheme3.shouldUseDarkColors ? "dark" : "light";
2513
- }
2514
- function pushWorkbenchTheme() {
2515
- if (!workbenchView || workbenchView.webContents.isDestroyed()) return;
2516
- const wc = workbenchView.webContents;
2517
- if (typeof wc.executeJavaScript !== "function") return;
2518
- const script = `window.__WB_SET_THEME && window.__WB_SET_THEME(${JSON.stringify(workbenchThemeScheme())})`;
2519
- wc.executeJavaScript(script, true).catch(() => {
2520
- });
2521
- }
2522
- async function attachWorkbench(url) {
2523
- if (workbenchView) return;
2524
- const view = new WebContentsView2({
2525
- webPreferences: {
2526
- nodeIntegration: false,
2527
- contextIsolation: true,
2528
- sandbox: false
2529
- }
2530
- });
2531
- workbenchView = view;
2532
- if (!workbenchThemeSyncBound) {
2533
- nativeTheme3.on("updated", pushWorkbenchTheme);
2534
- workbenchThemeSyncBound = true;
2535
- }
2536
- try {
2537
- view.webContents.setWindowOpenHandler(({ url: target }) => handleWindowOpenExternal(target));
2538
- } catch {
2539
- }
2540
- ctx.windows.mainWindow.contentView.addChildView(view);
2541
- workbenchViewAdded = true;
2542
- raiseTopOverlays();
2543
- const loadUrl = `${url}index.html?theme=${workbenchThemeScheme()}`;
2544
- await view.webContents.loadURL(loadUrl).catch((err) => {
2545
- console.error("[workbench] attachWorkbench \u2014 loadURL failed", err);
2546
- });
2547
- }
2548
- function setWorkbenchSource(url) {
2549
- workbenchUrl = url;
2550
- }
2551
- function setWorkbenchBounds(bounds) {
2552
- if (ctx.windows.mainWindow.isDestroyed()) return;
2553
- if (isHidden(bounds)) {
2554
- if (workbenchView && workbenchViewAdded && !workbenchView.webContents.isDestroyed()) {
2555
- try {
2556
- ctx.windows.mainWindow.contentView.removeChildView(workbenchView);
2557
- } catch {
2558
- }
2559
- workbenchViewAdded = false;
2560
- }
2561
- return;
2562
- }
2563
- if (!workbenchView && workbenchUrl) {
2564
- void attachWorkbench(workbenchUrl);
2565
- }
2566
- if (!workbenchView || workbenchView.webContents.isDestroyed()) return;
2567
- if (!workbenchViewAdded) {
2568
- ctx.windows.mainWindow.contentView.addChildView(workbenchView);
2569
- workbenchViewAdded = true;
2570
- raiseTopOverlays();
2571
- }
2572
- workbenchView.setBounds(bounds);
2573
- }
2574
- function detachWorkbench() {
2575
- if (workbenchThemeSyncBound) {
2576
- nativeTheme3.removeListener("updated", pushWorkbenchTheme);
2577
- workbenchThemeSyncBound = false;
2578
- }
2579
- destroyViewInternal(workbenchView);
2580
- workbenchView = null;
2581
- workbenchViewAdded = false;
2582
- }
2583
- function workspaceUriFor(rel) {
2584
- const encoded = rel.split("/").map(encodeURIComponent).join("/");
2585
- return `file:///workspace/${encoded}`;
2586
- }
2587
- function tryRevealInWorkbench(uri, zeroLine, zeroCol) {
2588
- if (!workbenchView || workbenchView.webContents.isDestroyed()) {
2589
- return Promise.resolve(false);
2590
- }
2591
- const script = `(async () => {
2592
- try {
2593
- const P = window.__WB_PROBE; if (!P) return false
2594
- const vscode = P.vscode
2595
- const uri = vscode.Uri.parse(${JSON.stringify(uri)})
2596
- const doc = await vscode.workspace.openTextDocument(uri)
2597
- const pos = new vscode.Position(${zeroLine}, ${zeroCol})
2598
- await vscode.window.showTextDocument(doc, { selection: new vscode.Range(pos, pos) })
2599
- return true
2600
- } catch (e) { return false }
2601
- })()`;
2602
- return workbenchView.webContents.executeJavaScript(script, true).then((ok) => ok === true).catch(() => false);
2603
- }
2604
- function openFileInWorkbench(relPath, line, column) {
2605
- if (!workbenchView || workbenchView.webContents.isDestroyed()) return false;
2606
- const uri = workspaceUriFor(relPath.replace(/^\/+/, ""));
2607
- const zeroLine = Math.max(0, Math.floor(line) - 1);
2608
- const zeroCol = Math.max(0, Math.floor(column) - 1);
2609
- void (async () => {
2610
- for (let attempt = 0; attempt < 10; attempt++) {
2611
- if (!workbenchView || workbenchView.webContents.isDestroyed()) return;
2612
- if (await tryRevealInWorkbench(uri, zeroLine, zeroCol)) return;
2613
- await new Promise((resolve) => setTimeout(resolve, 150));
2614
- }
2615
- console.error("[workbench] openFileInWorkbench: workbench never became ready for", uri);
2616
- })();
2617
- return true;
2618
- }
2619
2935
  function liveHostToolbarWebContents() {
2620
2936
  const wc = hostToolbarView?.webContents;
2621
2937
  if (!wc || wc.isDestroyed()) return null;
@@ -2632,6 +2948,7 @@ function createViewManager(ctx) {
2632
2948
  }
2633
2949
  hostToolbarViewAdded = false;
2634
2950
  }
2951
+ reconciler.forgetActual(VIEW_ID.hostToolbar);
2635
2952
  if (!hostToolbarRuntimeAcquired) {
2636
2953
  acquireHostToolbarSessionRuntime();
2637
2954
  hostToolbarRuntimeAcquired = true;
@@ -2645,7 +2962,7 @@ function createViewManager(ctx) {
2645
2962
  if (hostToolbarPreloadOverride !== null) {
2646
2963
  webPreferences.preload = hostToolbarPreloadOverride;
2647
2964
  }
2648
- const view = new WebContentsView2({ webPreferences });
2965
+ const view = new WebContentsView4({ webPreferences });
2649
2966
  hostToolbarView = view;
2650
2967
  hostToolbarPort.attach(view.webContents);
2651
2968
  try {
@@ -2658,46 +2975,25 @@ function createViewManager(ctx) {
2658
2975
  }
2659
2976
  return view;
2660
2977
  }
2661
- function setHostToolbarBounds(bounds) {
2662
- if (ctx.windows.mainWindow.isDestroyed()) return;
2663
- if (isHidden(bounds)) {
2664
- if (hostToolbarView && hostToolbarViewAdded && liveHostToolbarWebContents()) {
2665
- try {
2666
- ctx.windows.mainWindow.contentView.removeChildView(hostToolbarView);
2667
- } catch {
2668
- }
2669
- }
2670
- hostToolbarViewAdded = false;
2671
- return;
2672
- }
2673
- const view = ensureHostToolbarView();
2674
- if (!hostToolbarViewAdded) {
2675
- ctx.windows.mainWindow.contentView.addChildView(view);
2676
- hostToolbarViewAdded = true;
2677
- }
2678
- view.setBounds(bounds);
2679
- }
2680
2978
  function notifyHostToolbarHeight(height) {
2681
2979
  hostToolbarLastHeight = height;
2682
2980
  ctx.notify.hostToolbarHeightChanged(height);
2683
- if (settingsViewAdded) applySettingsBounds();
2684
- if (popoverView) applyPopoverBounds();
2981
+ deps.reapplyPresentOverlays();
2685
2982
  }
2686
2983
  function setHostToolbarHeight(extent) {
2687
2984
  if (hostToolbarHeightMode !== "auto") return;
2688
2985
  notifyHostToolbarHeight(extent);
2689
2986
  }
2690
2987
  function hideHostToolbar() {
2691
- if (hostToolbarView && hostToolbarViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
2692
- try {
2693
- ctx.windows.mainWindow.contentView.removeChildView(hostToolbarView);
2694
- } catch {
2695
- }
2696
- }
2697
- hostToolbarViewAdded = false;
2988
+ reconciler.setBaseDesired(VIEW_ID.hostToolbar, {
2989
+ viewId: VIEW_ID.hostToolbar,
2990
+ placement: { visible: false },
2991
+ layer: VIEW_LAYER.hostToolbar
2992
+ });
2993
+ reconciler.reconcileNow();
2698
2994
  notifyHostToolbarHeight(0);
2699
2995
  }
2700
- const hostToolbar = {
2996
+ const control = {
2701
2997
  async loadURL(url) {
2702
2998
  const view = ensureHostToolbarView();
2703
2999
  hostToolbarPort.invalidate();
@@ -2714,8 +3010,8 @@ function createViewManager(ctx) {
2714
3010
  hide() {
2715
3011
  hideHostToolbar();
2716
3012
  },
2717
- setPreloadPath(path23) {
2718
- hostToolbarPreloadOverride = path23;
3013
+ setPreloadPath(path24) {
3014
+ hostToolbarPreloadOverride = path24;
2719
3015
  },
2720
3016
  setHeightMode(mode) {
2721
3017
  if (mode !== "auto" && !(Number.isFinite(mode.fixed) && mode.fixed >= 0)) {
@@ -2738,243 +3034,417 @@ function createViewManager(ctx) {
2738
3034
  return hostToolbarPort.send(channel, payload);
2739
3035
  }
2740
3036
  };
3037
+ reconciler.registerView(VIEW_ID.hostToolbar, {
3038
+ getView: () => hostToolbarView,
3039
+ ensureView: () => ensureHostToolbarView(),
3040
+ setAdded: (added) => {
3041
+ hostToolbarViewAdded = added;
3042
+ },
3043
+ ensureLazy: (desired) => {
3044
+ if (desired?.placement.visible && !liveHostToolbarWebContents()) ensureHostToolbarView();
3045
+ }
3046
+ });
3047
+ function dispose() {
3048
+ hostToolbarPort.dispose();
3049
+ destroyChildView(ctx.windows.mainWindow, hostToolbarView);
3050
+ hostToolbarView = null;
3051
+ hostToolbarViewAdded = false;
3052
+ if (hostToolbarRuntimeAcquired) {
3053
+ releaseHostToolbarSessionRuntime();
3054
+ hostToolbarRuntimeAcquired = false;
3055
+ }
3056
+ }
3057
+ return {
3058
+ control,
3059
+ setHostToolbarHeight,
3060
+ getHostToolbarHeight: () => hostToolbarLastHeight,
3061
+ getHostToolbarWebContentsId: () => liveHostToolbarWebContents()?.id ?? null,
3062
+ dispose
3063
+ };
3064
+ }
3065
+
3066
+ // src/main/services/views/overlay-panels-view.ts
3067
+ import { WebContentsView as WebContentsView5 } from "electron";
3068
+ import path8 from "path";
3069
+
3070
+ // src/main/services/layout/index.ts
3071
+ function computeNativeSimulatorViewParams(rect, zoomPercent) {
3072
+ const zoomFactor = zoomPercent / 100;
3073
+ return {
3074
+ bounds: {
3075
+ x: Math.round(rect.x),
3076
+ y: Math.round(rect.y),
3077
+ width: Math.max(0, Math.round(rect.width)),
3078
+ height: Math.max(0, Math.round(rect.height))
3079
+ },
3080
+ zoomFactor
3081
+ };
3082
+ }
3083
+ function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
3084
+ return {
3085
+ x: 0,
3086
+ y: headerHeight,
3087
+ width: Math.max(1, contentWidth),
3088
+ height: Math.max(1, contentHeight - headerHeight)
3089
+ };
3090
+ }
3091
+ function computePopoverBounds(contentWidth, contentHeight, headerHeight) {
3092
+ return {
3093
+ x: 0,
3094
+ y: headerHeight,
3095
+ width: Math.max(1, contentWidth),
3096
+ height: contentHeight - headerHeight
3097
+ };
3098
+ }
3099
+
3100
+ // src/main/services/views/overlay-panels-view.ts
3101
+ function createOverlayPanelsView(ctx, reconciler, deps) {
3102
+ let settingsView = null;
3103
+ let popoverView = null;
2741
3104
  function overlayHeaderHeight() {
2742
- return HEADER_H + hostToolbarLastHeight;
3105
+ return HEADER_H + deps.getHostToolbarHeight();
2743
3106
  }
2744
3107
  function applySettingsBounds() {
2745
3108
  if (!settingsView || ctx.windows.mainWindow.isDestroyed()) return;
2746
3109
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
2747
- settingsView.setBounds(computeSettingsBounds(w, h, overlayHeaderHeight()));
3110
+ reconciler.setOverlayDesired(VIEW_ID.settings, {
3111
+ viewId: VIEW_ID.settings,
3112
+ placement: {
3113
+ visible: true,
3114
+ bounds: computeSettingsBounds(w, h, overlayHeaderHeight())
3115
+ },
3116
+ layer: VIEW_LAYER.settings
3117
+ });
3118
+ reconciler.reconcileNow();
2748
3119
  }
2749
3120
  function applyPopoverBounds() {
2750
3121
  if (!popoverView || ctx.windows.mainWindow.isDestroyed()) return;
2751
3122
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
2752
- popoverView.setBounds(computePopoverBounds(w, h, overlayHeaderHeight()));
2753
- }
2754
- function clearNativeDevtoolsRetry() {
2755
- nativeDevtoolsRetryToken++;
2756
- if (nativeDevtoolsRetryTimer) {
2757
- clearTimeout(nativeDevtoolsRetryTimer);
2758
- nativeDevtoolsRetryTimer = null;
2759
- }
3123
+ reconciler.setOverlayDesired(VIEW_ID.popover, {
3124
+ viewId: VIEW_ID.popover,
3125
+ placement: {
3126
+ visible: true,
3127
+ bounds: computePopoverBounds(w, h, overlayHeaderHeight())
3128
+ },
3129
+ layer: VIEW_LAYER.popover
3130
+ });
3131
+ reconciler.reconcileNow();
2760
3132
  }
2761
- function closeNativeDevtoolsSource() {
2762
- const source = nativeDevtoolsSourceWc;
2763
- nativeDevtoolsSourceWc = null;
2764
- try {
2765
- stopServiceConsole?.();
2766
- } catch {
2767
- }
2768
- stopServiceConsole = null;
2769
- if (!source || source.isDestroyed()) return;
2770
- try {
2771
- if (source.isDevToolsOpened()) {
2772
- source.closeDevTools();
2773
- }
2774
- } catch {
3133
+ async function showSettings() {
3134
+ if (!settingsView) {
3135
+ settingsView = new WebContentsView5({
3136
+ webPreferences: {
3137
+ nodeIntegration: false,
3138
+ contextIsolation: true,
3139
+ sandbox: false,
3140
+ preload: mainPreloadPath
3141
+ }
3142
+ });
3143
+ applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
3144
+ settingsView.setBackgroundColor("#00000000");
3145
+ await settingsView.webContents.loadFile(
3146
+ path8.join(ctx.rendererDir, "entries/settings/index.html")
3147
+ );
2775
3148
  }
3149
+ applySettingsBounds();
2776
3150
  }
2777
- function stopFollowingNativeServiceHost() {
2778
- clearNativeDevtoolsRetry();
2779
- if (unsubscribeNativeRenderEvents) {
2780
- try {
2781
- unsubscribeNativeRenderEvents();
2782
- } catch {
3151
+ function hideSettings() {
3152
+ reconciler.deleteOverlayDesired(VIEW_ID.settings);
3153
+ reconciler.reconcileNow();
3154
+ }
3155
+ function showPopover(data) {
3156
+ hidePopover();
3157
+ const popover = new WebContentsView5({
3158
+ webPreferences: {
3159
+ nodeIntegration: false,
3160
+ contextIsolation: true,
3161
+ sandbox: false,
3162
+ preload: mainPreloadPath
2783
3163
  }
2784
- unsubscribeNativeRenderEvents = null;
2785
- }
2786
- closeNativeDevtoolsSource();
3164
+ });
3165
+ applyNavigationHardening(popover.webContents, ctx.rendererDir);
3166
+ popoverView = popover;
3167
+ popover.setBackgroundColor("#00000000");
3168
+ applyPopoverBounds();
3169
+ popover.webContents.once("did-finish-load", () => {
3170
+ ctx.notify.popoverInit(popover, data);
3171
+ });
3172
+ popover.webContents.loadFile(
3173
+ path8.join(ctx.rendererDir, "entries/popover/index.html")
3174
+ );
2787
3175
  }
2788
- const openInEditorWiredWcIds = /* @__PURE__ */ new Set();
2789
- function injectOpenResourceHandler(serviceWc, devtoolsWc) {
2790
- if (serviceWc.isDestroyed() || devtoolsWc.isDestroyed()) return;
2791
- try {
2792
- const sourceContext = projectSourceContextFromServiceHostUrl(
2793
- serviceWc.getURL(),
2794
- ctx.workspace?.getProjectPath?.()
2795
- );
2796
- if (!sourceContext) return;
2797
- devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => {
2798
- });
2799
- } catch {
3176
+ function hidePopover() {
3177
+ if (!popoverView) return;
3178
+ reconciler.deleteOverlayDesired(VIEW_ID.popover);
3179
+ destroyChildView(ctx.windows.mainWindow, popoverView);
3180
+ popoverView = null;
3181
+ reconciler.reconcileNow();
3182
+ ctx.notify.popoverClosed();
3183
+ }
3184
+ function reapplyPresentOverlays() {
3185
+ if (reconciler.hasOverlayDesired(VIEW_ID.settings)) applySettingsBounds();
3186
+ if (reconciler.hasOverlayDesired(VIEW_ID.popover)) applyPopoverBounds();
3187
+ }
3188
+ function applySettingsBoundsIfPresent() {
3189
+ if (reconciler.hasOverlayDesired(VIEW_ID.settings)) applySettingsBounds();
3190
+ }
3191
+ function destroySettings() {
3192
+ destroyChildView(ctx.windows.mainWindow, settingsView);
3193
+ settingsView = null;
3194
+ }
3195
+ reconciler.registerView(VIEW_ID.settings, { getView: () => settingsView });
3196
+ reconciler.registerView(VIEW_ID.popover, { getView: () => popoverView });
3197
+ return {
3198
+ showSettings,
3199
+ hideSettings,
3200
+ showPopover,
3201
+ hidePopover,
3202
+ reapplyPresentOverlays,
3203
+ applySettingsBoundsIfPresent,
3204
+ destroySettings,
3205
+ getSettingsWebContents: () => {
3206
+ if (!settingsView) return null;
3207
+ if (settingsView.webContents.isDestroyed()) return null;
3208
+ return settingsView.webContents;
3209
+ },
3210
+ getSettingsWebContentsId: () => {
3211
+ if (!settingsView) return null;
3212
+ if (settingsView.webContents.isDestroyed()) return null;
3213
+ return settingsView.webContents.id;
3214
+ },
3215
+ getPopoverWebContentsId: () => {
3216
+ if (!popoverView) return null;
3217
+ if (popoverView.webContents.isDestroyed()) return null;
3218
+ return popoverView.webContents.id;
2800
3219
  }
3220
+ };
3221
+ }
3222
+
3223
+ // src/main/services/views/native-simulator-view.ts
3224
+ import { ipcMain as ipcMain2, nativeTheme as nativeTheme4, shell as shell2, WebContentsView as WebContentsView6, webContents } from "electron";
3225
+
3226
+ // src/main/services/simulator/custom-apis.ts
3227
+ async function handleCustomApiBridgeRequest(apis, req) {
3228
+ try {
3229
+ const result = req.op === "list" ? apis.list() : await apis.invoke(req.name, req.params);
3230
+ return { id: req.id, result };
3231
+ } catch (err) {
3232
+ return { id: req.id, error: err instanceof Error ? err.message : String(err) };
2801
3233
  }
2802
- function customizeDevtoolsTabs(devtoolsWc) {
2803
- try {
2804
- if (devtoolsWc.isDestroyed()) return;
2805
- const inject = () => {
2806
- if (devtoolsWc.isDestroyed()) return;
2807
- try {
2808
- void devtoolsWc.executeJavaScript(buildCustomizeTabsScript()).catch(() => {
2809
- });
2810
- } catch {
2811
- }
3234
+ }
3235
+ function createSimulatorApiRegistry() {
3236
+ const handlers2 = /* @__PURE__ */ new Map();
3237
+ return {
3238
+ register(name, handler) {
3239
+ handlers2.set(name, handler);
3240
+ return () => {
3241
+ if (handlers2.get(name) === handler) handlers2.delete(name);
2812
3242
  };
2813
- if (devtoolsWc.isLoading()) {
2814
- devtoolsWc.once("dom-ready", inject);
2815
- } else {
2816
- inject();
2817
- }
2818
- } catch {
3243
+ },
3244
+ list() {
3245
+ return Array.from(handlers2.keys());
3246
+ },
3247
+ has(name) {
3248
+ return handlers2.has(name);
3249
+ },
3250
+ async invoke(name, params) {
3251
+ const handler = handlers2.get(name);
3252
+ if (!handler) throw new Error(`Simulator API "${name}" is not registered`);
3253
+ return await handler(params);
3254
+ },
3255
+ clear() {
3256
+ handlers2.clear();
2819
3257
  }
3258
+ };
3259
+ }
3260
+
3261
+ // src/main/services/views/miniapp-partition.ts
3262
+ import * as electron from "electron";
3263
+ var SHARED_MINIAPP_PARTITION = "persist:simulator";
3264
+ var PARTITION_PREFIX = "persist:miniapp-";
3265
+ function miniappPartitionKey(appId, projectPath) {
3266
+ const safe = appId.replace(/[^A-Za-z0-9_-]/g, "");
3267
+ const base = safe === appId && safe.length > 0 ? safe : safe.length > 0 ? `${safe}-${djb2(appId).toString(36)}` : djb2(appId).toString(36);
3268
+ if (!projectPath) return base;
3269
+ return `${base}-p${djb2(projectPath).toString(36)}`;
3270
+ }
3271
+ function miniappPartition(appId, projectPath) {
3272
+ if (!appId) return SHARED_MINIAPP_PARTITION;
3273
+ return `${PARTITION_PREFIX}${miniappPartitionKey(appId, projectPath)}`;
3274
+ }
3275
+ function djb2(input) {
3276
+ let h = 5381;
3277
+ for (let i = 0; i < input.length; i++) {
3278
+ h = (h << 5) + h + input.charCodeAt(i) >>> 0;
2820
3279
  }
2821
- function wireOpenInEditor(serviceWc, devtoolsWc) {
2822
- if (!devtoolsWc.isDestroyed()) {
2823
- if (devtoolsWc.isLoading()) {
2824
- devtoolsWc.once("dom-ready", () => injectOpenResourceHandler(serviceWc, devtoolsWc));
2825
- } else {
2826
- injectOpenResourceHandler(serviceWc, devtoolsWc);
2827
- }
3280
+ return h;
3281
+ }
3282
+ var configurators = /* @__PURE__ */ new Set();
3283
+ var configuredPartitions = /* @__PURE__ */ new Set();
3284
+ function registerMiniappSessionConfigurator(fn) {
3285
+ configurators.add(fn);
3286
+ for (const partition of configuredPartitions) {
3287
+ try {
3288
+ fn(electron.session.fromPartition(partition), partition);
3289
+ } catch (err) {
3290
+ console.warn("[miniapp-partition] configurator failed for", partition, err);
2828
3291
  }
2829
- if (openInEditorWiredWcIds.has(serviceWc.id)) return;
2830
- openInEditorWiredWcIds.add(serviceWc.id);
2831
- const onOpenUrl = (_event, url) => {
2832
- const req = decodeOpenInEditorUrl(url);
2833
- if (!req) return;
2834
- const target = resolveProjectEditorTarget(
2835
- serviceWc.getURL(),
2836
- ctx.workspace?.getProjectPath?.(),
2837
- req
2838
- );
2839
- if (!target) return;
2840
- openFileInWorkbench(target.path, target.line ?? 1, target.column ?? 1);
2841
- ctx.notify.editorOpenFile(target);
2842
- };
2843
- serviceWc.on("devtools-open-url", onOpenUrl);
2844
- const conn = ctx.connections.acquire(serviceWc);
2845
- conn.on("closed", () => {
2846
- openInEditorWiredWcIds.delete(serviceWc.id);
2847
- try {
2848
- serviceWc.removeListener("devtools-open-url", onOpenUrl);
2849
- } catch {
2850
- }
2851
- });
2852
3292
  }
2853
- function pointNativeDevtoolsAtServiceWc(next) {
2854
- if (!simulatorView || simulatorView.webContents.isDestroyed()) return true;
2855
- if (nativeDevtoolsSourceWc?.id === next.id && !nativeDevtoolsSourceWc.isDestroyed()) {
2856
- return true;
3293
+ return () => {
3294
+ configurators.delete(fn);
3295
+ };
3296
+ }
3297
+ function configureMiniappSession(partition) {
3298
+ const alreadyConfigured = configuredPartitions.has(partition);
3299
+ configuredPartitions.add(partition);
3300
+ if (configurators.size === 0) return null;
3301
+ const sess = electron.session.fromPartition(partition);
3302
+ if (alreadyConfigured) return sess;
3303
+ for (const fn of configurators) {
3304
+ try {
3305
+ fn(sess, partition);
3306
+ } catch (err) {
3307
+ console.warn("[miniapp-partition] configurator failed for", partition, err);
2857
3308
  }
2858
- closeNativeDevtoolsSource();
2859
- nativeDevtoolsSourceWc = next;
3309
+ }
3310
+ return sess;
3311
+ }
3312
+
3313
+ // src/shared/simulator-route.ts
3314
+ function decodePageSpec(value) {
3315
+ const qIdx = value.indexOf("?");
3316
+ if (qIdx < 0) return { pagePath: value, query: {} };
3317
+ const pagePath = value.slice(0, qIdx);
3318
+ const query = {};
3319
+ for (const pair of value.slice(qIdx + 1).split("&")) {
3320
+ if (!pair) continue;
3321
+ const eqIdx = pair.indexOf("=");
3322
+ const k = eqIdx >= 0 ? pair.slice(0, eqIdx) : pair;
3323
+ const v = eqIdx >= 0 ? pair.slice(eqIdx + 1) : "";
3324
+ if (k) query[decodeURIComponent(k)] = decodeURIComponent(v);
3325
+ }
3326
+ return { pagePath, query };
3327
+ }
3328
+ function parseLocationRoute(search) {
3329
+ const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
3330
+ const appId = params.get("appId");
3331
+ const entryRaw = params.get("entry");
3332
+ if (!appId || !entryRaw) return null;
3333
+ const entry = decodePageSpec(entryRaw);
3334
+ const pageRaw = params.get("page");
3335
+ const current = pageRaw && pageRaw !== entryRaw ? decodePageSpec(pageRaw) : entry;
3336
+ return { appId, entry, current };
3337
+ }
3338
+ function parseRoute(url) {
3339
+ if (!url) return null;
3340
+ try {
3341
+ const u = new URL(url);
3342
+ return parseLocationRoute(u.search);
3343
+ } catch {
3344
+ return null;
3345
+ }
3346
+ }
3347
+
3348
+ // src/shared/bridge-channels.ts
3349
+ var BRIDGE_CHANNELS = {
3350
+ SPAWN: "dmb:spawn",
3351
+ DISPOSE: "dmb:dispose",
3352
+ PAGE_OPEN: "dmb:page:open",
3353
+ PAGE_CLOSE: "dmb:page:close",
3354
+ PAGE_LIFECYCLE: "dmb:page:lifecycle",
3355
+ NAV_CALLBACK: "dmb:nav:callback",
3356
+ SERVICE_INVOKE: "dmb:service:invoke",
3357
+ SERVICE_PUBLISH: "dmb:service:publish",
3358
+ RENDER_INVOKE: "dmb:render:invoke",
3359
+ RENDER_PUBLISH: "dmb:render:publish",
3360
+ TO_SERVICE: "dmb:to-service",
3361
+ TO_RENDER: "dmb:to-render",
3362
+ SIMULATOR_API: "dmb:simulator-api",
3363
+ /** simulator → main: ack of an API_CALL request (carries success/fail args). */
3364
+ API_RESPONSE: "dmb:api:response",
3365
+ /**
3366
+ * simulator webview preload → main (sendSync): "is native-host mode on?".
3367
+ * The guest preload can't read the launch `process.env`, so it asks main
3368
+ * (which can) at install time. Reply is `e.returnValue = boolean`.
3369
+ */
3370
+ NATIVE_HOST_ENABLED: "dmb:native-host-enabled",
3371
+ /**
3372
+ * simulator (DeviceShell) → main: the visible top-of-stack page bridgeId.
3373
+ * Main has no z-order concept — the active page lives only in DeviceShell's
3374
+ * ShellState — so devtools panels / automation that must target "the current
3375
+ * page's render webContents" resolve it through this signal. Fire-and-forget.
3376
+ */
3377
+ ACTIVE_PAGE: "dmb:active-page",
3378
+ /**
3379
+ * simulator (DeviceShell) → main: the FULL ordered page stack (bottom→top)
3380
+ * whenever it changes. Main has no stack of its own (it only learns the
3381
+ * active bridgeId via ACTIVE_PAGE), so automation's `App.getPageStack` needs
3382
+ * this to report multi-page stacks. Fire-and-forget.
3383
+ */
3384
+ PAGE_STACK: "dmb:page-stack"
3385
+ };
3386
+ var SIMULATOR_EVENTS = {
3387
+ DOM_READY: "simulator:dom-ready",
3388
+ NAV_BAR: "simulator:navigation-bar",
3389
+ NAV_ACTION: "simulator:nav-action",
3390
+ TAB_ACTION: "simulator:tab-action",
3391
+ /** main → simulator: invoke a wx.* API on the simulator-resident MiniApp. */
3392
+ API_CALL: "simulator:api-call",
3393
+ /**
3394
+ * main → simulator: the renderer toolbar picked a different device. Carries a
3395
+ * NativeDeviceInfo; DeviceShell resizes the bezel + re-renders status bar /
3396
+ * notch. The race-free INITIAL device rides NativeHostConfig.device (read
3397
+ * synchronously at preload bridge-install); this event covers live changes.
3398
+ */
3399
+ DEVICE_CHANGE: "simulator:device-change",
3400
+ /**
3401
+ * main → simulator: a watcher rebuild finished; boot a NEW app session for
3402
+ * the carried url ({@link RelaunchPayload}) IN PLACE, keeping the live
3403
+ * DeviceShell painted, and swap once the new session's root page reports
3404
+ * DOM_READY (ready-then-swap soft reload). Main sends this only when the
3405
+ * shell is live+ready; otherwise the renderer falls back to the hard
3406
+ * attachNativeSimulator rebuild.
3407
+ */
3408
+ RELAUNCH: "simulator:relaunch"
3409
+ };
3410
+ function deviceInfoToHostEnv(d) {
3411
+ return {
3412
+ brand: d.brand,
3413
+ model: d.model,
3414
+ system: d.system,
3415
+ platform: d.platform,
3416
+ pixelRatio: d.pixelRatio,
3417
+ screenWidth: d.screenWidth,
3418
+ screenHeight: d.screenHeight,
3419
+ windowWidth: d.screenWidth,
3420
+ windowHeight: Math.max(0, d.screenHeight - d.statusBarHeight),
3421
+ statusBarHeight: d.statusBarHeight
3422
+ };
3423
+ }
3424
+
3425
+ // src/main/services/views/native-simulator-view.ts
3426
+ function createNativeSimulatorView(ctx, reconciler, deps) {
3427
+ const { safeArea, devtoolsHost, overlayPanels } = deps;
3428
+ let nativeSimulatorView = null;
3429
+ let nativeSimulatorViewAdded = false;
3430
+ let nativeSimulatorProjectPath = null;
3431
+ let settleNativeSimulatorReady = null;
3432
+ let nativeSimulatorShellReady = false;
3433
+ let nativeCustomApiBridgeHandler = null;
3434
+ let currentZoomFactor = 1;
3435
+ let simulatorWebContentsId = null;
3436
+ function applyNativeSimulatorBounds(view, bounds, zoom) {
3437
+ const p = computeNativeSimulatorViewParams(bounds, zoom);
3438
+ currentZoomFactor = p.zoomFactor;
3439
+ view.setBounds(p.bounds);
3440
+ const simWc = view.webContents;
3441
+ if (!simWc.isDestroyed()) simWc.setZoomFactor(p.zoomFactor);
2860
3442
  try {
2861
- next.setDevToolsWebContents(simulatorView.webContents);
2862
- next.openDevTools({ mode: "detach", activate: false });
2863
- wireOpenInEditor(next, simulatorView.webContents);
2864
- customizeDevtoolsTabs(simulatorView.webContents);
2865
- try {
2866
- stopServiceConsole?.();
2867
- } catch {
3443
+ for (const wc of webContents.getAllWebContents()) {
3444
+ if (wc.isDestroyed()) continue;
3445
+ if (wc.hostWebContents === simWc) wc.setZoomFactor(p.zoomFactor);
2868
3446
  }
2869
- stopServiceConsole = installServiceConsoleForward({
2870
- serviceWc: next,
2871
- connections: ctx.connections,
2872
- emit: (entry) => ctx.consoleForwarder?.emit(entry)
2873
- }).stop;
2874
- return true;
2875
3447
  } catch {
2876
- if (nativeDevtoolsSourceWc?.id === next.id) {
2877
- nativeDevtoolsSourceWc = null;
2878
- }
2879
- return false;
2880
- }
2881
- }
2882
- function pointNativeDevtoolsAtActiveServiceHost(appId) {
2883
- if (!ctx.bridge?.isNativeHost()) return true;
2884
- if (!simulatorView || simulatorView.webContents.isDestroyed()) return true;
2885
- const wc = ctx.bridge.getServiceWc(appId);
2886
- if (!wc || wc.isDestroyed()) return false;
2887
- return pointNativeDevtoolsAtServiceWc(wc);
2888
- }
2889
- function scheduleNativeDevtoolsFollow(appId, attempt = 0) {
2890
- if (attempt >= 20) return;
2891
- if (!ctx.bridge?.isNativeHost()) return;
2892
- const token = nativeDevtoolsRetryToken;
2893
- if (nativeDevtoolsRetryTimer) clearTimeout(nativeDevtoolsRetryTimer);
2894
- nativeDevtoolsRetryTimer = setTimeout(() => {
2895
- nativeDevtoolsRetryTimer = null;
2896
- if (token !== nativeDevtoolsRetryToken) return;
2897
- if (pointNativeDevtoolsAtActiveServiceHost(appId)) return;
2898
- scheduleNativeDevtoolsFollow(appId, attempt + 1);
2899
- }, 50);
2900
- }
2901
- function followNativeDevtoolsServiceHost(appId) {
2902
- clearNativeDevtoolsRetry();
2903
- if (pointNativeDevtoolsAtActiveServiceHost(appId)) return;
2904
- scheduleNativeDevtoolsFollow(appId);
2905
- }
2906
- function onNativeRenderEvent(event) {
2907
- if (event.kind !== "activePage" && event.kind !== "domReady") return;
2908
- followNativeDevtoolsServiceHost(event.appId);
2909
- }
2910
- function attachNativeSimulatorDevtoolsHost() {
2911
- stopFollowingNativeServiceHost();
2912
- if (simulatorView) {
2913
- removeSimulatorDevtoolsView();
2914
- try {
2915
- if (!simulatorView.webContents.isDestroyed()) {
2916
- simulatorView.webContents.close();
2917
- }
2918
- } catch {
2919
- }
2920
- simulatorView = null;
2921
- }
2922
- simulatorView = new WebContentsView2();
2923
- const devtoolsWc = simulatorView.webContents;
2924
- ctx.networkForward?.setDevtoolsHost(devtoolsWc);
2925
- if (ctx.bridge) {
2926
- try {
2927
- stopElementsForward?.();
2928
- } catch {
2929
- }
2930
- const thisForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
2931
- stopElementsForward = thisForward;
2932
- devtoolsWc.once("destroyed", () => {
2933
- try {
2934
- thisForward();
2935
- } catch {
2936
- }
2937
- if (stopElementsForward === thisForward) stopElementsForward = null;
2938
- });
2939
- }
2940
- devtoolsWc.once("dom-ready", () => {
2941
- devtoolsWc.executeJavaScript(`
2942
- (function() {
2943
- try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
2944
- let tries = 0
2945
- const timer = setInterval(() => {
2946
- tries++
2947
- try {
2948
- const UI = globalThis.UI
2949
- const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
2950
- ? UI.ViewManager.instance()
2951
- : null
2952
- if (vm && typeof vm.showView === 'function') {
2953
- vm.showView('console')
2954
- clearInterval(timer)
2955
- return
2956
- }
2957
- } catch {}
2958
- if (tries > 80) clearInterval(timer)
2959
- }, 50)
2960
- })()
2961
- `).catch(() => {
2962
- });
2963
- });
2964
- if (simulatorBoundsOverride && !isHidden(simulatorBoundsOverride)) {
2965
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
2966
- simulatorViewAdded = true;
2967
- simulatorView.setBounds(simulatorBoundsOverride);
2968
- }
2969
- if (ctx.bridge?.isNativeHost()) {
2970
- unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
2971
- followNativeDevtoolsServiceHost();
2972
- }
2973
- }
2974
- function detachNativeCustomApiBridge() {
2975
- if (nativeCustomApiBridgeHandler) {
2976
- ipcMain2.removeListener(SimulatorCustomApiBridgeChannel.Request, nativeCustomApiBridgeHandler);
2977
- nativeCustomApiBridgeHandler = null;
2978
3448
  }
2979
3449
  }
2980
3450
  function tearDownNativeSimulatorView(label) {
@@ -2994,6 +3464,15 @@ function createViewManager(ctx) {
2994
3464
  }
2995
3465
  nativeSimulatorView = null;
2996
3466
  nativeSimulatorViewAdded = false;
3467
+ nativeSimulatorShellReady = false;
3468
+ reconciler.forgetActual(VIEW_ID.simulator);
3469
+ reconciler.reconcileNow();
3470
+ }
3471
+ function detachNativeCustomApiBridge() {
3472
+ if (nativeCustomApiBridgeHandler) {
3473
+ ipcMain2.removeListener(SimulatorCustomApiBridgeChannel.Request, nativeCustomApiBridgeHandler);
3474
+ nativeCustomApiBridgeHandler = null;
3475
+ }
2997
3476
  }
2998
3477
  function attachNativeCustomApiBridge(simWc) {
2999
3478
  detachNativeCustomApiBridge();
@@ -3032,7 +3511,7 @@ function createViewManager(ctx) {
3032
3511
  const route = parseRoute(simulatorUrl);
3033
3512
  const partition = miniappPartition(route?.appId, ctx.workspace?.getProjectPath());
3034
3513
  configureMiniappSession(partition);
3035
- const view = new WebContentsView2({
3514
+ const view = new WebContentsView6({
3036
3515
  webPreferences: {
3037
3516
  nodeIntegration: false,
3038
3517
  contextIsolation: false,
@@ -3052,8 +3531,8 @@ function createViewManager(ctx) {
3052
3531
  } catch {
3053
3532
  }
3054
3533
  };
3055
- nativeTheme3.on("updated", syncDeskBg);
3056
- ctx.connections.acquire(simWc).own(() => nativeTheme3.removeListener("updated", syncDeskBg));
3534
+ nativeTheme4.on("updated", syncDeskBg);
3535
+ ctx.connections.acquire(simWc).own(() => nativeTheme4.removeListener("updated", syncDeskBg));
3057
3536
  attachNativeCustomApiBridge(simWc);
3058
3537
  const pendingGuestIsTab = [];
3059
3538
  simWc.on("will-attach-webview", (_event, webPreferences, params) => {
@@ -3095,10 +3574,11 @@ function createViewManager(ctx) {
3095
3574
  });
3096
3575
  guestWc.once("did-finish-load", () => {
3097
3576
  if (nativeSimulatorView !== view || simWc.isDestroyed()) return;
3577
+ nativeSimulatorShellReady = true;
3098
3578
  settleNativeSimulatorReady?.();
3099
3579
  settleNativeSimulatorReady = null;
3100
3580
  });
3101
- followNativeDevtoolsServiceHost();
3581
+ devtoolsHost.followServiceHost();
3102
3582
  });
3103
3583
  simWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
3104
3584
  simWc.on("will-navigate", (e, url) => {
@@ -3106,231 +3586,125 @@ function createViewManager(ctx) {
3106
3586
  const u = new URL(url);
3107
3587
  if (u.protocol === "about:") return;
3108
3588
  if (u.protocol === "file:") return;
3109
- if ((u.protocol === "http:" || u.protocol === "https:") && (u.hostname === "localhost" || u.hostname === "127.0.0.1")) {
3110
- return;
3111
- }
3112
- e.preventDefault();
3113
- if (u.protocol === "http:" || u.protocol === "https:") {
3114
- void shell2.openExternal(url);
3115
- }
3116
- } catch {
3117
- e.preventDefault();
3118
- }
3119
- });
3120
- void simWc.loadURL(simulatorUrl).catch((err) => {
3121
- console.error("[workbench] attachNativeSimulator \u2014 loadURL failed", err);
3122
- });
3123
- simulatorWebContentsId = simWc.id;
3124
- ctx.networkForward?.attachSimulator(simWc);
3125
- if (lastRendererRect) {
3126
- setNativeSimulatorViewBounds(lastRendererRect);
3127
- }
3128
- attachNativeSimulatorDevtoolsHost();
3129
- return ready;
3130
- }
3131
- function detachSimulator() {
3132
- settleNativeSimulatorReady?.();
3133
- settleNativeSimulatorReady = null;
3134
- stopFollowingNativeServiceHost();
3135
- detachNativeCustomApiBridge();
3136
- try {
3137
- stopElementsForward?.();
3138
- } catch {
3139
- }
3140
- stopElementsForward = null;
3141
- ctx.networkForward?.detachSimulator();
3142
- ctx.networkForward?.setDevtoolsHost(null);
3143
- tearDownNativeSimulatorView("close");
3144
- hidePopover();
3145
- destroyViewInternal(settingsView);
3146
- settingsView = null;
3147
- settingsViewAdded = false;
3148
- destroyViewInternal(simulatorView);
3149
- simulatorView = null;
3150
- simulatorViewAdded = false;
3151
- simulatorWebContentsId = null;
3152
- nativeSimulatorProjectPath = null;
3153
- simulatorBoundsOverride = null;
3154
- lastRendererRect = null;
3155
- }
3156
- function removeSimulatorDevtoolsView() {
3157
- if (simulatorView && simulatorViewAdded) {
3158
- try {
3159
- ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
3160
- } catch (e) {
3161
- console.error("[workbench] removeSimulatorDevtoolsView error", e);
3162
- }
3163
- simulatorViewAdded = false;
3164
- }
3165
- }
3166
- async function showSettings() {
3167
- if (!settingsView) {
3168
- settingsView = new WebContentsView2({
3169
- webPreferences: {
3170
- nodeIntegration: false,
3171
- contextIsolation: true,
3172
- sandbox: false,
3173
- preload: mainPreloadPath
3174
- }
3175
- });
3176
- applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
3177
- settingsView.setBackgroundColor("#00000000");
3178
- await settingsView.webContents.loadFile(
3179
- path7.join(ctx.rendererDir, "entries/settings/index.html")
3180
- );
3181
- }
3182
- if (!settingsViewAdded) {
3183
- ctx.windows.mainWindow.contentView.addChildView(settingsView);
3184
- settingsViewAdded = true;
3185
- }
3186
- applySettingsBounds();
3187
- }
3188
- function hideSettings() {
3189
- if (settingsView && settingsViewAdded) {
3190
- try {
3191
- ctx.windows.mainWindow.contentView.removeChildView(settingsView);
3192
- } catch {
3193
- }
3194
- settingsViewAdded = false;
3195
- }
3196
- }
3197
- function raiseTopOverlays() {
3198
- if (ctx.windows.mainWindow.isDestroyed()) return;
3199
- const cv = ctx.windows.mainWindow.contentView;
3200
- if (settingsView && settingsViewAdded) cv.addChildView(settingsView);
3201
- if (popoverView) cv.addChildView(popoverView);
3202
- }
3203
- function showPopover(data) {
3204
- hidePopover();
3205
- const popover = new WebContentsView2({
3206
- webPreferences: {
3207
- nodeIntegration: false,
3208
- contextIsolation: true,
3209
- sandbox: false,
3210
- preload: mainPreloadPath
3211
- }
3212
- });
3213
- applyNavigationHardening(popover.webContents, ctx.rendererDir);
3214
- popoverView = popover;
3215
- popover.setBackgroundColor("#00000000");
3216
- ctx.windows.mainWindow.contentView.addChildView(popover);
3217
- applyPopoverBounds();
3218
- popover.webContents.once("did-finish-load", () => {
3219
- ctx.notify.popoverInit(popover, data);
3220
- });
3221
- popover.webContents.loadFile(
3222
- path7.join(ctx.rendererDir, "entries/popover/index.html")
3223
- );
3224
- }
3225
- function hidePopover() {
3226
- if (popoverView) {
3227
- destroyViewInternal(popoverView);
3228
- popoverView = null;
3229
- ctx.notify.popoverClosed();
3230
- }
3231
- }
3232
- function repositionAll() {
3233
- if (settingsView && settingsViewAdded)
3234
- applySettingsBounds();
3235
- if (popoverView)
3236
- applyPopoverBounds();
3237
- }
3238
- function disposeAll() {
3239
- detachSimulator();
3240
- detachWorkbench();
3241
- hostToolbarPort.dispose();
3242
- destroyViewInternal(hostToolbarView);
3243
- hostToolbarView = null;
3244
- hostToolbarViewAdded = false;
3245
- if (hostToolbarRuntimeAcquired) {
3246
- releaseHostToolbarSessionRuntime();
3247
- hostToolbarRuntimeAcquired = false;
3248
- }
3249
- safeArea.dispose();
3250
- }
3251
- function setNativeSimulatorViewBounds(params) {
3252
- lastRendererRect = params;
3253
- if (ctx.windows.mainWindow.isDestroyed() || !nativeSimulatorView) return;
3254
- const p = computeNativeSimulatorViewParams(params, params.zoom);
3255
- currentZoomFactor = p.zoomFactor;
3256
- if (isHidden(p.bounds)) {
3257
- if (nativeSimulatorViewAdded) {
3258
- try {
3259
- ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
3260
- } catch {
3589
+ if ((u.protocol === "http:" || u.protocol === "https:") && (u.hostname === "localhost" || u.hostname === "127.0.0.1")) {
3590
+ return;
3261
3591
  }
3262
- nativeSimulatorViewAdded = false;
3263
- }
3264
- return;
3265
- }
3266
- if (!nativeSimulatorViewAdded) {
3267
- ctx.windows.mainWindow.contentView.addChildView(nativeSimulatorView);
3268
- nativeSimulatorViewAdded = true;
3269
- raiseTopOverlays();
3270
- }
3271
- nativeSimulatorView.setBounds(p.bounds);
3272
- const simWc = nativeSimulatorView.webContents;
3273
- if (!simWc.isDestroyed()) {
3274
- simWc.setZoomFactor(p.zoomFactor);
3275
- }
3276
- try {
3277
- for (const wc of webContents.getAllWebContents()) {
3278
- if (wc.isDestroyed()) continue;
3279
- if (wc.hostWebContents === simWc) {
3280
- wc.setZoomFactor(p.zoomFactor);
3592
+ e.preventDefault();
3593
+ if (u.protocol === "http:" || u.protocol === "https:") {
3594
+ void shell2.openExternal(url);
3281
3595
  }
3596
+ } catch {
3597
+ e.preventDefault();
3282
3598
  }
3283
- } catch {
3284
- }
3599
+ });
3600
+ void simWc.loadURL(simulatorUrl).catch((err) => {
3601
+ console.error("[workbench] attachNativeSimulator \u2014 loadURL failed", err);
3602
+ });
3603
+ simulatorWebContentsId = simWc.id;
3604
+ ctx.networkForward?.attachSimulator(simWc);
3605
+ reconciler.reconcileNow();
3606
+ devtoolsHost.attach();
3607
+ return ready;
3285
3608
  }
3286
- function resize(_simWidth) {
3287
- if (settingsViewAdded) applySettingsBounds();
3609
+ function softReloadNativeSimulator(simulatorUrl) {
3610
+ if (!nativeSimulatorView || nativeSimulatorView.webContents.isDestroyed()) return false;
3611
+ if (!nativeSimulatorShellReady) return false;
3612
+ const payload = { url: simulatorUrl };
3613
+ nativeSimulatorView.webContents.send(SIMULATOR_EVENTS.RELAUNCH, payload);
3614
+ return true;
3615
+ }
3616
+ function detachSimulator() {
3617
+ settleNativeSimulatorReady?.();
3618
+ settleNativeSimulatorReady = null;
3619
+ devtoolsHost.stopFollowing();
3620
+ detachNativeCustomApiBridge();
3621
+ devtoolsHost.stopElementsForwarding();
3622
+ ctx.networkForward?.detachSimulator();
3623
+ ctx.networkForward?.setDevtoolsHost(null);
3624
+ tearDownNativeSimulatorView("close");
3625
+ overlayPanels.hidePopover();
3626
+ overlayPanels.destroySettings();
3627
+ devtoolsHost.destroyHostView();
3628
+ simulatorWebContentsId = null;
3629
+ nativeSimulatorProjectPath = null;
3288
3630
  }
3631
+ reconciler.registerView(VIEW_ID.simulator, {
3632
+ getView: () => nativeSimulatorView,
3633
+ setAdded: (added) => {
3634
+ nativeSimulatorViewAdded = added;
3635
+ },
3636
+ gateHidden: () => !nativeSimulatorView,
3637
+ applyBounds: (view, bounds, extra) => applyNativeSimulatorBounds(view, bounds, extra?.zoom ?? 1)
3638
+ });
3289
3639
  return {
3290
3640
  attachNativeSimulator,
3641
+ softReloadNativeSimulator,
3291
3642
  detachSimulator,
3292
- reapplySafeArea: (device) => safeArea.reapplyAll(device),
3293
- showSettings,
3294
- hideSettings,
3295
- showPopover,
3296
- hidePopover,
3297
- repositionAll,
3298
- disposeAll,
3299
3643
  getSimulatorWebContentsId: () => simulatorWebContentsId,
3300
3644
  getSimulatorWebContents: () => {
3301
3645
  if (simulatorWebContentsId == null) return null;
3302
3646
  const wc = webContents.fromId(simulatorWebContentsId);
3303
3647
  return wc && !wc.isDestroyed() ? wc : null;
3304
3648
  },
3305
- getSimulatorProjectPath: () => nativeSimulatorProjectPath,
3306
- getSettingsWebContents: () => {
3307
- if (!settingsView) return null;
3308
- if (settingsView.webContents.isDestroyed()) return null;
3309
- return settingsView.webContents;
3310
- },
3311
- getSettingsWebContentsId: () => {
3312
- if (!settingsView) return null;
3313
- if (settingsView.webContents.isDestroyed()) return null;
3314
- return settingsView.webContents.id;
3315
- },
3316
- getPopoverWebContentsId: () => {
3317
- if (!popoverView) return null;
3318
- if (popoverView.webContents.isDestroyed()) return null;
3319
- return popoverView.webContents.id;
3320
- },
3321
- getHostToolbarWebContentsId: () => liveHostToolbarWebContents()?.id ?? null,
3322
- getHostToolbarHeight: () => hostToolbarLastHeight,
3323
- setNativeSimulatorViewBounds,
3324
- resize,
3325
- setSimulatorDevtoolsBounds,
3326
- attachWorkbench,
3327
- setWorkbenchSource,
3328
- setWorkbenchBounds,
3329
- detachWorkbench,
3330
- openFileInWorkbench,
3331
- setHostToolbarBounds,
3332
- setHostToolbarHeight,
3333
- hostToolbar
3649
+ getSimulatorProjectPath: () => nativeSimulatorProjectPath
3650
+ };
3651
+ }
3652
+
3653
+ // src/main/services/views/view-manager.ts
3654
+ function createViewManager(ctx) {
3655
+ const safeArea = createSafeAreaController({ connections: ctx.connections });
3656
+ const reconciler = createPlacementReconciler(ctx);
3657
+ const workbench = createWorkbenchView(ctx, reconciler);
3658
+ const devtoolsHost = createDevtoolsHost(ctx, reconciler, {
3659
+ openFileInWorkbench: workbench.openFileInWorkbench
3660
+ });
3661
+ const hostToolbar = createHostToolbarView(ctx, reconciler, {
3662
+ reapplyPresentOverlays: () => reapplyToolbarDependentOverlays()
3663
+ });
3664
+ const overlayPanels = createOverlayPanelsView(ctx, reconciler, {
3665
+ getHostToolbarHeight: hostToolbar.getHostToolbarHeight
3666
+ });
3667
+ const nativeSimulator = createNativeSimulatorView(ctx, reconciler, {
3668
+ safeArea,
3669
+ devtoolsHost,
3670
+ overlayPanels
3671
+ });
3672
+ function reapplyToolbarDependentOverlays() {
3673
+ overlayPanels.reapplyPresentOverlays();
3674
+ }
3675
+ function disposeAll() {
3676
+ nativeSimulator.detachSimulator();
3677
+ workbench.detachWorkbench();
3678
+ hostToolbar.dispose();
3679
+ safeArea.dispose();
3680
+ }
3681
+ return {
3682
+ attachNativeSimulator: nativeSimulator.attachNativeSimulator,
3683
+ softReloadNativeSimulator: nativeSimulator.softReloadNativeSimulator,
3684
+ detachSimulator: nativeSimulator.detachSimulator,
3685
+ reapplySafeArea: (device) => safeArea.reapplyAll(device),
3686
+ showSettings: overlayPanels.showSettings,
3687
+ hideSettings: overlayPanels.hideSettings,
3688
+ showPopover: overlayPanels.showPopover,
3689
+ hidePopover: overlayPanels.hidePopover,
3690
+ repositionAll: () => overlayPanels.reapplyPresentOverlays(),
3691
+ disposeAll,
3692
+ getSimulatorWebContentsId: nativeSimulator.getSimulatorWebContentsId,
3693
+ getSimulatorWebContents: nativeSimulator.getSimulatorWebContents,
3694
+ getSimulatorProjectPath: nativeSimulator.getSimulatorProjectPath,
3695
+ getSettingsWebContents: overlayPanels.getSettingsWebContents,
3696
+ getSettingsWebContentsId: overlayPanels.getSettingsWebContentsId,
3697
+ getPopoverWebContentsId: overlayPanels.getPopoverWebContentsId,
3698
+ getHostToolbarWebContentsId: hostToolbar.getHostToolbarWebContentsId,
3699
+ getHostToolbarHeight: hostToolbar.getHostToolbarHeight,
3700
+ resize: () => overlayPanels.applySettingsBoundsIfPresent(),
3701
+ setPlacementSnapshot: reconciler.setPlacementSnapshot,
3702
+ attachWorkbench: workbench.attachWorkbench,
3703
+ setWorkbenchSource: workbench.setWorkbenchSource,
3704
+ detachWorkbench: workbench.detachWorkbench,
3705
+ openFileInWorkbench: workbench.openFileInWorkbench,
3706
+ setHostToolbarHeight: hostToolbar.setHostToolbarHeight,
3707
+ hostToolbar: hostToolbar.control
3334
3708
  };
3335
3709
  }
3336
3710
 
@@ -3362,7 +3736,7 @@ function createWindowService(mainWindow) {
3362
3736
 
3363
3737
  // src/main/windows/settings-window/create.ts
3364
3738
  import { BrowserWindow as BrowserWindow3 } from "electron";
3365
- import path8 from "path";
3739
+ import path9 from "path";
3366
3740
  async function createSettingsWindow(parent, rendererDir2) {
3367
3741
  const win = new BrowserWindow3({
3368
3742
  width: 420,
@@ -3380,7 +3754,7 @@ async function createSettingsWindow(parent, rendererDir2) {
3380
3754
  }
3381
3755
  });
3382
3756
  applyNavigationHardening(win.webContents, rendererDir2);
3383
- await win.loadFile(path8.join(rendererDir2, "entries/workbench-settings/index.html"));
3757
+ await win.loadFile(path9.join(rendererDir2, "entries/workbench-settings/index.html"));
3384
3758
  return win;
3385
3759
  }
3386
3760
 
@@ -3431,11 +3805,11 @@ import { z as z2 } from "zod";
3431
3805
 
3432
3806
  // src/main/services/projects/project-repository.ts
3433
3807
  import { app as app6 } from "electron";
3434
- import path9 from "path";
3808
+ import path10 from "path";
3435
3809
  import fs5 from "fs";
3436
3810
  var log2 = createLogger("projects");
3437
3811
  function getProjectsFile() {
3438
- return path9.join(app6.getPath("userData"), "dimina-projects.json");
3812
+ return path10.join(app6.getPath("userData"), "dimina-projects.json");
3439
3813
  }
3440
3814
  function load() {
3441
3815
  try {
@@ -3457,13 +3831,13 @@ function validateProjectDir(dirPath) {
3457
3831
  if (!fs5.existsSync(dirPath)) {
3458
3832
  return `\u5C0F\u7A0B\u5E8F\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dirPath}`;
3459
3833
  }
3460
- if (!fs5.existsSync(path9.join(dirPath, "app.json"))) {
3461
- const configPath = path9.join(dirPath, "project.config.json");
3834
+ if (!fs5.existsSync(path10.join(dirPath, "app.json"))) {
3835
+ const configPath = path10.join(dirPath, "project.config.json");
3462
3836
  if (fs5.existsSync(configPath)) {
3463
3837
  try {
3464
3838
  const cfg = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
3465
3839
  if (cfg.miniprogramRoot) {
3466
- const resolvedRoot = path9.resolve(dirPath, cfg.miniprogramRoot);
3840
+ const resolvedRoot = path10.resolve(dirPath, cfg.miniprogramRoot);
3467
3841
  return `\u8BE5\u76EE\u5F55\u7F3A\u5C11 app.json\uFF0Cproject.config.json \u4E2D\u6307\u5B9A\u4E86 miniprogramRoot: "${cfg.miniprogramRoot}"\uFF0C\u8BF7\u5BFC\u5165 ${resolvedRoot}`;
3468
3842
  }
3469
3843
  } catch (err) {
@@ -3476,9 +3850,9 @@ function validateProjectDir(dirPath) {
3476
3850
  }
3477
3851
  function addProject(dirPath) {
3478
3852
  const projects = load();
3479
- let name = path9.basename(dirPath);
3853
+ let name = path10.basename(dirPath);
3480
3854
  try {
3481
- const configPath = path9.join(dirPath, "project.config.json");
3855
+ const configPath = path10.join(dirPath, "project.config.json");
3482
3856
  if (fs5.existsSync(configPath)) {
3483
3857
  const cfg = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
3484
3858
  if (cfg.projectname) name = cfg.projectname;
@@ -3517,7 +3891,7 @@ function getCompileConfig(dirPath) {
3517
3891
  };
3518
3892
  }
3519
3893
  function getProjectPages(dirPath) {
3520
- const appJsonPath = path9.join(dirPath, "app.json");
3894
+ const appJsonPath = path10.join(dirPath, "app.json");
3521
3895
  try {
3522
3896
  const appJson = JSON.parse(
3523
3897
  fs5.readFileSync(appJsonPath, "utf-8")
@@ -3543,7 +3917,7 @@ function getProjectSettings(projectPath) {
3543
3917
  return { uploadWithSourceMap: false };
3544
3918
  }
3545
3919
  try {
3546
- const configPath = path9.join(projectPath, "project.config.json");
3920
+ const configPath = path10.join(projectPath, "project.config.json");
3547
3921
  const config = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
3548
3922
  return {
3549
3923
  uploadWithSourceMap: !!config.setting?.uploadWithSourceMap
@@ -3554,7 +3928,7 @@ function getProjectSettings(projectPath) {
3554
3928
  }
3555
3929
  function updateProjectSettings(projectPath, patch) {
3556
3930
  if (!projectPath) return;
3557
- const configPath = path9.join(projectPath, "project.config.json");
3931
+ const configPath = path10.join(projectPath, "project.config.json");
3558
3932
  let config = {};
3559
3933
  try {
3560
3934
  config = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
@@ -3859,16 +4233,16 @@ function createWorkspaceService(ctx) {
3859
4233
  // src/main/services/projects/thumbnail.ts
3860
4234
  import { createHash } from "crypto";
3861
4235
  import fs6 from "fs";
3862
- import path10 from "path";
4236
+ import path11 from "path";
3863
4237
  import { app as app7 } from "electron";
3864
4238
  function getThumbnailDir() {
3865
- return path10.join(app7.getPath("userData"), "thumbnails");
4239
+ return path11.join(app7.getPath("userData"), "thumbnails");
3866
4240
  }
3867
4241
  function hashProjectPath(projectPath) {
3868
4242
  return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
3869
4243
  }
3870
4244
  function getThumbnailPath(projectPath) {
3871
- return path10.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
4245
+ return path11.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
3872
4246
  }
3873
4247
  var DATA_URL_PNG_PREFIX = "data:image/png;base64,";
3874
4248
  function saveThumbnailFromDataUrl(projectPath, imageDataUrl) {
@@ -3929,20 +4303,20 @@ function sanitizeTemplates(templates) {
3929
4303
  }
3930
4304
 
3931
4305
  // src/main/services/projects/builtin-templates.ts
3932
- import path11 from "node:path";
3933
- var TEMPLATES_DIR = path11.join(devtoolsPackageRoot, "templates");
4306
+ import path12 from "node:path";
4307
+ var TEMPLATES_DIR = path12.join(devtoolsPackageRoot, "templates");
3934
4308
  var BUILTIN_TEMPLATES = [
3935
4309
  {
3936
4310
  id: "blank",
3937
4311
  name: "Blank",
3938
4312
  description: "\u6700\u5C0F\u9AA8\u67B6\uFF1Aapp.* + \u4E00\u4E2A index \u9875\u9762",
3939
- source: { type: "directory", path: path11.join(TEMPLATES_DIR, "blank") }
4313
+ source: { type: "directory", path: path12.join(TEMPLATES_DIR, "blank") }
3940
4314
  },
3941
4315
  {
3942
4316
  id: "taro-todo",
3943
4317
  name: "Taro Todo",
3944
4318
  description: "Taro \u7F16\u8BD1\u4EA7\u7269\u7684 Todo \u793A\u4F8B",
3945
- source: { type: "directory", path: path11.join(TEMPLATES_DIR, "taro-todo") }
4319
+ source: { type: "directory", path: path12.join(TEMPLATES_DIR, "taro-todo") }
3946
4320
  }
3947
4321
  ];
3948
4322
 
@@ -4088,13 +4462,9 @@ var SimulatorAttachNativeSchema = z3.tuple([
4088
4462
  }),
4089
4463
  SimWidth
4090
4464
  ]);
4091
- var SimulatorSetNativeBoundsSchema = z3.tuple([
4092
- z3.object({
4093
- x: z3.number().finite(),
4094
- y: z3.number().finite(),
4095
- width: z3.number().finite(),
4096
- height: z3.number().finite(),
4097
- zoom: z3.number().finite().positive()
4465
+ var SimulatorSoftReloadSchema = z3.tuple([
4466
+ z3.string().url().refine((u) => u.startsWith("http://") || u.startsWith("https://"), {
4467
+ message: "simulator URL must be http(s)"
4098
4468
  })
4099
4469
  ]);
4100
4470
  var SimulatorSetDeviceInfoSchema = z3.tuple([
@@ -4117,12 +4487,31 @@ var SimulatorSetDeviceInfoSchema = z3.tuple([
4117
4487
  })
4118
4488
  ]);
4119
4489
  var NonNegInt = z3.number().int().min(0).max(1e5);
4120
- var ViewBoundsSchema = z3.tuple([
4490
+ var FiniteNum = z3.number().finite();
4491
+ var PlacementSchema = z3.union([
4492
+ z3.object({
4493
+ visible: z3.literal(true),
4494
+ bounds: z3.object({
4495
+ x: FiniteNum,
4496
+ y: FiniteNum,
4497
+ width: FiniteNum,
4498
+ height: FiniteNum
4499
+ })
4500
+ }),
4501
+ z3.object({ visible: z3.literal(false) })
4502
+ ]);
4503
+ var PlacementSnapshotSchema = z3.tuple([
4121
4504
  z3.object({
4122
- x: NonNegInt,
4123
- y: NonNegInt,
4124
- width: NonNegInt,
4125
- height: NonNegInt
4505
+ generation: z3.number().int().min(0),
4506
+ epoch: z3.number().int().min(0),
4507
+ views: z3.array(
4508
+ z3.object({
4509
+ viewId: z3.string().min(1).max(64),
4510
+ placement: PlacementSchema,
4511
+ layer: z3.number().int(),
4512
+ extra: z3.object({ zoom: z3.number().finite().positive().optional() }).optional()
4513
+ })
4514
+ ).max(64)
4126
4515
  })
4127
4516
  ]);
4128
4517
  var HostToolbarAdvertiseHeightSchema = z3.tuple([
@@ -4169,84 +4558,16 @@ var SettingsProjectSettingsChangedSchema = z3.tuple([
4169
4558
  var ProjectCaptureThumbnailSchema = z3.tuple([AbsolutePath]);
4170
4559
  var ProjectGetThumbnailSchema = z3.tuple([AbsolutePath]);
4171
4560
 
4172
- // src/shared/bridge-channels.ts
4173
- var BRIDGE_CHANNELS = {
4174
- SPAWN: "dmb:spawn",
4175
- DISPOSE: "dmb:dispose",
4176
- PAGE_OPEN: "dmb:page:open",
4177
- PAGE_CLOSE: "dmb:page:close",
4178
- PAGE_LIFECYCLE: "dmb:page:lifecycle",
4179
- NAV_CALLBACK: "dmb:nav:callback",
4180
- SERVICE_INVOKE: "dmb:service:invoke",
4181
- SERVICE_PUBLISH: "dmb:service:publish",
4182
- RENDER_INVOKE: "dmb:render:invoke",
4183
- RENDER_PUBLISH: "dmb:render:publish",
4184
- TO_SERVICE: "dmb:to-service",
4185
- TO_RENDER: "dmb:to-render",
4186
- SIMULATOR_API: "dmb:simulator-api",
4187
- /** simulator → main: ack of an API_CALL request (carries success/fail args). */
4188
- API_RESPONSE: "dmb:api:response",
4189
- /**
4190
- * simulator webview preload → main (sendSync): "is native-host mode on?".
4191
- * The guest preload can't read the launch `process.env`, so it asks main
4192
- * (which can) at install time. Reply is `e.returnValue = boolean`.
4193
- */
4194
- NATIVE_HOST_ENABLED: "dmb:native-host-enabled",
4195
- /**
4196
- * simulator (DeviceShell) → main: the visible top-of-stack page bridgeId.
4197
- * Main has no z-order concept — the active page lives only in DeviceShell's
4198
- * ShellState — so devtools panels / automation that must target "the current
4199
- * page's render webContents" resolve it through this signal. Fire-and-forget.
4200
- */
4201
- ACTIVE_PAGE: "dmb:active-page",
4202
- /**
4203
- * simulator (DeviceShell) → main: the FULL ordered page stack (bottom→top)
4204
- * whenever it changes. Main has no stack of its own (it only learns the
4205
- * active bridgeId via ACTIVE_PAGE), so automation's `App.getPageStack` needs
4206
- * this to report multi-page stacks. Fire-and-forget.
4207
- */
4208
- PAGE_STACK: "dmb:page-stack"
4209
- };
4210
- var SIMULATOR_EVENTS = {
4211
- DOM_READY: "simulator:dom-ready",
4212
- NAV_BAR: "simulator:navigation-bar",
4213
- NAV_ACTION: "simulator:nav-action",
4214
- TAB_ACTION: "simulator:tab-action",
4215
- /** main → simulator: invoke a wx.* API on the simulator-resident MiniApp. */
4216
- API_CALL: "simulator:api-call",
4217
- /**
4218
- * main → simulator: the renderer toolbar picked a different device. Carries a
4219
- * NativeDeviceInfo; DeviceShell resizes the bezel + re-renders status bar /
4220
- * notch. The race-free INITIAL device rides NativeHostConfig.device (read
4221
- * synchronously at preload bridge-install); this event covers live changes.
4222
- */
4223
- DEVICE_CHANGE: "simulator:device-change"
4224
- };
4225
- function deviceInfoToHostEnv(d) {
4226
- return {
4227
- brand: d.brand,
4228
- model: d.model,
4229
- system: d.system,
4230
- platform: d.platform,
4231
- pixelRatio: d.pixelRatio,
4232
- screenWidth: d.screenWidth,
4233
- screenHeight: d.screenHeight,
4234
- windowWidth: d.screenWidth,
4235
- windowHeight: Math.max(0, d.screenHeight - d.statusBarHeight),
4236
- statusBarHeight: d.statusBarHeight
4237
- };
4238
- }
4239
-
4240
4561
  // src/main/ipc/simulator.ts
4241
4562
  function registerSimulatorIpc(ctx) {
4242
4563
  return new IpcRegistry(ctx.senderPolicy).handle(SimulatorChannel.AttachNative, (_, ...args) => {
4243
4564
  const [simulatorUrl, simWidth] = validate(SimulatorChannel.AttachNative, SimulatorAttachNativeSchema, args);
4244
4565
  return ctx.views.attachNativeSimulator(simulatorUrl, simWidth);
4566
+ }).handle(SimulatorChannel.SoftReload, (_, ...args) => {
4567
+ const [simulatorUrl] = validate(SimulatorChannel.SoftReload, SimulatorSoftReloadSchema, args);
4568
+ return ctx.views.softReloadNativeSimulator(simulatorUrl);
4245
4569
  }).handle(SimulatorChannel.Detach, () => {
4246
4570
  ctx.views.detachSimulator();
4247
- }).handle(SimulatorChannel.SetNativeBounds, (_, ...args) => {
4248
- const [p] = validate(SimulatorChannel.SetNativeBounds, SimulatorSetNativeBoundsSchema, args);
4249
- ctx.views.setNativeSimulatorViewBounds(p);
4250
4571
  }).handle(SimulatorChannel.SetDeviceInfo, (_, ...args) => {
4251
4572
  const [device] = validate(SimulatorChannel.SetDeviceInfo, SimulatorSetDeviceInfoSchema, args);
4252
4573
  ctx.bridge?.setDevice(device);
@@ -4280,11 +4601,11 @@ var popoverModule = {
4280
4601
 
4281
4602
  // src/main/ipc/projects.ts
4282
4603
  import { app as app8, dialog } from "electron";
4283
- import path13 from "path";
4604
+ import path14 from "path";
4284
4605
 
4285
4606
  // src/main/services/projects/create-project-service.ts
4286
4607
  import fs7 from "node:fs";
4287
- import path12 from "node:path";
4608
+ import path13 from "node:path";
4288
4609
  var DEFAULT_TEMPLATE_ID = "blank";
4289
4610
  async function createProject(input, ctx) {
4290
4611
  const name = (input.name ?? "").trim();
@@ -4331,7 +4652,7 @@ async function createProject(input, ctx) {
4331
4652
  force: true
4332
4653
  });
4333
4654
  }
4334
- const cfgPath = path12.join(target, "project.config.json");
4655
+ const cfgPath = path13.join(target, "project.config.json");
4335
4656
  let cfg = {};
4336
4657
  if (fs7.existsSync(cfgPath)) {
4337
4658
  try {
@@ -4417,7 +4738,7 @@ function registerProjectsIpc(ctx) {
4417
4738
  }
4418
4739
  try {
4419
4740
  const settings = loadWorkbenchSettings();
4420
- const newBase = path13.dirname(input.path);
4741
+ const newBase = path14.dirname(input.path);
4421
4742
  if (newBase && settings.lastCreateBaseDir !== newBase) {
4422
4743
  saveWorkbenchSettings({ ...settings, lastCreateBaseDir: newBase });
4423
4744
  }
@@ -4589,7 +4910,7 @@ import { DisposableRegistry as DisposableRegistry6 } from "@dimina-kit/electron-
4589
4910
 
4590
4911
  // src/main/ipc/bridge-router.ts
4591
4912
  import { app as app12, ipcMain as ipcMain3, protocol as protocol2, session as electronSession, webContents as webContents2 } from "electron";
4592
- import path16 from "node:path";
4913
+ import path17 from "node:path";
4593
4914
  import { pathToFileURL as pathToFileURL3 } from "node:url";
4594
4915
 
4595
4916
  // src/shared/simulator-api-metadata.ts
@@ -4598,13 +4919,40 @@ function isPersistentSimulatorApi(name) {
4598
4919
  return PERSISTENT_SIMULATOR_APIS.has(name);
4599
4920
  }
4600
4921
 
4922
+ // src/main/ipc/session-listener-bag.ts
4923
+ function createSessionListenerBag() {
4924
+ const records = [];
4925
+ let disposed = false;
4926
+ return {
4927
+ on(emitter, event, fn) {
4928
+ if (disposed) return;
4929
+ emitter.on(event, fn);
4930
+ records.push({ emitter, event, fn });
4931
+ },
4932
+ once(emitter, event, fn) {
4933
+ if (disposed) return;
4934
+ emitter.once(event, fn);
4935
+ records.push({ emitter, event, fn });
4936
+ },
4937
+ dispose() {
4938
+ if (disposed) return;
4939
+ disposed = true;
4940
+ for (const { emitter, event, fn } of records) {
4941
+ if (emitter.isDestroyed?.()) continue;
4942
+ emitter.removeListener(event, fn);
4943
+ }
4944
+ records.length = 0;
4945
+ }
4946
+ };
4947
+ }
4948
+
4601
4949
  // src/main/ipc/bridge-router.ts
4602
4950
  import { createDebugTap } from "@dimina-kit/electron-deck/main";
4603
4951
 
4604
4952
  // src/main/services/dimina-resource-server.ts
4605
4953
  import http from "node:http";
4606
4954
  import fs8 from "node:fs";
4607
- import path14 from "node:path";
4955
+ import path15 from "node:path";
4608
4956
  var MIME_TYPES = {
4609
4957
  ".css": "text/css; charset=utf-8",
4610
4958
  ".html": "text/html; charset=utf-8",
@@ -4620,7 +4968,7 @@ var MIME_TYPES = {
4620
4968
  ".wasm": "application/wasm"
4621
4969
  };
4622
4970
  async function startDiminaResourceServer(rootDir) {
4623
- const root = path14.resolve(rootDir);
4971
+ const root = path15.resolve(rootDir);
4624
4972
  const server = http.createServer((req, res) => {
4625
4973
  void handleRequest(root, req, res);
4626
4974
  });
@@ -4668,7 +5016,7 @@ async function handleRequest(root, req, res) {
4668
5016
  return;
4669
5017
  }
4670
5018
  res.writeHead(200, {
4671
- "content-type": MIME_TYPES[path14.extname(filePath)] ?? "application/octet-stream",
5019
+ "content-type": MIME_TYPES[path15.extname(filePath)] ?? "application/octet-stream",
4672
5020
  "content-length": stat.size,
4673
5021
  "cache-control": "no-store"
4674
5022
  });
@@ -4684,8 +5032,8 @@ async function handleRequest(root, req, res) {
4684
5032
  }
4685
5033
  function resolveContainedPath(root, pathname) {
4686
5034
  const relative = pathname.replace(/^\/+/, "") || "index.html";
4687
- const resolved = path14.resolve(root, relative);
4688
- if (resolved !== root && !resolved.startsWith(root + path14.sep)) return null;
5035
+ const resolved = path15.resolve(root, relative);
5036
+ if (resolved !== root && !resolved.startsWith(root + path15.sep)) return null;
4689
5037
  return resolved;
4690
5038
  }
4691
5039
  function setCorsHeaders(res) {
@@ -4697,11 +5045,11 @@ function setCorsHeaders(res) {
4697
5045
 
4698
5046
  // src/main/windows/service-host-window/create.ts
4699
5047
  import { app as app10, BrowserWindow as BrowserWindow4 } from "electron";
4700
- import path15 from "node:path";
5048
+ import path16 from "node:path";
4701
5049
  import { pathToFileURL as pathToFileURL2 } from "node:url";
4702
5050
  var SERVICE_HOST_PARTITION = SHARED_MINIAPP_PARTITION;
4703
- var serviceHostPreloadPath = path15.join(devtoolsPackageRoot, "dist/service-host/preload.cjs");
4704
- var serviceHostHtmlPath = path15.join(devtoolsPackageRoot, "dist/service-host/service.html");
5051
+ var serviceHostPreloadPath = path16.join(devtoolsPackageRoot, "dist/service-host/preload.cjs");
5052
+ var serviceHostHtmlPath = path16.join(devtoolsPackageRoot, "dist/service-host/service.html");
4705
5053
  function constructServiceHostWindow(opts = {}) {
4706
5054
  const partition = opts.partition ?? SERVICE_HOST_PARTITION;
4707
5055
  configureMiniappSession(partition);
@@ -5618,7 +5966,22 @@ function setupSimulatorStorage(host, options) {
5618
5966
  }
5619
5967
  }
5620
5968
  const storageApi = options.bridge?.isNativeHost() ? { invoke: runtimeInvoke } : null;
5621
- return { dispose: () => registry.dispose(), storageApi };
5969
+ function onSyncStorageChange(appId, change) {
5970
+ if (appId !== getActiveAppId()) return;
5971
+ if (host.isDestroyed()) return;
5972
+ switch (change.op) {
5973
+ case "set":
5974
+ pushSyntheticEvent({ type: "added", key: change.key, newValue: change.value });
5975
+ break;
5976
+ case "remove":
5977
+ pushSyntheticEvent({ type: "removed", key: change.key });
5978
+ break;
5979
+ case "clear":
5980
+ host.send(SimulatorStorageChannel.Event, { type: "cleared" });
5981
+ break;
5982
+ }
5983
+ }
5984
+ return { dispose: () => registry.dispose(), storageApi, onSyncStorageChange };
5622
5985
  }
5623
5986
  function findSimulatorWebContents() {
5624
5987
  for (const wc of wcStatic.getAllWebContents()) {
@@ -5744,7 +6107,8 @@ function installBridgeRouter(ctx) {
5744
6107
  const state = {
5745
6108
  appSessions: /* @__PURE__ */ new Map(),
5746
6109
  pageSessions: /* @__PURE__ */ new Map(),
5747
- wcIdToAppSessionId: /* @__PURE__ */ new Map(),
6110
+ serviceWcIdToAppSessionId: /* @__PURE__ */ new Map(),
6111
+ simulatorWcIdToAppSessionIds: /* @__PURE__ */ new Map(),
5748
6112
  wcIdToBridgeId: /* @__PURE__ */ new Map(),
5749
6113
  pendingApiCalls: /* @__PURE__ */ new Map(),
5750
6114
  pool: null,
@@ -5776,8 +6140,8 @@ function installBridgeRouter(ctx) {
5776
6140
  const onNativeHostQuery = (event) => {
5777
6141
  const reply = {
5778
6142
  enabled: true,
5779
- renderHostHtmlUrl: pathToFileURL3(path16.join(devtoolsPackageRoot, "dist/render-host/pageFrame.html")).toString(),
5780
- renderPreloadUrl: pathToFileURL3(path16.join(devtoolsPackageRoot, "dist/render-host/preload.cjs")).toString(),
6143
+ renderHostHtmlUrl: pathToFileURL3(path17.join(devtoolsPackageRoot, "dist/render-host/pageFrame.html")).toString(),
6144
+ renderPreloadUrl: pathToFileURL3(path17.join(devtoolsPackageRoot, "dist/render-host/preload.cjs")).toString(),
5781
6145
  device: currentDevice ?? void 0
5782
6146
  };
5783
6147
  event.returnValue = reply;
@@ -5852,9 +6216,34 @@ function installBridgeRouter(ctx) {
5852
6216
  return Promise.all(ids.map((id) => disposeAppSession(state, id))).then(() => {
5853
6217
  });
5854
6218
  },
5855
- debugTap: state.debugTap
6219
+ debugTap: state.debugTap,
6220
+ census: () => {
6221
+ const simulatorDestroyedListeners = {};
6222
+ let simulatorWcBindings = 0;
6223
+ for (const ids of state.simulatorWcIdToAppSessionIds.values()) simulatorWcBindings += ids.size;
6224
+ for (const ap of state.appSessions.values()) {
6225
+ const wc = ap.simulatorWc;
6226
+ if (!wc.isDestroyed() && simulatorDestroyedListeners[wc.id] === void 0) {
6227
+ simulatorDestroyedListeners[wc.id] = wc.listenerCount("destroyed");
6228
+ }
6229
+ }
6230
+ return {
6231
+ appSessions: state.appSessions.size,
6232
+ pageSessions: state.pageSessions.size,
6233
+ serviceWcBindings: state.serviceWcIdToAppSessionId.size,
6234
+ simulatorWcs: state.simulatorWcIdToAppSessionIds.size,
6235
+ simulatorWcBindings,
6236
+ renderWcBindings: state.wcIdToBridgeId.size,
6237
+ pendingApiCalls: state.pendingApiCalls.size,
6238
+ simulatorDestroyedListeners
6239
+ };
6240
+ }
5856
6241
  };
5857
6242
  ctx.bridge = bridgeHandle;
6243
+ if (process.env.NODE_ENV === "test") {
6244
+ ;
6245
+ globalThis.__diminaResourceCensus = () => bridgeHandle.census?.();
6246
+ }
5858
6247
  const consoleForwarder = createConsoleForwarder(bridgeHandle);
5859
6248
  ctx.consoleForwarder = consoleForwarder;
5860
6249
  ctx.guestConsole = consoleForwarder;
@@ -5866,8 +6255,7 @@ function installBridgeRouter(ctx) {
5866
6255
  const onActivePage = (event, payload) => {
5867
6256
  const ap = state.appSessions.get(payload.appSessionId);
5868
6257
  if (!ap) return;
5869
- const senderApp = appByWc(state, event.sender);
5870
- if (!senderApp || senderApp.appSessionId !== ap.appSessionId) return;
6258
+ if (!senderBoundToSession(state, event.sender, ap)) return;
5871
6259
  if (ap.pages.has(payload.bridgeId)) {
5872
6260
  ap.activeBridgeId = payload.bridgeId;
5873
6261
  const pagePath = state.pageSessions.get(payload.bridgeId)?.pagePath;
@@ -5881,8 +6269,7 @@ function installBridgeRouter(ctx) {
5881
6269
  const onPageStack = (event, payload) => {
5882
6270
  const ap = state.appSessions.get(payload.appSessionId);
5883
6271
  if (!ap) return;
5884
- const senderApp = appByWc(state, event.sender);
5885
- if (!senderApp || senderApp.appSessionId !== ap.appSessionId) return;
6272
+ if (!senderBoundToSession(state, event.sender, ap)) return;
5886
6273
  ap.pageStack = payload.stack;
5887
6274
  };
5888
6275
  ipcMain3.on(BRIDGE_CHANNELS.PAGE_STACK, onPageStack);
@@ -5912,8 +6299,7 @@ function installBridgeRouter(ctx) {
5912
6299
  handlePageLifecycle(state, event.sender, payload);
5913
6300
  if (payload.event === "pageUnload" && ctx.appData) {
5914
6301
  const ap = state.appSessions.get(payload.appSessionId);
5915
- const senderApp = appByWc(state, event.sender);
5916
- if (ap && senderApp && senderApp.appSessionId === ap.appSessionId) {
6302
+ if (ap && senderBoundToSession(state, event.sender, ap)) {
5917
6303
  ctx.appData.evictBridge(ap.appId, payload.bridgeId);
5918
6304
  }
5919
6305
  }
@@ -5932,9 +6318,8 @@ function installBridgeRouter(ctx) {
5932
6318
  const onDispose = (event, payload) => {
5933
6319
  const target = resolveAppByBridgeId(state, payload.bridgeId);
5934
6320
  if (!target) return;
5935
- const senderApp = appByWc(state, event.sender);
5936
- if (senderApp && senderApp.appSessionId !== target.appSessionId) {
5937
- console.warn(`[bridge-router] DISPOSE rejected: sender belongs to ${senderApp.appSessionId}, target ${target.appSessionId}`);
6321
+ if (!senderBoundToSession(state, event.sender, target) && appByWc(state, event.sender)) {
6322
+ console.warn(`[bridge-router] DISPOSE rejected: sender not bound to target ${target.appSessionId}`);
5938
6323
  return;
5939
6324
  }
5940
6325
  void disposeAppSession(state, target.appSessionId);
@@ -6033,7 +6418,7 @@ async function handleSpawn(state, ctx, event, opts) {
6033
6418
  const simulatorWc = resolveSimulatorWebContents(ctx, opts.simulatorWcId, event.sender);
6034
6419
  const pagePath = normalizePagePath(opts.pagePath || "pages/index/index");
6035
6420
  const workspaceProjectPath = typeof ctx.workspace.getProjectPath === "function" ? ctx.workspace.getProjectPath() : "";
6036
- const pkgRoot = path16.resolve(opts.pkgRoot || workspaceProjectPath || process.cwd());
6421
+ const pkgRoot = path17.resolve(opts.pkgRoot || workspaceProjectPath || process.cwd());
6037
6422
  const root = opts.root || "main";
6038
6423
  const apiNamespaces = ctx.apiNamespaces ?? [];
6039
6424
  let resourceServer = null;
@@ -6041,7 +6426,7 @@ async function handleSpawn(state, ctx, event, opts) {
6041
6426
  if (opts.resourceBaseUrl) {
6042
6427
  resourceBaseUrl = opts.resourceBaseUrl.endsWith("/") ? opts.resourceBaseUrl : `${opts.resourceBaseUrl}/`;
6043
6428
  } else {
6044
- resourceServer = await startDiminaResourceServer(path16.resolve(pkgRoot, root));
6429
+ resourceServer = await startDiminaResourceServer(path17.resolve(pkgRoot, root));
6045
6430
  resourceBaseUrl = resourceServer.baseUrl;
6046
6431
  }
6047
6432
  const selectedDevice = ctx.bridge?.getDevice?.() ?? null;
@@ -6100,8 +6485,9 @@ async function handleSpawn(state, ctx, event, opts) {
6100
6485
  pages: /* @__PURE__ */ new Map(),
6101
6486
  activeBridgeId: null,
6102
6487
  poolEntryId,
6103
- onServiceClosed: null,
6104
- onServiceBoot: null
6488
+ onServiceBoot: null,
6489
+ listenerBag: createSessionListenerBag(),
6490
+ registryHandle: null
6105
6491
  };
6106
6492
  const rootPage = {
6107
6493
  bridgeId,
@@ -6119,23 +6505,22 @@ async function handleSpawn(state, ctx, event, opts) {
6119
6505
  state.appSessions.set(appSessionId, appSession);
6120
6506
  state.pageSessions.set(bridgeId, rootPage);
6121
6507
  appSession.pages.set(bridgeId, rootPage);
6122
- bindWc(state.wcIdToAppSessionId, serviceWindow.webContents, appSessionId);
6123
- bindWc(state.wcIdToAppSessionId, simulatorWc, appSessionId);
6508
+ bindWc(state.serviceWcIdToAppSessionId, serviceWindow.webContents, appSessionId);
6509
+ bindSimulatorWc(state.simulatorWcIdToAppSessionIds, simulatorWc, appSessionId);
6124
6510
  state.connections.acquire(serviceWindow.webContents).own(() => {
6125
- if (state.wcIdToAppSessionId.get(appSession.serviceWc.id) === appSessionId) {
6126
- state.wcIdToAppSessionId.delete(appSession.serviceWc.id);
6511
+ if (state.serviceWcIdToAppSessionId.get(appSession.serviceWc.id) === appSessionId) {
6512
+ state.serviceWcIdToAppSessionId.delete(appSession.serviceWc.id);
6127
6513
  }
6128
6514
  });
6129
- ctx.registry.add(() => disposeAppSession(state, appSessionId));
6515
+ appSession.registryHandle = ctx.registry.add(() => disposeAppSession(state, appSessionId));
6130
6516
  const onServiceClosed = () => {
6131
6517
  void disposeAppSession(state, appSessionId, { serviceAlreadyClosed: true });
6132
6518
  };
6133
- appSession.onServiceClosed = onServiceClosed;
6134
- serviceWindow.once("closed", onServiceClosed);
6519
+ appSession.listenerBag.once(serviceWindow, "closed", onServiceClosed);
6135
6520
  const onSimulatorDestroyed = () => {
6136
6521
  void disposeAppSession(state, appSessionId);
6137
6522
  };
6138
- simulatorWc.once("destroyed", onSimulatorDestroyed);
6523
+ appSession.listenerBag.once(simulatorWc, "destroyed", onSimulatorDestroyed);
6139
6524
  if (simulatorWc.isDestroyed()) {
6140
6525
  void disposeAppSession(state, appSessionId);
6141
6526
  }
@@ -6183,8 +6568,7 @@ async function handleSpawn(state, ctx, event, opts) {
6183
6568
  async function handlePageOpen(state, event, opts) {
6184
6569
  const ap = state.appSessions.get(opts.appSessionId);
6185
6570
  if (!ap) throw new Error(`[bridge-router] PAGE_OPEN unknown appSession ${opts.appSessionId}`);
6186
- const senderApp = appByWc(state, event.sender);
6187
- if (!senderApp || senderApp.appSessionId !== opts.appSessionId) {
6571
+ if (!senderBoundToSession(state, event.sender, ap)) {
6188
6572
  throw new Error("[bridge-router] PAGE_OPEN rejected: caller not bound to app session");
6189
6573
  }
6190
6574
  const pagePath = normalizePagePath(opts.pagePath);
@@ -6217,8 +6601,7 @@ function handlePageClose(state, sender, payload) {
6217
6601
  }
6218
6602
  const ap = state.appSessions.get(page.appSessionId);
6219
6603
  if (!ap) return;
6220
- const senderApp = appByWc(state, sender);
6221
- if (!senderApp || senderApp.appSessionId !== ap.appSessionId) {
6604
+ if (!senderBoundToSession(state, sender, ap)) {
6222
6605
  console.warn("[bridge-router] PAGE_CLOSE rejected: caller not bound to app session");
6223
6606
  return;
6224
6607
  }
@@ -6227,8 +6610,7 @@ function handlePageClose(state, sender, payload) {
6227
6610
  function handlePageLifecycle(state, sender, payload) {
6228
6611
  const ap = state.appSessions.get(payload.appSessionId);
6229
6612
  if (!ap) return;
6230
- const senderApp = appByWc(state, sender);
6231
- if (!senderApp || senderApp.appSessionId !== ap.appSessionId) return;
6613
+ if (!senderBoundToSession(state, sender, ap)) return;
6232
6614
  forwardToService(ap, {
6233
6615
  type: payload.event,
6234
6616
  target: "service",
@@ -6238,8 +6620,7 @@ function handlePageLifecycle(state, sender, payload) {
6238
6620
  function handleNavCallback(state, sender, payload) {
6239
6621
  const ap = state.appSessions.get(payload.appSessionId);
6240
6622
  if (!ap) return;
6241
- const senderApp = appByWc(state, sender);
6242
- if (!senderApp || senderApp.appSessionId !== ap.appSessionId) return;
6623
+ if (!senderBoundToSession(state, sender, ap)) return;
6243
6624
  const result = { errMsg: payload.errMsg };
6244
6625
  if (payload.ok) {
6245
6626
  sendCallback(ap, payload.callbacks.success, result);
@@ -6255,18 +6636,23 @@ async function bootServiceHost(state, ap, ctx) {
6255
6636
  reportLogicLoadFailure(ap, ctx);
6256
6637
  return;
6257
6638
  }
6258
- forwardToService(ap, makeLoadResource(ap, ap.pages.get(ap.appSessionId), "service"));
6639
+ const rootPage = ap.pages.get(ap.appSessionId);
6640
+ const rootMissing = !!rootPage && !pageInManifest(ap, rootPage.pagePath);
6641
+ if (rootMissing) {
6642
+ reportPageNotFound(ap, ctx, rootPage.pagePath);
6643
+ } else if (rootPage) {
6644
+ forwardToService(ap, makeLoadResource(ap, rootPage, "service"));
6645
+ }
6259
6646
  for (const page of ap.pages.values()) {
6260
- if (page.renderLoadPending) {
6261
- page.renderLoadPending = false;
6262
- sendRenderLoadResource(ap, page);
6263
- }
6647
+ if (!page.renderLoadPending) continue;
6648
+ page.renderLoadPending = false;
6649
+ sendRenderLoadResource(ap, page);
6264
6650
  }
6265
6651
  }
6266
6652
  function sendRenderLoadResource(ap, page) {
6267
- if (page.renderWc && !page.renderWc.isDestroyed()) {
6268
- page.renderWc.send(BRIDGE_CHANNELS.TO_RENDER, { msg: makeLoadResource(ap, page, "render") });
6269
- }
6653
+ if (!page.renderWc || page.renderWc.isDestroyed()) return;
6654
+ if (!pageInManifest(ap, page.pagePath)) return;
6655
+ page.renderWc.send(BRIDGE_CHANNELS.TO_RENDER, { msg: makeLoadResource(ap, page, "render") });
6270
6656
  }
6271
6657
  function logicBundleUrl(ap) {
6272
6658
  return ap.resourceServer ? new URL("logic.js", ap.resourceBaseUrl).toString() : new URL(`${ap.appId}/${ap.root}/logic.js`, ap.resourceBaseUrl).toString();
@@ -6291,6 +6677,14 @@ function reportLogicLoadFailure(ap, ctx) {
6291
6677
  console.error(message);
6292
6678
  ctx.guestConsole?.emit({ source: "service", level: "error", args: [message] });
6293
6679
  }
6680
+ function pageInManifest(ap, pagePath) {
6681
+ return ap.manifest.pages.includes(normalizePagePath(pagePath));
6682
+ }
6683
+ function reportPageNotFound(ap, ctx, pagePath) {
6684
+ const message = `Page[${pagePath}] not found. May be caused by: 1. Forgetting to add page route in app.json. 2. Invoking Page() in async task.`;
6685
+ console.error(`[bridge-router] ${message}`);
6686
+ ctx.guestConsole?.emit({ source: "service", level: "error", args: [message] });
6687
+ }
6294
6688
  function maybeSendResourceLoaded(ap, page) {
6295
6689
  if (page.resourceLoadedSent || !ap.serviceLoaded || !page.renderLoaded) return;
6296
6690
  page.resourceLoadedSent = true;
@@ -6379,6 +6773,12 @@ function handleContainerMsg(ap, page, msg, ctx, state) {
6379
6773
  case "consoleLog":
6380
6774
  ctx.guestConsole?.emit(msg.body);
6381
6775
  break;
6776
+ case "storageChanged":
6777
+ ctx.onServiceStorageChanged?.(ap.appId, msg.body);
6778
+ break;
6779
+ case "wxmlChanged":
6780
+ state.emitRenderEvent({ kind: "domMutated", appId: ap.appId, bridgeId: page.bridgeId });
6781
+ break;
6382
6782
  default:
6383
6783
  break;
6384
6784
  }
@@ -6590,8 +6990,7 @@ function handleApiResponse(state, sender, payload) {
6590
6990
  clearTimeout(pending.timer);
6591
6991
  return;
6592
6992
  }
6593
- const senderApp = appByWc(state, sender);
6594
- if (!senderApp || senderApp.appSessionId !== ap.appSessionId) {
6993
+ if (!senderBoundToSession(state, sender, ap)) {
6595
6994
  console.warn("[bridge-router] API_RESPONSE rejected: sender not bound to app session");
6596
6995
  return;
6597
6996
  }
@@ -6696,8 +7095,14 @@ function pageFromMsg(state, ap, msg) {
6696
7095
  }
6697
7096
  function appByWc(state, wc) {
6698
7097
  if (wc.isDestroyed()) return void 0;
6699
- const appSessionId = state.wcIdToAppSessionId.get(wc.id);
7098
+ const appSessionId = state.serviceWcIdToAppSessionId.get(wc.id);
6700
7099
  if (appSessionId) return state.appSessions.get(appSessionId);
7100
+ const hosted = state.simulatorWcIdToAppSessionIds.get(wc.id);
7101
+ if (hosted) {
7102
+ let latest;
7103
+ for (const id of hosted) latest = state.appSessions.get(id) ?? latest;
7104
+ if (latest) return latest;
7105
+ }
6701
7106
  const bridgeId = state.wcIdToBridgeId.get(wc.id);
6702
7107
  if (bridgeId) {
6703
7108
  const page = state.pageSessions.get(bridgeId);
@@ -6705,6 +7110,12 @@ function appByWc(state, wc) {
6705
7110
  }
6706
7111
  return void 0;
6707
7112
  }
7113
+ function senderBoundToSession(state, sender, ap) {
7114
+ if (sender.isDestroyed()) return false;
7115
+ const hosted = state.simulatorWcIdToAppSessionIds.get(sender.id);
7116
+ if (hosted) return hosted.has(ap.appSessionId);
7117
+ return appByWc(state, sender)?.appSessionId === ap.appSessionId;
7118
+ }
6708
7119
  function resolveAppByBridgeId(state, bridgeId) {
6709
7120
  if (state.appSessions.has(bridgeId)) return state.appSessions.get(bridgeId);
6710
7121
  const page = state.pageSessions.get(bridgeId);
@@ -6715,6 +7126,15 @@ function bindWc(map, wc, id) {
6715
7126
  if (wc.isDestroyed()) return;
6716
7127
  map.set(wc.id, id);
6717
7128
  }
7129
+ function bindSimulatorWc(map, wc, id) {
7130
+ if (wc.isDestroyed()) return;
7131
+ let hosted = map.get(wc.id);
7132
+ if (!hosted) {
7133
+ hosted = /* @__PURE__ */ new Set();
7134
+ map.set(wc.id, hosted);
7135
+ }
7136
+ hosted.add(id);
7137
+ }
6718
7138
  function readBridgeId(msg) {
6719
7139
  return typeof msg.body?.bridgeId === "string" ? msg.body.bridgeId : void 0;
6720
7140
  }
@@ -6736,6 +7156,9 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
6736
7156
  const ap = state.appSessions.get(appSessionId);
6737
7157
  if (!ap) return;
6738
7158
  state.appSessions.delete(appSessionId);
7159
+ const registryHandle = ap.registryHandle;
7160
+ ap.registryHandle = null;
7161
+ void registryHandle?.dispose();
6739
7162
  state.appLifecycle.dispose(appSessionId);
6740
7163
  state.evictAppDataBridges(ap);
6741
7164
  for (const [requestId, pending] of state.pendingApiCalls) {
@@ -6754,11 +7177,9 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
6754
7177
  state.pageSessions.delete(page.bridgeId);
6755
7178
  }
6756
7179
  ap.pages.clear();
7180
+ ap.listenerBag.dispose();
6757
7181
  if (ap.poolEntryId !== null && state.pool && !opts.serviceAlreadyClosed) {
6758
7182
  state.connections.reset(ap.serviceWc.id);
6759
- if (ap.onServiceClosed && !ap.serviceWindow.isDestroyed()) {
6760
- ap.serviceWindow.removeListener("closed", ap.onServiceClosed);
6761
- }
6762
7183
  if (ap.onServiceBoot && !ap.serviceWindow.isDestroyed()) {
6763
7184
  ap.serviceWindow.webContents.removeListener("did-finish-load", ap.onServiceBoot);
6764
7185
  }
@@ -6770,8 +7191,14 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
6770
7191
  } else if (!opts.serviceAlreadyClosed && !ap.serviceWindow.isDestroyed()) {
6771
7192
  ap.serviceWindow.close();
6772
7193
  }
6773
- state.wcIdToAppSessionId.delete(ap.serviceWc.id);
6774
- state.wcIdToAppSessionId.delete(ap.simulatorWc.id);
7194
+ if (state.serviceWcIdToAppSessionId.get(ap.serviceWc.id) === appSessionId) {
7195
+ state.serviceWcIdToAppSessionId.delete(ap.serviceWc.id);
7196
+ }
7197
+ const hosted = state.simulatorWcIdToAppSessionIds.get(ap.simulatorWc.id);
7198
+ if (hosted) {
7199
+ hosted.delete(appSessionId);
7200
+ if (hosted.size === 0) state.simulatorWcIdToAppSessionIds.delete(ap.simulatorWc.id);
7201
+ }
6775
7202
  if (ap.resourceServer) {
6776
7203
  await ap.resourceServer.close().catch((error) => {
6777
7204
  console.warn("[bridge-router] resource server close failed:", error);
@@ -6893,27 +7320,13 @@ function makeHostEnv(snapshot) {
6893
7320
  // src/main/ipc/views.ts
6894
7321
  import { ipcMain as ipcMain4 } from "electron";
6895
7322
  function registerViewsIpc(ctx) {
6896
- const registry = new IpcRegistry(ctx.senderPolicy).handle(ViewChannel.SimulatorDevtoolsBounds, (_event, ...args) => {
6897
- const [bounds] = validate(
6898
- ViewChannel.SimulatorDevtoolsBounds,
6899
- ViewBoundsSchema,
6900
- args
6901
- );
6902
- ctx.views.setSimulatorDevtoolsBounds(bounds);
6903
- }).handle(ViewChannel.WorkbenchBounds, (_event, ...args) => {
6904
- const [bounds] = validate(
6905
- ViewChannel.WorkbenchBounds,
6906
- ViewBoundsSchema,
6907
- args
6908
- );
6909
- ctx.views.setWorkbenchBounds(bounds);
6910
- }).handle(ViewChannel.HostToolbarBounds, (_event, ...args) => {
6911
- const [bounds] = validate(
6912
- ViewChannel.HostToolbarBounds,
6913
- ViewBoundsSchema,
7323
+ const registry = new IpcRegistry(ctx.senderPolicy).handle(ViewChannel.PlacementSnapshot, (_event, ...args) => {
7324
+ const [snapshot] = validate(
7325
+ ViewChannel.PlacementSnapshot,
7326
+ PlacementSnapshotSchema,
6914
7327
  args
6915
7328
  );
6916
- ctx.views.setHostToolbarBounds(bounds);
7329
+ ctx.views.setPlacementSnapshot(snapshot);
6917
7330
  }).handle(ViewChannel.HostToolbarGetHeight, () => ctx.views.getHostToolbarHeight());
6918
7331
  const onAdvertiseHeight = (event, ...args) => {
6919
7332
  if (event.sender.id !== ctx.views.getHostToolbarWebContentsId()) return;
@@ -8503,6 +8916,11 @@ function createNetworkForwarder(bridge) {
8503
8916
  trimQueue();
8504
8917
  return;
8505
8918
  }
8919
+ if (!isFrontendSettled(wc)) {
8920
+ trimQueue();
8921
+ scheduleReadyRetry();
8922
+ return;
8923
+ }
8506
8924
  if (sink === "idle") beginProbing();
8507
8925
  const batch = [];
8508
8926
  let batchChars = 0;
@@ -8813,21 +9231,87 @@ import { DisposableRegistry as DisposableRegistry8 } from "@dimina-kit/electron-
8813
9231
  function setupSimulatorWxml(host, options) {
8814
9232
  const { bridge, inspector, getActiveAppId } = options;
8815
9233
  const registry = new DisposableRegistry8();
8816
- async function pull() {
8817
- const wc = bridge.getActiveRenderWc(getActiveAppId() ?? void 0);
9234
+ let active = false;
9235
+ let seq = 0;
9236
+ let pulling = false;
9237
+ let again = false;
9238
+ let observedWc = null;
9239
+ function activeWc() {
9240
+ return bridge.getActiveRenderWc(getActiveAppId() ?? void 0);
9241
+ }
9242
+ async function pull(wc) {
8818
9243
  if (!wc) return null;
8819
9244
  return inspector.getWxml(wc);
8820
9245
  }
9246
+ function schedulePull() {
9247
+ if (!active) return;
9248
+ const mySeq = ++seq;
9249
+ if (pulling) {
9250
+ again = true;
9251
+ return;
9252
+ }
9253
+ pulling = true;
9254
+ void pull(activeWc()).then((tree) => {
9255
+ if (mySeq === seq && active && !host.isDestroyed()) {
9256
+ host.send(SimulatorWxmlChannel.Event, tree);
9257
+ }
9258
+ }).finally(() => {
9259
+ pulling = false;
9260
+ if (again) {
9261
+ again = false;
9262
+ schedulePull();
9263
+ }
9264
+ });
9265
+ }
9266
+ function startObserving(wc) {
9267
+ if (!wc) return;
9268
+ observedWc = wc;
9269
+ void inspector.setWxmlObserving(wc, true);
9270
+ }
9271
+ function stopObserving() {
9272
+ const wc = observedWc;
9273
+ observedWc = null;
9274
+ if (wc) void inspector.setWxmlObserving(wc, false);
9275
+ }
9276
+ function reconcileObserving() {
9277
+ if (!active) return;
9278
+ const wc = activeWc();
9279
+ if (wc !== observedWc) {
9280
+ stopObserving();
9281
+ startObserving(wc);
9282
+ }
9283
+ }
9284
+ function cancelPending() {
9285
+ seq++;
9286
+ again = false;
9287
+ }
8821
9288
  const ipc = new IpcRegistry(options.senderPolicy);
8822
- ipc.handle(SimulatorWxmlChannel.GetSnapshot, () => pull());
9289
+ ipc.handle(SimulatorWxmlChannel.GetSnapshot, () => pull(activeWc()));
9290
+ ipc.handle(SimulatorWxmlChannel.SetActive, (_event, on) => {
9291
+ if (on) {
9292
+ active = true;
9293
+ reconcileObserving();
9294
+ schedulePull();
9295
+ } else {
9296
+ active = false;
9297
+ cancelPending();
9298
+ stopObserving();
9299
+ }
9300
+ });
8823
9301
  registry.add(ipc);
8824
- const unsubscribe = bridge.onRenderEvent(() => {
8825
- void pull().then((tree) => {
8826
- if (host.isDestroyed()) return;
8827
- host.send(SimulatorWxmlChannel.Event, tree);
8828
- });
9302
+ const unsubscribe = bridge.onRenderEvent((event) => {
9303
+ if (!active) return;
9304
+ const appId = getActiveAppId();
9305
+ if (appId && event.appId !== appId) return;
9306
+ reconcileObserving();
9307
+ schedulePull();
8829
9308
  });
8830
9309
  registry.add(unsubscribe);
9310
+ registry.add(() => {
9311
+ active = false;
9312
+ cancelPending();
9313
+ stopObserving();
9314
+ });
8831
9315
  return registry;
8832
9316
  }
8833
9317
 
@@ -9037,7 +9521,7 @@ function setupSimulatorCurrentPage(host, options) {
9037
9521
 
9038
9522
  // src/main/services/render-inspect/index.ts
9039
9523
  import { readFileSync } from "node:fs";
9040
- import path17 from "node:path";
9524
+ import path18 from "node:path";
9041
9525
  var DEFAULT_SOURCE_PATH = "dist/render-host/render-inspect.js";
9042
9526
  var HIGHLIGHT_CONFIG = {
9043
9527
  showInfo: true,
@@ -9063,7 +9547,7 @@ function withTimeout(p, ms) {
9063
9547
  });
9064
9548
  }
9065
9549
  function createRenderInspector(options = {}) {
9066
- const loadSource = options.loadSource ?? (() => readFileSync(path17.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
9550
+ const loadSource = options.loadSource ?? (() => readFileSync(path18.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
9067
9551
  let cachedSource = null;
9068
9552
  const injected = /* @__PURE__ */ new Set();
9069
9553
  const selfAttached = /* @__PURE__ */ new Set();
@@ -9209,20 +9693,30 @@ function createRenderInspector(options = {}) {
9209
9693
  } catch {
9210
9694
  }
9211
9695
  }
9212
- return { getWxml, highlight, unhighlight };
9696
+ async function setWxmlObserving(wc, on) {
9697
+ if (wc.isDestroyed()) return;
9698
+ if (!await ensureInjected(wc)) return;
9699
+ try {
9700
+ await wc.executeJavaScript(
9701
+ `window.__diminaRenderInspect && window.__diminaRenderInspect.setWxmlObserving(${on ? "true" : "false"})`
9702
+ );
9703
+ } catch {
9704
+ }
9705
+ }
9706
+ return { getWxml, highlight, unhighlight, setWxmlObserving };
9213
9707
  }
9214
9708
 
9215
9709
  // src/main/services/simulator-temp-files/index.ts
9216
9710
  import { toDisposable as toDisposable6 } from "@dimina-kit/electron-deck/main";
9217
9711
 
9218
9712
  // src/main/services/simulator-temp-files/store.ts
9219
- function registerTempFile(store, path23, mime, bytes) {
9713
+ function registerTempFile(store, path24, mime, bytes) {
9220
9714
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
9221
- store.delete(path23);
9222
- store.set(path23, { bytes: buf, mime });
9715
+ store.delete(path24);
9716
+ store.set(path24, { bytes: buf, mime });
9223
9717
  }
9224
- function revokeTempFile(store, path23) {
9225
- store.delete(path23);
9718
+ function revokeTempFile(store, path24) {
9719
+ store.delete(path24);
9226
9720
  }
9227
9721
  function revokeAllTempFiles(store) {
9228
9722
  store.clear();
@@ -9230,14 +9724,14 @@ function revokeAllTempFiles(store) {
9230
9724
 
9231
9725
  // src/shared/vpath.ts
9232
9726
  import os from "node:os";
9233
- import path18 from "node:path";
9727
+ import path19 from "node:path";
9234
9728
  var DIFILE_PREFIX = "difile://";
9235
9729
  function sandboxBase() {
9236
9730
  const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
9237
9731
  if (env) {
9238
- return path18.join(env, "files");
9732
+ return path19.join(env, "files");
9239
9733
  }
9240
- return path18.join(os.homedir(), ".dimina", "files");
9734
+ return path19.join(os.homedir(), ".dimina", "files");
9241
9735
  }
9242
9736
  function resolveVPath(url) {
9243
9737
  if (typeof url !== "string") return null;
@@ -9271,15 +9765,15 @@ function resolveVPath(url) {
9271
9765
  return { kind, writable };
9272
9766
  }
9273
9767
  const base = sandboxBase();
9274
- const joined = path18.join(base, decoded);
9275
- const normalized = path18.normalize(joined);
9276
- if (normalized !== base && !normalized.startsWith(base + path18.sep)) return null;
9768
+ const joined = path19.join(base, decoded);
9769
+ const normalized = path19.normalize(joined);
9770
+ if (normalized !== base && !normalized.startsWith(base + path19.sep)) return null;
9277
9771
  return { kind, writable, realPath: normalized };
9278
9772
  }
9279
9773
 
9280
9774
  // src/main/services/simulator-temp-files/disk.ts
9281
9775
  import fs9 from "node:fs/promises";
9282
- import path19 from "node:path";
9776
+ import path20 from "node:path";
9283
9777
  var EXT_MIME = {
9284
9778
  ".txt": "text/plain",
9285
9779
  ".log": "text/plain",
@@ -9344,7 +9838,7 @@ function sniffMime(head) {
9344
9838
  return null;
9345
9839
  }
9346
9840
  function extMime(realPath) {
9347
- const ext = path19.extname(realPath).toLowerCase();
9841
+ const ext = path20.extname(realPath).toLowerCase();
9348
9842
  if (!ext) return null;
9349
9843
  return EXT_MIME[ext] ?? null;
9350
9844
  }
@@ -9414,7 +9908,7 @@ async function readDiskFile(realPath, opts) {
9414
9908
  }
9415
9909
  async function writeDiskFile(realPath, bytes) {
9416
9910
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
9417
- await fs9.mkdir(path19.dirname(realPath), { recursive: true });
9911
+ await fs9.mkdir(path20.dirname(realPath), { recursive: true });
9418
9912
  await fs9.writeFile(realPath, buf);
9419
9913
  }
9420
9914
  async function readDiskDir(realPath) {
@@ -9556,28 +10050,28 @@ async function handleDifileRequest(ctx, req) {
9556
10050
 
9557
10051
  // src/main/services/simulator-temp-files/fs-channels.ts
9558
10052
  import fs10 from "node:fs/promises";
9559
- import path20 from "node:path";
10053
+ import path21 from "node:path";
9560
10054
  async function enforceSandbox(realPath) {
9561
10055
  if (typeof realPath !== "string" || realPath.length === 0) {
9562
10056
  throw new Error("sandbox: realPath must be a non-empty string");
9563
10057
  }
9564
10058
  const base = sandboxBase();
9565
10059
  const baseReal = await fs10.realpath(base).catch(() => base);
9566
- const normalized = path20.normalize(realPath);
9567
- if (normalized !== base && !normalized.startsWith(base + path20.sep)) {
10060
+ const normalized = path21.normalize(realPath);
10061
+ if (normalized !== base && !normalized.startsWith(base + path21.sep)) {
9568
10062
  throw new Error("sandbox: realPath escapes the user-data base");
9569
10063
  }
9570
10064
  let probe = normalized;
9571
- while (probe !== path20.parse(probe).root) {
10065
+ while (probe !== path21.parse(probe).root) {
9572
10066
  try {
9573
10067
  const resolved = await fs10.realpath(probe);
9574
- if (resolved !== baseReal && !resolved.startsWith(baseReal + path20.sep)) {
10068
+ if (resolved !== baseReal && !resolved.startsWith(baseReal + path21.sep)) {
9575
10069
  throw new Error("sandbox: realPath escapes the user-data base via symlink");
9576
10070
  }
9577
10071
  break;
9578
10072
  } catch (err) {
9579
10073
  if (err.code === "ENOENT") {
9580
- probe = path20.dirname(probe);
10074
+ probe = path21.dirname(probe);
9581
10075
  continue;
9582
10076
  }
9583
10077
  throw err;
@@ -9633,10 +10127,10 @@ function setupSimulatorTempFiles(simSession) {
9633
10127
  const store = /* @__PURE__ */ new Map();
9634
10128
  const pendingWaiters = /* @__PURE__ */ new Map();
9635
10129
  let disposed = false;
9636
- function drainWaiters(path23) {
9637
- const list = pendingWaiters.get(path23);
10130
+ function drainWaiters(path24) {
10131
+ const list = pendingWaiters.get(path24);
9638
10132
  if (!list) return;
9639
- pendingWaiters.delete(path23);
10133
+ pendingWaiters.delete(path24);
9640
10134
  for (const fn of list) fn();
9641
10135
  }
9642
10136
  function drainAllWaiters() {
@@ -9649,15 +10143,15 @@ function setupSimulatorTempFiles(simSession) {
9649
10143
  const registry = new IpcRegistry(simulatorOnlyPolicy);
9650
10144
  registry.on("simulator:temp-file:write", (_event, payload) => {
9651
10145
  if (disposed) return;
9652
- const { path: path23, mime, bytes } = payload;
9653
- registerTempFile(store, path23, mime, bytes);
10146
+ const { path: path24, mime, bytes } = payload;
10147
+ registerTempFile(store, path24, mime, bytes);
9654
10148
  enforceStoreCap(store);
9655
- drainWaiters(path23);
10149
+ drainWaiters(path24);
9656
10150
  });
9657
10151
  registry.on("simulator:temp-file:revoke", (_event, payload) => {
9658
10152
  if (disposed) return;
9659
- const { path: path23 } = payload;
9660
- revokeTempFile(store, path23);
10153
+ const { path: path24 } = payload;
10154
+ revokeTempFile(store, path24);
9661
10155
  });
9662
10156
  registry.on("simulator:temp-file:revoke-all", () => {
9663
10157
  if (disposed) return;
@@ -9795,7 +10289,7 @@ function setupSimulatorSessionPolicy() {
9795
10289
  import http2 from "node:http";
9796
10290
  import nodeFs2 from "node:fs";
9797
10291
  import fs11 from "node:fs/promises";
9798
- import path21 from "node:path";
10292
+ import path22 from "node:path";
9799
10293
  var MAX_FS_WRITE_BYTES = 32 * 1024 * 1024;
9800
10294
  var MIME = {
9801
10295
  ".html": "text/html; charset=utf-8",
@@ -9819,8 +10313,8 @@ function setIsolationHeaders(res) {
9819
10313
  }
9820
10314
  function containedStaticPath(root, pathname) {
9821
10315
  const rel = pathname.replace(/^\/+/, "") || "index.html";
9822
- const resolved = path21.resolve(root, rel);
9823
- if (resolved !== root && !resolved.startsWith(root + path21.sep)) return null;
10316
+ const resolved = path22.resolve(root, rel);
10317
+ if (resolved !== root && !resolved.startsWith(root + path22.sep)) return null;
9824
10318
  return resolved;
9825
10319
  }
9826
10320
  function serveStaticFile(res, root, pathname) {
@@ -9831,7 +10325,7 @@ function serveStaticFile(res, root, pathname) {
9831
10325
  return;
9832
10326
  }
9833
10327
  Promise.all([fs11.realpath(root), fs11.realpath(candidate)]).then(([realRoot, realFile]) => {
9834
- if (realFile !== realRoot && !realFile.startsWith(realRoot + path21.sep)) {
10328
+ if (realFile !== realRoot && !realFile.startsWith(realRoot + path22.sep)) {
9835
10329
  res.writeHead(403);
9836
10330
  res.end("Forbidden");
9837
10331
  return;
@@ -9843,7 +10337,7 @@ function serveStaticFile(res, root, pathname) {
9843
10337
  return;
9844
10338
  }
9845
10339
  res.writeHead(200, {
9846
- "Content-Type": MIME[path21.extname(realFile)] ?? "application/octet-stream",
10340
+ "Content-Type": MIME[path22.extname(realFile)] ?? "application/octet-stream",
9847
10341
  "Content-Length": stat.size,
9848
10342
  "Cache-Control": "no-store"
9849
10343
  });
@@ -9985,7 +10479,7 @@ async function listFilesRecursive(dir, base = "") {
9985
10479
  for (const e of entries) {
9986
10480
  const rel = base ? `${base}/${e.name}` : e.name;
9987
10481
  if (e.isDirectory()) {
9988
- out.push(...await listFilesRecursive(path21.join(dir, e.name), rel));
10482
+ out.push(...await listFilesRecursive(path22.join(dir, e.name), rel));
9989
10483
  } else if (e.isFile()) {
9990
10484
  out.push(rel);
9991
10485
  }
@@ -10002,9 +10496,9 @@ async function readContributedExtensions(extensionsDir) {
10002
10496
  }
10003
10497
  for (const e of entries) {
10004
10498
  if (!e.isDirectory()) continue;
10005
- const extDir = path21.join(extensionsDir, e.name);
10499
+ const extDir = path22.join(extensionsDir, e.name);
10006
10500
  try {
10007
- const pkgRaw = await fs11.readFile(path21.join(extDir, "package.json"), "utf8");
10501
+ const pkgRaw = await fs11.readFile(path22.join(extDir, "package.json"), "utf8");
10008
10502
  const files = await listFilesRecursive(extDir);
10009
10503
  out.push({ dir: e.name, packageJson: JSON.parse(pkgRaw), files });
10010
10504
  } catch {
@@ -10013,8 +10507,8 @@ async function readContributedExtensions(extensionsDir) {
10013
10507
  return out;
10014
10508
  }
10015
10509
  async function startWorkbenchCoiServer(options) {
10016
- const root = path21.resolve(options.rootDir);
10017
- const contribRoot = options.extensionsDir ? path21.resolve(options.extensionsDir) : null;
10510
+ const root = path22.resolve(options.rootDir);
10511
+ const contribRoot = options.extensionsDir ? path22.resolve(options.extensionsDir) : null;
10018
10512
  const server = http2.createServer((req, res) => {
10019
10513
  setIsolationHeaders(res);
10020
10514
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -10232,7 +10726,7 @@ async function disposeContext(ctx) {
10232
10726
  function createConfiguredMainWindow(config, rendererDir2) {
10233
10727
  const mainWindow = createMainWindow({
10234
10728
  title: config.appName ?? "Dimina DevTools",
10235
- indexHtml: path22.join(rendererDir2, "entries/main/index.html"),
10729
+ indexHtml: path23.join(rendererDir2, "entries/main/index.html"),
10236
10730
  width: config.window?.width,
10237
10731
  height: config.window?.height,
10238
10732
  minWidth: config.window?.minWidth,
@@ -10368,6 +10862,7 @@ function runDevtoolsBootstrap(config = {}) {
10368
10862
  }
10369
10863
  setupCdpPort();
10370
10864
  suppressInsecureCspWarnings();
10865
+ if (!app15.isPackaged) installMaxListenersWarningDiagnostic();
10371
10866
  registerDifileScheme();
10372
10867
  try {
10373
10868
  app15.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
@@ -10492,6 +10987,10 @@ async function createDevtoolsRuntime(config = {}) {
10492
10987
  context.registry.add(() => {
10493
10988
  context.storageApi = void 0;
10494
10989
  });
10990
+ context.onServiceStorageChanged = storage.onSyncStorageChange;
10991
+ context.registry.add(() => {
10992
+ context.onServiceStorageChanged = void 0;
10993
+ });
10495
10994
  }
10496
10995
  if (context.bridge?.isNativeHost()) {
10497
10996
  const networkForward = createNetworkForwarder({
@@ -10522,8 +11021,8 @@ async function createDevtoolsRuntime(config = {}) {
10522
11021
  bridge: context.bridge
10523
11022
  }));
10524
11023
  }
10525
- const bundleDir = config.editorViewConfig?.bundleDir ?? path22.join(devtoolsPackageRoot, "dist/vscode-workbench");
10526
- if (!fs12.existsSync(path22.join(bundleDir, "index.html"))) {
11024
+ const bundleDir = config.editorViewConfig?.bundleDir ?? path23.join(devtoolsPackageRoot, "dist/vscode-workbench");
11025
+ if (!fs12.existsSync(path23.join(bundleDir, "index.html"))) {
10527
11026
  console.warn(
10528
11027
  `[workbench] editor bundle not found at ${bundleDir} \u2014 skipping embedded editor assembly`
10529
11028
  );