@dimina-kit/devtools 0.4.0-dev.20260711141929 → 0.4.0-dev.20260716163758

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 (59) hide show
  1. package/dist/main/app/app.js +39 -10
  2. package/dist/main/app/native-overview.d.ts +1 -1
  3. package/dist/main/index.bundle.js +189 -172
  4. package/dist/main/services/simulator-appdata/index.js +1 -1
  5. package/dist/main/services/views/native-simulator-view.d.ts +6 -0
  6. package/dist/main/services/views/native-simulator-view.js +16 -0
  7. package/dist/main/services/views/refresh-styles.d.ts +10 -0
  8. package/dist/main/services/views/refresh-styles.js +57 -0
  9. package/dist/main/services/views/view-manager.d.ts +26 -1
  10. package/dist/main/services/views/view-manager.js +7 -0
  11. package/dist/main/services/views/workbench-view.d.ts +2 -0
  12. package/dist/main/services/views/workbench-view.js +87 -7
  13. package/dist/main/services/workspace/open-project-guards.d.ts +16 -0
  14. package/dist/main/services/workspace/open-project-guards.js +42 -0
  15. package/dist/main/services/workspace/rebuild-status.d.ts +24 -0
  16. package/dist/main/services/workspace/rebuild-status.js +20 -0
  17. package/dist/main/services/workspace/workspace-lifecycle-race.testutil.d.ts +2 -0
  18. package/dist/main/services/workspace/workspace-lifecycle-race.testutil.js +4 -0
  19. package/dist/main/services/workspace/workspace-service.js +21 -36
  20. package/dist/native-host/common/common.js +1 -1
  21. package/dist/native-host/render/render.js +49 -34
  22. package/dist/native-host/service/service.js +2 -2
  23. package/dist/preload/instrumentation/app-data.d.ts +2 -2
  24. package/dist/preload/instrumentation/app-data.js +1 -1
  25. package/dist/preload/windows/host-toolbar-runtime.cjs.map +1 -1
  26. package/dist/preload/windows/main.cjs +96 -0
  27. package/dist/preload/windows/main.cjs.map +2 -2
  28. package/dist/preload/windows/simulator.cjs +162 -140
  29. package/dist/preload/windows/simulator.cjs.map +3 -3
  30. package/dist/preload/windows/simulator.js +162 -140
  31. package/dist/render-host/render-inspect.js +15 -47
  32. package/dist/renderer/assets/index-DQnIsRhp.js +49 -0
  33. package/dist/renderer/assets/{input-COtRON-n.js → input-7P0WbXyg.js} +2 -2
  34. package/dist/renderer/assets/ipc-transport-BGtDnv6p.css +1 -0
  35. package/dist/renderer/assets/{ipc-transport-SuI2rOB_.js → ipc-transport-DTEmLpxx.js} +2 -2
  36. package/dist/renderer/assets/popover-DsOcyIS6.js +2 -0
  37. package/dist/renderer/assets/{select-Bwb8O0Mf.js → select-Bzbhbg9Z.js} +2 -2
  38. package/dist/renderer/assets/{settings-BispnvJU.js → settings-CR1TyBWu.js} +2 -2
  39. package/dist/renderer/assets/{settings-api-HIBz8BFz.js → settings-api-B1wVFAPB.js} +2 -2
  40. package/dist/renderer/assets/workbenchSettings-CFbToT4Q.js +8 -0
  41. package/dist/renderer/entries/main/index.html +6 -6
  42. package/dist/renderer/entries/popover/index.html +5 -5
  43. package/dist/renderer/entries/settings/index.html +5 -5
  44. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  45. package/dist/shared/ipc-channels.d.ts +1 -0
  46. package/dist/shared/ipc-channels.js +7 -0
  47. package/dist/simulator/assets/device-shell-C-HZY3bL.css +1 -0
  48. package/dist/simulator/assets/device-shell-Cu1ZPLfe.js +2 -0
  49. package/dist/simulator/assets/{simulator-A1vGZVBM.js → simulator-Cy8c-eA2.js} +5 -5
  50. package/dist/simulator/simulator.html +1 -1
  51. package/package.json +7 -7
  52. package/dist/renderer/assets/index-WbvXdhWQ.js +0 -49
  53. package/dist/renderer/assets/ipc-transport-CNvORIEX.css +0 -1
  54. package/dist/renderer/assets/popover-B73T3z-m.js +0 -2
  55. package/dist/renderer/assets/workbenchSettings-CtRBr7gS.js +0 -8
  56. package/dist/shared/appdata-accumulator.d.ts +0 -95
  57. package/dist/shared/appdata-accumulator.js +0 -218
  58. package/dist/simulator/assets/device-shell-C-wcFq3Z.js +0 -2
  59. package/dist/simulator/assets/device-shell-CS1rqAQz.css +0 -1
@@ -30,6 +30,7 @@ import { startWorkbenchCoiServer } from '../services/workbench-coi-server.js';
30
30
  import { UpdateManager } from '../services/update/index.js';
31
31
  import { toDisposable } from '@dimina-kit/electron-deck/main';
32
32
  import { IpcRegistry } from '../utils/ipc-registry.js';
33
+ import { WindowChannel } from '../../shared/ipc-channels.js';
33
34
  const DEFAULT_MODULES = {
34
35
  projects: true,
35
36
  session: true,
@@ -227,7 +228,7 @@ function setupMcp() {
227
228
  const cdpPort = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
228
229
  return startMcpServer(cdpPort, settings.mcp.port);
229
230
  }
230
- function wireAppWindowEvents(config, instance) {
231
+ function wireAppWindowEvents(config, instance, isOnProjectScreen) {
231
232
  const { mainWindow, context } = instance;
232
233
  // In-flight guard: `closeProject()` is async, and the window stays open
233
234
  // (preventDefault'd) while it runs — a second close click during that window
@@ -259,14 +260,26 @@ function wireAppWindowEvents(config, instance) {
259
260
  e.preventDefault();
260
261
  return;
261
262
  }
262
- if (!context.workspace.hasActiveSession())
263
+ // Keep the app alive on close whenever EITHER a live compiled session
264
+ // exists OR the renderer reports it is on a project screen. The renderer
265
+ // enters the project screen (and reports it) before openProject resolves,
266
+ // so a FAILED open — invalid/non-existent project, no session ever
267
+ // created — still parks the renderer there showing the compile-failed
268
+ // overlay. Keying off `hasActiveSession()` alone let that close fall
269
+ // through with no preventDefault → the last window is destroyed →
270
+ // `window-all-closed` → `app.quit()`, quitting the whole app instead of
271
+ // returning to the list. The two signals answer one question ("is the
272
+ // user inside a project, so close means back-to-list?"); the renderer's
273
+ // screen is the authority and hasActiveSession is a resource-safety belt
274
+ // (never destroy the window while a live session runs behind it).
275
+ if (!context.workspace.hasActiveSession() && !isOnProjectScreen())
263
276
  return;
264
- // Close button while a project session is open: stay in the workbench
265
- // and surface the project list. Tear down only the session — do NOT
266
- // dispose `context.registry`, which owns every IPC handler (projects,
267
- // dialog, settings…). Disposing it would leave the renderer alive but
268
- // unable to invoke anything, so subsequent clicks on Import/etc. would
269
- // raise `No handler registered for ...`.
277
+ // Close button while inside a project: stay in the workbench and surface
278
+ // the project list. Tear down only the session — do NOT dispose
279
+ // `context.registry`, which owns every IPC handler (projects, dialog,
280
+ // settings…). Disposing it would leave the renderer alive but unable to
281
+ // invoke anything, so subsequent clicks on Import/etc. would raise
282
+ // `No handler registered for ...`.
270
283
  e.preventDefault();
271
284
  closing = true;
272
285
  try {
@@ -568,7 +581,14 @@ export async function createDevtoolsRuntime(config = {}) {
568
581
  // instead of fire-and-forgetting it (a dangling http server would keep the
569
582
  // port + event loop alive past teardown).
570
583
  context.registry.add(() => coiServer.close());
571
- context.registry.add(() => context.views.detachWorkbench());
584
+ // The registry disposes LIFO, so this runs BEFORE the context-level
585
+ // disposeAll: void any in-flight open's attach hold here too, or a stale
586
+ // release / cap firing could rebuild the workbench during the awaited
587
+ // coiServer.close() above, before disposeAll's own cancel runs.
588
+ context.registry.add(() => {
589
+ context.views.cancelWorkbenchAttachHold();
590
+ context.views.detachWorkbench();
591
+ });
572
592
  // Only HAND the view manager the COI URL — do NOT load yet. The heavy
573
593
  // WebContentsView load (10MB bundle + ext-host) is deferred to the first time
574
594
  // the 'editor' dock slot becomes visible (first non-zero bounds), so it never
@@ -576,7 +596,16 @@ export async function createDevtoolsRuntime(config = {}) {
576
596
  // preload/window-ready enough to trip the e2e launch health check.
577
597
  context.views.setWorkbenchSource(coiServer.baseUrl);
578
598
  }
579
- context.registry.add(wireAppWindowEvents(config, instance));
599
+ // Main's mirror of the renderer's top-level screen. The renderer's `page`
600
+ // state is the single authority; it pushes every change over
601
+ // WindowChannel.ScreenState. Main reads this mirror in the window-close
602
+ // decision so a close while the renderer is parked on a project screen (even
603
+ // a failed open with no session) returns to the list instead of quitting.
604
+ let rendererScreen = 'list';
605
+ context.registry.add(new IpcRegistry(context.senderPolicy).handle(WindowChannel.ScreenState, (_e, screen) => {
606
+ rendererScreen = screen === 'project' ? 'project' : 'list';
607
+ }));
608
+ context.registry.add(wireAppWindowEvents(config, instance, () => rendererScreen === 'project'));
580
609
  context.registry.add(enableDevRendererAutoReload(rendererDir));
581
610
  return instance;
582
611
  }
@@ -3,7 +3,7 @@ import type { StorageApi } from '../services/simulator-storage/index.js';
3
3
  /**
4
4
  * Narrow view of the fields the native overview provider reads. Depending on
5
5
  * this instead of the full `WorkbenchContext` grab-bag keeps this module out
6
- * of the workbench-context import ratchet.
6
+ * of the workbench-context import gate.
7
7
  */
8
8
  export interface NativeOverviewContext {
9
9
  storageApi?: StorageApi;
@@ -1,3 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
1
7
  // src/main/index.ts
2
8
  import { app as app16 } from "electron";
3
9
 
@@ -495,7 +501,14 @@ var PopoverChannel = {
495
501
  Init: "popover:init"
496
502
  };
497
503
  var WindowChannel = {
498
- NavigateBack: "window:navigateBack"
504
+ NavigateBack: "window:navigateBack",
505
+ // Renderer → main: the renderer's current top-level screen ('project' when
506
+ // inside a project screen, 'list' on the project list / landing). The
507
+ // renderer pushes this on every screen change, including the moment it enters
508
+ // a project — BEFORE the open resolves — so a FAILED open (no session) still
509
+ // leaves main's mirror = 'project'. The window-close decision reads it so
510
+ // closing a stuck/failed project returns to the list instead of quitting.
511
+ ScreenState: "window:screenState"
499
512
  };
500
513
  var AppChannel = {
501
514
  GetBranding: "app:getBranding"
@@ -1401,10 +1414,14 @@ function destroyChildView(mainWindow, view) {
1401
1414
  }
1402
1415
 
1403
1416
  // src/main/services/views/workbench-view.ts
1417
+ var ATTACH_HOLD_CAP_MS = 3e3;
1404
1418
  function createWorkbenchView(ctx, reconciler) {
1405
1419
  let workbenchView = null;
1406
1420
  let workbenchThemeSyncBound = false;
1407
1421
  let workbenchUrl = null;
1422
+ let attachHeld = false;
1423
+ let attachHoldGeneration = 0;
1424
+ let attachHoldTimer = null;
1408
1425
  function workbenchThemeScheme() {
1409
1426
  return nativeTheme3.shouldUseDarkColors ? "dark" : "light";
1410
1427
  }
@@ -1443,6 +1460,36 @@ function createWorkbenchView(ctx, reconciler) {
1443
1460
  function setWorkbenchSource(url) {
1444
1461
  workbenchUrl = url;
1445
1462
  }
1463
+ function releaseAttachHold(generation, viaCap) {
1464
+ if (!attachHeld || generation !== attachHoldGeneration) return;
1465
+ attachHeld = false;
1466
+ if (attachHoldTimer) {
1467
+ clearTimeout(attachHoldTimer);
1468
+ attachHoldTimer = null;
1469
+ }
1470
+ if (viaCap) {
1471
+ console.warn(
1472
+ `[workbench] attach gate: hold not released within ${ATTACH_HOLD_CAP_MS}ms (compile still running or a release was lost) \u2014 attaching now`
1473
+ );
1474
+ }
1475
+ reconciler.reconcileNow();
1476
+ }
1477
+ function holdWorkbenchAttach() {
1478
+ const generation = ++attachHoldGeneration;
1479
+ attachHeld = true;
1480
+ if (attachHoldTimer) clearTimeout(attachHoldTimer);
1481
+ attachHoldTimer = setTimeout(() => releaseAttachHold(generation, true), ATTACH_HOLD_CAP_MS);
1482
+ return () => releaseAttachHold(generation, false);
1483
+ }
1484
+ function cancelWorkbenchAttachHold() {
1485
+ if (!attachHeld && !attachHoldTimer) return;
1486
+ attachHeld = false;
1487
+ attachHoldGeneration++;
1488
+ if (attachHoldTimer) {
1489
+ clearTimeout(attachHoldTimer);
1490
+ attachHoldTimer = null;
1491
+ }
1492
+ }
1446
1493
  function detachWorkbench() {
1447
1494
  if (workbenchThemeSyncBound) {
1448
1495
  nativeTheme3.removeListener("updated", pushWorkbenchTheme);
@@ -1450,7 +1497,11 @@ function createWorkbenchView(ctx, reconciler) {
1450
1497
  }
1451
1498
  destroyChildView(ctx.windows.mainWindow, workbenchView);
1452
1499
  workbenchView = null;
1453
- reconciler.deleteBaseDesired(VIEW_ID.workbench);
1500
+ if (attachHeld) {
1501
+ reconciler.forgetActual(VIEW_ID.workbench);
1502
+ } else {
1503
+ reconciler.deleteBaseDesired(VIEW_ID.workbench);
1504
+ }
1454
1505
  reconciler.reconcileNow();
1455
1506
  }
1456
1507
  function workspaceUriFor(rel) {
@@ -1475,6 +1526,7 @@ function createWorkbenchView(ctx, reconciler) {
1475
1526
  return workbenchView.webContents.executeJavaScript(script, true).then((ok2) => ok2 === true).catch(() => false);
1476
1527
  }
1477
1528
  function openFileInWorkbench(relPath, line, column) {
1529
+ if (attachHeld) releaseAttachHold(attachHoldGeneration, false);
1478
1530
  if (!workbenchView || workbenchView.webContents.isDestroyed()) return false;
1479
1531
  const uri = workspaceUriFor(relPath.replace(/^\/+/, ""));
1480
1532
  const zeroLine = Math.max(0, Math.floor(line) - 1);
@@ -1491,19 +1543,19 @@ function createWorkbenchView(ctx, reconciler) {
1491
1543
  }
1492
1544
  reconciler.registerView(VIEW_ID.workbench, {
1493
1545
  getView: () => workbenchView,
1494
- gateHidden: () => !workbenchView && !workbenchUrl,
1546
+ gateHidden: () => !workbenchView && (!workbenchUrl || attachHeld),
1495
1547
  beforeAttach: () => {
1496
- if (!workbenchView && workbenchUrl) {
1548
+ if (!workbenchView && workbenchUrl && !attachHeld) {
1497
1549
  void attachWorkbench(workbenchUrl);
1498
1550
  return true;
1499
1551
  }
1500
1552
  return false;
1501
1553
  },
1502
1554
  ensureLazy: (desired) => {
1503
- if (desired?.placement.visible && !workbenchView && workbenchUrl) void attachWorkbench(workbenchUrl);
1555
+ if (desired?.placement.visible && !workbenchView && workbenchUrl && !attachHeld) void attachWorkbench(workbenchUrl);
1504
1556
  }
1505
1557
  });
1506
- return { attachWorkbench, setWorkbenchSource, detachWorkbench, openFileInWorkbench };
1558
+ return { attachWorkbench, setWorkbenchSource, detachWorkbench, openFileInWorkbench, holdWorkbenchAttach, cancelWorkbenchAttachHold };
1507
1559
  }
1508
1560
 
1509
1561
  // src/main/services/views/native-simulator-devtools-host.ts
@@ -3279,7 +3331,7 @@ function createOverlayPanelsView(ctx, reconciler, deps) {
3279
3331
  }
3280
3332
 
3281
3333
  // src/main/services/views/native-simulator-view.ts
3282
- import { ipcMain as ipcMain2, nativeTheme as nativeTheme4, shell as shell2, WebContentsView as WebContentsView6, webContents } from "electron";
3334
+ import { ipcMain as ipcMain2, nativeTheme as nativeTheme4, shell as shell2, WebContentsView as WebContentsView6, webContents as webContents2 } from "electron";
3283
3335
 
3284
3336
  // src/main/services/simulator/custom-apis.ts
3285
3337
  async function handleCustomApiBridgeRequest(apis, req) {
@@ -3368,6 +3420,45 @@ function configureMiniappSession(partition) {
3368
3420
  return sess;
3369
3421
  }
3370
3422
 
3423
+ // src/main/services/views/refresh-styles.ts
3424
+ import { webContents } from "electron";
3425
+ var REFRESH_STYLES_JS = `(function refresh(doc){
3426
+ try {
3427
+ var links = doc.querySelectorAll('link[rel="stylesheet"]');
3428
+ for (var i = 0; i < links.length; i++) {
3429
+ var link = links[i];
3430
+ try {
3431
+ var u = new URL(link.href, doc.baseURI || undefined);
3432
+ u.searchParams.set('__hmr', String(Date.now()));
3433
+ var next = link.cloneNode();
3434
+ next.href = u.href;
3435
+ next.addEventListener('load', (function(stale){ return function(){ stale.remove(); }; })(link));
3436
+ link.parentNode.insertBefore(next, link.nextSibling);
3437
+ } catch (e) {}
3438
+ }
3439
+ var frames = doc.querySelectorAll('iframe');
3440
+ for (var j = 0; j < frames.length; j++) {
3441
+ try { if (frames[j].contentDocument) refresh(frames[j].contentDocument); } catch (e) {}
3442
+ }
3443
+ } catch (e) {}
3444
+ })(document);`;
3445
+ function refreshGuestStylesheets(view) {
3446
+ const simWc = view.webContents;
3447
+ let refreshed = 0;
3448
+ try {
3449
+ for (const wc of webContents.getAllWebContents()) {
3450
+ if (wc.isDestroyed()) continue;
3451
+ if (wc.hostWebContents !== simWc) continue;
3452
+ wc.executeJavaScript(REFRESH_STYLES_JS).catch(() => {
3453
+ });
3454
+ refreshed++;
3455
+ }
3456
+ } catch {
3457
+ return false;
3458
+ }
3459
+ return refreshed > 0;
3460
+ }
3461
+
3371
3462
  // src/shared/simulator-route.ts
3372
3463
  function decodePageSpec(value) {
3373
3464
  const qIdx = value.indexOf("?");
@@ -3498,7 +3589,7 @@ function createNativeSimulatorView(ctx, reconciler, deps) {
3498
3589
  const simWc = view.webContents;
3499
3590
  if (!simWc.isDestroyed()) simWc.setZoomFactor(p.zoomFactor);
3500
3591
  try {
3501
- for (const wc of webContents.getAllWebContents()) {
3592
+ for (const wc of webContents2.getAllWebContents()) {
3502
3593
  if (wc.isDestroyed()) continue;
3503
3594
  if (wc.hostWebContents === simWc) wc.setZoomFactor(p.zoomFactor);
3504
3595
  }
@@ -3671,6 +3762,11 @@ function createNativeSimulatorView(ctx, reconciler, deps) {
3671
3762
  nativeSimulatorView.webContents.send(SIMULATOR_EVENTS.RELAUNCH, payload);
3672
3763
  return true;
3673
3764
  }
3765
+ function refreshSimulatorStyles() {
3766
+ const view = nativeSimulatorView;
3767
+ if (!view || view.webContents.isDestroyed() || !nativeSimulatorShellReady) return false;
3768
+ return refreshGuestStylesheets(view);
3769
+ }
3674
3770
  function detachSimulator() {
3675
3771
  settleNativeSimulatorReady?.();
3676
3772
  settleNativeSimulatorReady = null;
@@ -3697,11 +3793,12 @@ function createNativeSimulatorView(ctx, reconciler, deps) {
3697
3793
  return {
3698
3794
  attachNativeSimulator,
3699
3795
  softReloadNativeSimulator,
3796
+ refreshSimulatorStyles,
3700
3797
  detachSimulator,
3701
3798
  getSimulatorWebContentsId: () => simulatorWebContentsId,
3702
3799
  getSimulatorWebContents: () => {
3703
3800
  if (simulatorWebContentsId == null) return null;
3704
- const wc = webContents.fromId(simulatorWebContentsId);
3801
+ const wc = webContents2.fromId(simulatorWebContentsId);
3705
3802
  return wc && !wc.isDestroyed() ? wc : null;
3706
3803
  },
3707
3804
  getSimulatorProjectPath: () => nativeSimulatorProjectPath
@@ -3736,12 +3833,14 @@ function createViewManager(ctx) {
3736
3833
  safeArea.dispose();
3737
3834
  }
3738
3835
  function disposeAll() {
3836
+ workbench.cancelWorkbenchAttachHold();
3739
3837
  disposeProjectViews();
3740
3838
  hostToolbar.dispose();
3741
3839
  }
3742
3840
  return {
3743
3841
  attachNativeSimulator: nativeSimulator.attachNativeSimulator,
3744
3842
  softReloadNativeSimulator: nativeSimulator.softReloadNativeSimulator,
3843
+ refreshSimulatorStyles: nativeSimulator.refreshSimulatorStyles,
3745
3844
  detachSimulator: nativeSimulator.detachSimulator,
3746
3845
  reapplySafeArea: (device) => safeArea.reapplyAll(device),
3747
3846
  showSettings: overlayPanels.showSettings,
@@ -3765,6 +3864,8 @@ function createViewManager(ctx) {
3765
3864
  setWorkbenchSource: workbench.setWorkbenchSource,
3766
3865
  detachWorkbench: workbench.detachWorkbench,
3767
3866
  openFileInWorkbench: workbench.openFileInWorkbench,
3867
+ holdWorkbenchAttach: workbench.holdWorkbenchAttach,
3868
+ cancelWorkbenchAttachHold: workbench.cancelWorkbenchAttachHold,
3768
3869
  setHostToolbarHeight: hostToolbar.setHostToolbarHeight,
3769
3870
  hostToolbar: hostToolbar.control
3770
3871
  };
@@ -3862,10 +3963,41 @@ async function openSettingsWindow(deps) {
3862
3963
  });
3863
3964
  }
3864
3965
 
3865
- // src/main/services/workspace/workspace-service.ts
3966
+ // src/main/services/workspace/open-project-guards.ts
3866
3967
  import { z as z2 } from "zod";
3968
+ async function validateProjectDirSafe(provider, projectPath) {
3969
+ try {
3970
+ return provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
3971
+ } catch (err2) {
3972
+ return err2 instanceof Error ? err2.message : String(err2);
3973
+ }
3974
+ }
3975
+ var SessionAppInfoSchema = z2.looseObject({ appId: z2.string().min(1) });
3976
+ async function rejectInvalidAppId(session5) {
3977
+ if (SessionAppInfoSchema.safeParse(session5.appInfo).success) return null;
3978
+ try {
3979
+ await session5.close();
3980
+ } catch (closeErr) {
3981
+ console.warn("[workspace] closing appId-less adapter session failed (non-fatal):", closeErr);
3982
+ }
3983
+ return "adapter returned session.appInfo without a string appId \u2014 the CompilationAdapter must supply appInfo.appId";
3984
+ }
3867
3985
 
3868
3986
  // src/main/services/projects/project-repository.ts
3987
+ var project_repository_exports = {};
3988
+ __export(project_repository_exports, {
3989
+ addProject: () => addProject,
3990
+ getCompileConfig: () => getCompileConfig,
3991
+ getProjectPages: () => getProjectPages,
3992
+ getProjectSettings: () => getProjectSettings,
3993
+ hasProject: () => hasProject,
3994
+ listProjects: () => listProjects,
3995
+ removeProject: () => removeProject,
3996
+ saveCompileConfig: () => saveCompileConfig,
3997
+ updateLastOpened: () => updateLastOpened,
3998
+ updateProjectSettings: () => updateProjectSettings,
3999
+ validateProjectDir: () => validateProjectDir
4000
+ });
3869
4001
  import { app as app6 } from "electron";
3870
4002
  import path10 from "path";
3871
4003
  import fs5 from "fs";
@@ -3910,6 +4042,9 @@ function validateProjectDir(dirPath) {
3910
4042
  }
3911
4043
  return null;
3912
4044
  }
4045
+ function hasProject(dirPath) {
4046
+ return load().some((p) => p.path === dirPath);
4047
+ }
3913
4048
  function addProject(dirPath) {
3914
4049
  const projects = load();
3915
4050
  let name = path10.basename(dirPath);
@@ -4007,6 +4142,17 @@ function updateProjectSettings(projectPath, patch) {
4007
4142
  );
4008
4143
  }
4009
4144
 
4145
+ // src/main/services/workspace/rebuild-status.ts
4146
+ function reportRebuildStatus(info, deps) {
4147
+ const { pages } = deps.repo.getProjectPages(deps.projectPath);
4148
+ const pageList = pages.length ? pages : void 0;
4149
+ if (info?.styleOnly && deps.autoReload && deps.refreshSimulatorStyles()) {
4150
+ deps.sendStatus("ready", "\u6837\u5F0F\u5DF2\u70ED\u66F4\u65B0", false, pageList);
4151
+ return;
4152
+ }
4153
+ deps.sendStatus("ready", deps.autoReload ? "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u91CD\u542F" : "\u7F16\u8BD1\u5B8C\u6210", deps.autoReload, pageList);
4154
+ }
4155
+
4010
4156
  // src/main/services/projects/types.ts
4011
4157
  var DEFAULT_COMPILE_CONFIG = {
4012
4158
  startPage: "",
@@ -4057,7 +4203,6 @@ function createOpLock() {
4057
4203
  }
4058
4204
 
4059
4205
  // src/main/services/workspace/workspace-service.ts
4060
- var SessionAppInfoSchema = z2.looseObject({ appId: z2.string().min(1) });
4061
4206
  function createWorkspaceService(ctx) {
4062
4207
  let currentSession = null;
4063
4208
  let currentProjectPath = "";
@@ -4126,13 +4271,10 @@ function createWorkspaceService(ctx) {
4126
4271
  projectPath,
4127
4272
  sourcemap: true,
4128
4273
  fileTypes: ctx.fileTypes,
4129
- // Two independent gates: autoBuild = recompile on save; autoReload = refresh the simulator afterwards (off ⇒ page/form state survives; hotReload below stays false so the native simulator, which has no SSE reload, isn't refreshed).
4274
+ // Two independent gates: autoBuild = recompile on save; autoReload = reflect the rebuild in the simulator afterwards (off ⇒ page/form state survives). A style-only rebuild takes the in-place stylesheet hot-swap path (see reportRebuildStatus); any other change does a full reload.
4130
4275
  watch: compile.autoBuild,
4131
4276
  autoReload: preview.autoReload,
4132
- onRebuild: () => {
4133
- const { pages } = getProjectPages(projectPath);
4134
- sendStatus("ready", preview.autoReload ? "\u7F16\u8BD1\u5B8C\u6210\uFF0C\u5DF2\u91CD\u542F" : "\u7F16\u8BD1\u5B8C\u6210", preview.autoReload, pages.length ? pages : void 0);
4135
- },
4277
+ onRebuild: (info) => reportRebuildStatus(info, { projectPath, repo: project_repository_exports, autoReload: preview.autoReload, refreshSimulatorStyles: ctx.views.refreshSimulatorStyles, sendStatus }),
4136
4278
  onBuildError: (err2) => sendStatus("error", String(err2)),
4137
4279
  // Watcher died mid-session (EMFILE, permission loss, …): non-fatal, so 'ready' stays but `watcher: 'dead'` flags that saves no longer auto-rebuild.
4138
4280
  onWatcherError: () => sendStatus("ready", "\u6587\u4EF6\u76D1\u542C\u5DF2\u505C\u6B62\uFF0C\u4FDD\u5B58\u4E0D\u518D\u89E6\u53D1\u81EA\u52A8\u7F16\u8BD1", false, void 0, true),
@@ -4147,15 +4289,6 @@ function createWorkspaceService(ctx) {
4147
4289
  return { error: String(err2) };
4148
4290
  }
4149
4291
  }
4150
- async function rejectInvalidAppId(session5) {
4151
- if (SessionAppInfoSchema.safeParse(session5.appInfo).success) return null;
4152
- try {
4153
- await session5.close();
4154
- } catch (closeErr) {
4155
- console.warn("[workspace] closing appId-less adapter session failed (non-fatal):", closeErr);
4156
- }
4157
- return "adapter returned session.appInfo without a string appId \u2014 the CompilationAdapter must supply appInfo.appId";
4158
- }
4159
4292
  function applyRefererFromSession(session5) {
4160
4293
  const appInfo = session5.appInfo;
4161
4294
  if (appInfo && typeof appInfo.appId === "string" && appInfo.appId.length > 0) {
@@ -4189,17 +4322,21 @@ function createWorkspaceService(ctx) {
4189
4322
  }
4190
4323
  }
4191
4324
  opLock.takeOwnership(mySeq);
4325
+ const releaseWorkbenchHold = ctx.views.holdWorkbenchAttach();
4192
4326
  if (await runOpenTeardown(mySeq)) {
4327
+ releaseWorkbenchHold();
4193
4328
  return { success: false, error: "superseded by a newer project open/close" };
4194
4329
  }
4195
- const dirError = provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
4330
+ const dirError = await validateProjectDirSafe(provider, projectPath);
4196
4331
  if (dirError) {
4332
+ releaseWorkbenchHold();
4197
4333
  sendStatus("error", dirError);
4198
4334
  return { success: false, error: dirError };
4199
4335
  }
4200
4336
  sendStatus("compiling", "\u6B63\u5728\u7F16\u8BD1...");
4201
4337
  const sessionGeneration = ++logGeneration;
4202
4338
  const compiled = await runCompile(projectPath, sessionGeneration);
4339
+ releaseWorkbenchHold();
4203
4340
  if ("error" in compiled) {
4204
4341
  sendStatus("error", compiled.error);
4205
4342
  return { success: false, error: compiled.error };
@@ -4235,6 +4372,7 @@ function createWorkspaceService(ctx) {
4235
4372
  const release = await opLock.acquire();
4236
4373
  try {
4237
4374
  if (!opLock.isOwner(mySeq)) return;
4375
+ ctx.views.cancelWorkbenchAttachHold();
4238
4376
  closing = true;
4239
4377
  logGeneration++;
4240
4378
  clearSimulatorServicewechatReferer();
@@ -5049,7 +5187,7 @@ var settingsModule = {
5049
5187
  import { DisposableRegistry as DisposableRegistry6 } from "@dimina-kit/electron-deck/main";
5050
5188
 
5051
5189
  // src/main/ipc/bridge-router.ts
5052
- import { app as app12, ipcMain as ipcMain3, protocol as protocol2, session as electronSession, webContents as webContents2 } from "electron";
5190
+ import { app as app12, ipcMain as ipcMain3, protocol as protocol2, session as electronSession, webContents as webContents3 } from "electron";
5053
5191
  import path17 from "node:path";
5054
5192
  import { pathToFileURL as pathToFileURL3 } from "node:url";
5055
5193
 
@@ -7588,7 +7726,7 @@ function newBridgeId() {
7588
7726
  }
7589
7727
  function resolveSimulatorWebContents(ctx, simulatorWcId, sender) {
7590
7728
  if (!simulatorWcId) return sender ?? ctx.windows.mainWindow.webContents;
7591
- return webContents2.fromId(simulatorWcId) ?? ctx.windows.mainWindow.webContents;
7729
+ return webContents3.fromId(simulatorWcId) ?? ctx.windows.mainWindow.webContents;
7592
7730
  }
7593
7731
  function disposePageSession(state, ap, page) {
7594
7732
  if (page.renderWc && !page.renderWc.isDestroyed()) {
@@ -9857,147 +9995,12 @@ function setupSimulatorWxml(host, options) {
9857
9995
  return registry;
9858
9996
  }
9859
9997
 
9860
- // src/shared/appdata-accumulator.ts
9861
- function parseMessagePayload(message) {
9862
- if (typeof message !== "string") return message;
9863
- try {
9864
- return JSON.parse(message);
9865
- } catch {
9866
- return null;
9867
- }
9868
- }
9869
- function decodeUpdateBatchBody(rawBody) {
9870
- const body = rawBody;
9871
- if (!body || typeof body !== "object") return null;
9872
- if (typeof body.bridgeId !== "string" || !Array.isArray(body.updates)) return null;
9873
- const out = [];
9874
- for (const u of body.updates) {
9875
- if (!u || typeof u.moduleId !== "string") continue;
9876
- if (!u.moduleId.startsWith("page_")) continue;
9877
- out.push({ mode: "patch", bridgeId: body.bridgeId, moduleId: u.moduleId, data: u.data });
9878
- }
9879
- return out.length > 0 ? out : null;
9880
- }
9881
- function decodePageInitBody(moduleId, rawBody) {
9882
- const body = rawBody;
9883
- if (!body || typeof body !== "object") return null;
9884
- if (typeof body.bridgeId !== "string" || typeof body.path !== "string") return null;
9885
- if (!body.data || typeof body.data !== "object") return null;
9886
- return [{
9887
- mode: "init",
9888
- bridgeId: body.bridgeId,
9889
- moduleId,
9890
- componentPath: body.path,
9891
- data: body.data
9892
- }];
9893
- }
9894
- function decodeWorkerMessage(message) {
9895
- const payload = parseMessagePayload(message);
9896
- if (!payload || typeof payload !== "object") return null;
9897
- const record = payload;
9898
- if (record.type === "ub") return decodeUpdateBatchBody(record.body);
9899
- if (typeof record.type === "string" && record.type.startsWith("page_")) {
9900
- return decodePageInitBody(record.type, record.body);
9901
- }
9902
- return null;
9903
- }
9904
- function decodedToInput(entry) {
9905
- const input = {
9906
- bridgeId: entry.bridgeId,
9907
- moduleId: entry.moduleId,
9908
- data: entry.data,
9909
- mode: entry.mode
9910
- };
9911
- if (entry.mode === "init") input.componentPath = entry.componentPath;
9912
- return input;
9913
- }
9914
- var AppDataAccumulator = class {
9915
- cache = /* @__PURE__ */ new Map();
9916
- // Bridges in insertion order — drives the `bridges` array (stable tab order).
9917
- bridgeOrder = [];
9918
- // Page path per bridge: set from `page_*` init's body.path (the page route).
9919
- bridgePagePath = /* @__PURE__ */ new Map();
9920
- recordBridge(bridgeId) {
9921
- if (!this.bridgeOrder.includes(bridgeId)) this.bridgeOrder.push(bridgeId);
9922
- }
9923
- /**
9924
- * Apply one entry. Returns true if it was accepted (a mutation worth
9925
- * republishing), false if dropped (missing ids, or the init-gate).
9926
- */
9927
- apply(input) {
9928
- if (!input.bridgeId || !input.moduleId) return false;
9929
- if (input.mode !== "init" && !this.bridgePagePath.has(input.bridgeId)) return false;
9930
- const key = `${input.bridgeId}/${input.moduleId}`;
9931
- const prev = this.cache.get(key);
9932
- const incoming = input.data && typeof input.data === "object" ? input.data : {};
9933
- const merged = input.mode === "init" ? { ...incoming } : { ...prev?.data ?? {}, ...incoming };
9934
- const componentPath = input.componentPath ?? prev?.componentPath;
9935
- const next = componentPath !== void 0 ? { componentPath, data: merged } : { data: merged };
9936
- this.cache.set(key, next);
9937
- this.recordBridge(input.bridgeId);
9938
- if (input.mode === "init" && input.moduleId.startsWith("page_") && input.componentPath) {
9939
- this.bridgePagePath.set(input.bridgeId, input.componentPath);
9940
- }
9941
- return true;
9942
- }
9943
- /** Evict every entry for a bridge (page teardown). */
9944
- clearBridge(bridgeId) {
9945
- const prefix = `${bridgeId}/`;
9946
- for (const key of [...this.cache.keys()]) {
9947
- if (key.startsWith(prefix)) this.cache.delete(key);
9948
- }
9949
- const idx = this.bridgeOrder.indexOf(bridgeId);
9950
- if (idx >= 0) this.bridgeOrder.splice(idx, 1);
9951
- this.bridgePagePath.delete(bridgeId);
9952
- }
9953
- /** The full cumulative snapshot for the panel. */
9954
- snapshot() {
9955
- const bridges = [];
9956
- for (const id of this.bridgeOrder) {
9957
- bridges.push({ id, pagePath: this.bridgePagePath.get(id) ?? null });
9958
- }
9959
- const entries = {};
9960
- for (const [key, entry] of this.cache) {
9961
- const slash = key.indexOf("/");
9962
- if (slash < 0) continue;
9963
- const bridgeId = key.slice(0, slash);
9964
- const moduleId = key.slice(slash + 1);
9965
- if (!entries[bridgeId]) entries[bridgeId] = {};
9966
- const displayKey = entry.componentPath ?? moduleId;
9967
- entries[bridgeId][displayKey] = entry.data;
9968
- }
9969
- return { bridges, entries };
9970
- }
9971
- /**
9972
- * The current reactive page state for a bridge: shallow-merge of `entry.data`
9973
- * across every cache entry whose key starts with `${bridgeId}/`, in insertion
9974
- * order (later entries win on key conflicts). `{}` when no entries match
9975
- * (unknown bridge / after clearBridge). Pure, no side effects.
9976
- */
9977
- pageData(bridgeId) {
9978
- const merged = {};
9979
- for (const [key, entry] of this.cache) {
9980
- const slash = key.indexOf("/");
9981
- if (slash < 0) continue;
9982
- if (key.slice(0, slash) !== bridgeId) continue;
9983
- Object.assign(merged, entry.data);
9984
- }
9985
- return merged;
9986
- }
9987
- /** Flat `key → data` map for the `__simulatorData.getAppdata()` mirror. */
9988
- flat() {
9989
- const data = {};
9990
- for (const [key, entry] of this.cache) data[key] = entry.data;
9991
- return data;
9992
- }
9993
- clear() {
9994
- this.cache.clear();
9995
- this.bridgeOrder.length = 0;
9996
- this.bridgePagePath.clear();
9997
- }
9998
- };
9999
-
10000
9998
  // src/main/services/simulator-appdata/index.ts
9999
+ import {
10000
+ AppDataAccumulator,
10001
+ decodeWorkerMessage,
10002
+ decodedToInput
10003
+ } from "@dimina-kit/inspect";
10001
10004
  import { DisposableRegistry as DisposableRegistry9 } from "@dimina-kit/electron-deck/main";
10002
10005
  var EMPTY_SNAPSHOT = { bridges: [], entries: {} };
10003
10006
  function setupSimulatorAppData(host, options) {
@@ -11482,7 +11485,7 @@ function setupMcp() {
11482
11485
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
11483
11486
  return startMcpServer(cdpPort2, settings.mcp.port);
11484
11487
  }
11485
- function wireAppWindowEvents(config, instance) {
11488
+ function wireAppWindowEvents(config, instance, isOnProjectScreen) {
11486
11489
  const { mainWindow, context } = instance;
11487
11490
  let closing = false;
11488
11491
  return wireMainWindowEvents(mainWindow, {
@@ -11494,7 +11497,7 @@ function wireAppWindowEvents(config, instance) {
11494
11497
  e.preventDefault();
11495
11498
  return;
11496
11499
  }
11497
- if (!context.workspace.hasActiveSession()) return;
11500
+ if (!context.workspace.hasActiveSession() && !isOnProjectScreen()) return;
11498
11501
  e.preventDefault();
11499
11502
  closing = true;
11500
11503
  try {
@@ -11694,10 +11697,24 @@ async function createDevtoolsRuntime(config = {}) {
11694
11697
  getFileTypes: () => context.fileTypes
11695
11698
  });
11696
11699
  context.registry.add(() => coiServer.close());
11697
- context.registry.add(() => context.views.detachWorkbench());
11700
+ context.registry.add(() => {
11701
+ context.views.cancelWorkbenchAttachHold();
11702
+ context.views.detachWorkbench();
11703
+ });
11698
11704
  context.views.setWorkbenchSource(coiServer.baseUrl);
11699
11705
  }
11700
- context.registry.add(wireAppWindowEvents(config, instance));
11706
+ let rendererScreen = "list";
11707
+ context.registry.add(
11708
+ new IpcRegistry(context.senderPolicy).handle(
11709
+ WindowChannel.ScreenState,
11710
+ (_e, screen) => {
11711
+ rendererScreen = screen === "project" ? "project" : "list";
11712
+ }
11713
+ )
11714
+ );
11715
+ context.registry.add(
11716
+ wireAppWindowEvents(config, instance, () => rendererScreen === "project")
11717
+ );
11701
11718
  context.registry.add(enableDevRendererAutoReload(rendererDir2));
11702
11719
  return instance;
11703
11720
  }