@dimina-kit/devtools 0.4.0-dev.20260615083030 → 0.4.0-dev.20260616085026

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 (29) hide show
  1. package/dist/main/app/app.js +11 -0
  2. package/dist/main/app/lifecycle.d.ts +1 -0
  3. package/dist/main/app/lifecycle.js +10 -0
  4. package/dist/main/index.bundle.js +71 -51
  5. package/dist/main/runtime/devtools-backend.d.ts +5 -0
  6. package/dist/main/runtime/devtools-backend.js +10 -3
  7. package/dist/main/runtime/miniapp-runtime.d.ts +4 -2
  8. package/dist/main/services/workbench-context.d.ts +9 -0
  9. package/dist/main/services/workbench-context.js +1 -0
  10. package/dist/main/services/workspace/workspace-service.js +21 -0
  11. package/dist/main/windows/main-window/create.d.ts +6 -0
  12. package/dist/main/windows/main-window/create.js +17 -10
  13. package/dist/renderer/assets/index-BYV0bEvB.js +50 -0
  14. package/dist/renderer/assets/{input-hGF71bn3.js → input-B8NDCYv3.js} +2 -2
  15. package/dist/renderer/assets/ipc-transport-9agi76dX.css +1 -0
  16. package/dist/renderer/assets/{ipc-transport-BKWkvwKK.js → ipc-transport-DhrajiC5.js} +1 -1
  17. package/dist/renderer/assets/{popover-NNQNyjgI.js → popover-CI5uE9ZZ.js} +2 -2
  18. package/dist/renderer/assets/{select-Bb1AKeSq.js → select-tInleY0z.js} +2 -2
  19. package/dist/renderer/assets/{settings-7uwimev-.js → settings-CZJOHt8b.js} +2 -2
  20. package/dist/renderer/assets/{settings-api-DkrPHroq.js → settings-api-BSKzKiWd.js} +2 -2
  21. package/dist/renderer/assets/{workbenchSettings-EaMYvKHG.js → workbenchSettings-BGxjl25x.js} +2 -2
  22. package/dist/renderer/entries/main/index.html +6 -6
  23. package/dist/renderer/entries/popover/index.html +5 -5
  24. package/dist/renderer/entries/settings/index.html +5 -5
  25. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  26. package/dist/shared/types.d.ts +27 -0
  27. package/package.json +4 -4
  28. package/dist/renderer/assets/index-BFwsvw4p.js +0 -50
  29. package/dist/renderer/assets/ipc-transport-Cg1q1gwD.css +0 -1
@@ -6,6 +6,7 @@ import path from 'path';
6
6
  import { rendererDir as defaultRendererDir, defaultPreloadPath } from '../utils/paths.js';
7
7
  import { installThemeBackgroundSync } from '../utils/theme.js';
8
8
  import { createMainWindow, wireMainWindowEvents } from '../windows/main-window/index.js';
9
+ import { isAppQuitting } from './lifecycle.js';
9
10
  // eslint-disable-next-line no-restricted-syntax -- grandfathered(workbench-context): shrink-only
10
11
  import { createWorkbenchContext } from '../services/workbench-context.js';
11
12
  import { loadWorkbenchSettings, applyTheme } from '../services/settings/index.js';
@@ -131,6 +132,7 @@ function createConfiguredMainWindow(config, rendererDir) {
131
132
  height: config.window?.height,
132
133
  minWidth: config.window?.minWidth,
133
134
  minHeight: config.window?.minHeight,
135
+ autoShow: config.window?.autoShow,
134
136
  });
135
137
  // Set window/taskbar icon if provided (Linux/Windows; macOS uses app bundle icon)
136
138
  if (config.icon) {
@@ -157,6 +159,7 @@ function createContext(config, mainWindow, rendererDir) {
157
159
  projectTemplates: config.projectTemplates,
158
160
  builtinTemplates: config.builtinTemplates,
159
161
  customCreateProjectDialog: config.customCreateProjectDialog,
162
+ onBeforeOpenProject: config.onBeforeOpenProject,
160
163
  });
161
164
  }
162
165
  function registerBuiltinModules(config, context) {
@@ -229,6 +232,14 @@ function wireAppWindowEvents(config, instance) {
229
232
  context,
230
233
  onResize: () => context.views.repositionAll(),
231
234
  onClose: async (e) => {
235
+ // A real application quit (⌘Q / menu "Quit" / app.quit()) fires
236
+ // `before-quit` first, then this window's `close`. Let it through so the
237
+ // app actually exits — do NOT convert it into "close the project".
238
+ // Without this, `hasActiveSession()` would be true and the quit gets
239
+ // swallowed into closeProject(), so the app can never be quit while a
240
+ // project is open.
241
+ if (isAppQuitting())
242
+ return;
232
243
  if (!context.workspace.hasActiveSession())
233
244
  return;
234
245
  // Close button while a project session is open: stay in the workbench
@@ -1,2 +1,3 @@
1
+ export declare function isAppQuitting(): boolean;
1
2
  export declare function registerAppLifecycle(): void;
2
3
  //# sourceMappingURL=lifecycle.d.ts.map
@@ -1,10 +1,20 @@
1
1
  import { app, globalShortcut } from 'electron';
2
+ // Flips to `true` once Electron's `before-quit` fires — a real application
3
+ // quit (⌘Q / menu "Quit" / app.quit()) is underway. `before-quit` is emitted
4
+ // BEFORE each window's `close` event, so the main-window onClose handler can
5
+ // read this to tell "the whole app is exiting" apart from "the user closed a
6
+ // single project window" and avoid swallowing the quit.
7
+ let appIsQuitting = false;
8
+ export function isAppQuitting() {
9
+ return appIsQuitting;
10
+ }
2
11
  export function registerAppLifecycle() {
3
12
  app.on('window-all-closed', () => {
4
13
  globalShortcut.unregisterAll();
5
14
  app.quit();
6
15
  });
7
16
  app.on('before-quit', () => {
17
+ appIsQuitting = true;
8
18
  globalShortcut.unregisterAll();
9
19
  });
10
20
  }
@@ -761,7 +761,7 @@ function registerProjectFsIpc(ctx) {
761
761
  }
762
762
 
763
763
  // src/main/app/app.ts
764
- import { app as app14, BrowserWindow as BrowserWindow8, nativeImage, session as session4 } from "electron";
764
+ import { app as app15, BrowserWindow as BrowserWindow8, nativeImage, session as session4 } from "electron";
765
765
  import fs10 from "fs";
766
766
  import path21 from "path";
767
767
 
@@ -885,13 +885,13 @@ function createMainWindow(opts) {
885
885
  }
886
886
  });
887
887
  mainWindow.once("ready-to-show", () => {
888
- if (process.env.NODE_ENV === "test") {
889
- mainWindow.showInactive();
890
- } else {
891
- mainWindow.show();
892
- if (!app3.isPackaged && process.env.DIMINA_DEVTOOLS_MAIN_INSPECTOR === "1") {
893
- mainWindow.webContents.openDevTools({ mode: "detach" });
894
- }
888
+ const isTest = process.env.NODE_ENV === "test";
889
+ if (opts.autoShow !== false) {
890
+ if (isTest) mainWindow.showInactive();
891
+ else mainWindow.show();
892
+ }
893
+ if (!isTest && !app3.isPackaged && process.env.DIMINA_DEVTOOLS_MAIN_INSPECTOR === "1") {
894
+ mainWindow.webContents.openDevTools({ mode: "detach" });
895
895
  }
896
896
  });
897
897
  const rendererDir2 = path5.dirname(opts.indexHtml);
@@ -943,6 +943,23 @@ function wireMainWindowEvents(win, state = {}) {
943
943
  return registry;
944
944
  }
945
945
 
946
+ // src/main/app/lifecycle.ts
947
+ import { app as app4, globalShortcut as globalShortcut2 } from "electron";
948
+ var appIsQuitting = false;
949
+ function isAppQuitting() {
950
+ return appIsQuitting;
951
+ }
952
+ function registerAppLifecycle() {
953
+ app4.on("window-all-closed", () => {
954
+ globalShortcut2.unregisterAll();
955
+ app4.quit();
956
+ });
957
+ app4.on("before-quit", () => {
958
+ appIsQuitting = true;
959
+ globalShortcut2.unregisterAll();
960
+ });
961
+ }
962
+
946
963
  // src/main/services/workbench-context.ts
947
964
  import {
948
965
  createConnectionRegistry,
@@ -966,12 +983,12 @@ function createWorkbenchSenderPolicy(ctx) {
966
983
 
967
984
  // src/main/services/default-adapter.ts
968
985
  import path6 from "path";
969
- import { app as app4 } from "electron";
986
+ import { app as app5 } from "electron";
970
987
  var defaultAdapter = {
971
988
  async openProject(opts) {
972
989
  const diminaKit = await import("@dimina-kit/devkit");
973
990
  const openProjectOpts = {
974
- outputDir: path6.join(app4.getPath("userData"), "dimina-fe-output"),
991
+ outputDir: path6.join(app5.getPath("userData"), "dimina-fe-output"),
975
992
  ...opts,
976
993
  simulatorDir
977
994
  };
@@ -2707,12 +2724,12 @@ async function openSettingsWindow(deps) {
2707
2724
  import { z as z2 } from "zod";
2708
2725
 
2709
2726
  // src/main/services/projects/project-repository.ts
2710
- import { app as app5 } from "electron";
2727
+ import { app as app6 } from "electron";
2711
2728
  import path9 from "path";
2712
2729
  import fs4 from "fs";
2713
2730
  var log2 = createLogger("projects");
2714
2731
  function getProjectsFile() {
2715
- return path9.join(app5.getPath("userData"), "dimina-projects.json");
2732
+ return path9.join(app6.getPath("userData"), "dimina-projects.json");
2716
2733
  }
2717
2734
  function load() {
2718
2735
  try {
@@ -2927,6 +2944,15 @@ function createWorkspaceService(ctx) {
2927
2944
  },
2928
2945
  validateProjectDir: async (dirPath) => provider.validateProjectDir ? provider.validateProjectDir(dirPath) : null,
2929
2946
  async openProject(projectPath) {
2947
+ if (ctx.onBeforeOpenProject) {
2948
+ try {
2949
+ await ctx.onBeforeOpenProject(projectPath);
2950
+ } catch (err) {
2951
+ const error = err instanceof Error ? err.message : String(err);
2952
+ sendStatus("error", error);
2953
+ return { success: false, error };
2954
+ }
2955
+ }
2930
2956
  clearSimulatorServicewechatReferer();
2931
2957
  logGeneration++;
2932
2958
  await disposeSession();
@@ -3046,9 +3072,9 @@ function createWorkspaceService(ctx) {
3046
3072
  import { createHash } from "crypto";
3047
3073
  import fs5 from "fs";
3048
3074
  import path10 from "path";
3049
- import { app as app6 } from "electron";
3075
+ import { app as app7 } from "electron";
3050
3076
  function getThumbnailDir() {
3051
- return path10.join(app6.getPath("userData"), "thumbnails");
3077
+ return path10.join(app7.getPath("userData"), "thumbnails");
3052
3078
  }
3053
3079
  function hashProjectPath(projectPath) {
3054
3080
  return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
@@ -3157,6 +3183,7 @@ function createWorkbenchContext(opts) {
3157
3183
  opts.builtinTemplates ?? "all"
3158
3184
  );
3159
3185
  ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
3186
+ ctx.onBeforeOpenProject = opts.onBeforeOpenProject;
3160
3187
  ctx.workspace = createWorkspaceService(ctx);
3161
3188
  ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
3162
3189
  return ctx;
@@ -3463,7 +3490,7 @@ var popoverModule = {
3463
3490
  };
3464
3491
 
3465
3492
  // src/main/ipc/projects.ts
3466
- import { app as app7, dialog } from "electron";
3493
+ import { app as app8, dialog } from "electron";
3467
3494
  import path13 from "path";
3468
3495
 
3469
3496
  // src/main/services/projects/create-project-service.ts
@@ -3617,7 +3644,7 @@ function registerProjectsIpc(ctx) {
3617
3644
  }
3618
3645
  function safeAppPath(name) {
3619
3646
  try {
3620
- return app7.getPath(name);
3647
+ return app8.getPath(name);
3621
3648
  } catch {
3622
3649
  return null;
3623
3650
  }
@@ -3671,7 +3698,7 @@ var sessionModule = {
3671
3698
  };
3672
3699
 
3673
3700
  // src/main/ipc/settings.ts
3674
- import { app as app8 } from "electron";
3701
+ import { app as app9 } from "electron";
3675
3702
 
3676
3703
  // src/main/services/mcp/status.ts
3677
3704
  var status = { running: false, port: null, error: null };
@@ -3710,8 +3737,8 @@ function registerSettingsIpc(ctx) {
3710
3737
  applyTheme(theme);
3711
3738
  }).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
3712
3739
  const settings = loadWorkbenchSettings();
3713
- const switchValue = app8.commandLine.getSwitchValue("remote-debugging-port");
3714
- const implicitDevDefault = !app8.isPackaged && !settings.cdp.enabled && switchValue === String(DEFAULT_CDP_PORT);
3740
+ const switchValue = app9.commandLine.getSwitchValue("remote-debugging-port");
3741
+ const implicitDevDefault = !app9.isPackaged && !settings.cdp.enabled && switchValue === String(DEFAULT_CDP_PORT);
3715
3742
  return {
3716
3743
  configured: settings.cdp.enabled,
3717
3744
  port: settings.cdp.port,
@@ -3772,7 +3799,7 @@ var settingsModule = {
3772
3799
  import { DisposableRegistry as DisposableRegistry6 } from "@dimina-kit/electron-deck/main";
3773
3800
 
3774
3801
  // src/main/ipc/bridge-router.ts
3775
- import { app as app11, ipcMain as ipcMain3, protocol as protocol2, session as electronSession, webContents as webContents2 } from "electron";
3802
+ import { app as app12, ipcMain as ipcMain3, protocol as protocol2, session as electronSession, webContents as webContents2 } from "electron";
3776
3803
  import path16 from "node:path";
3777
3804
  import { pathToFileURL as pathToFileURL3 } from "node:url";
3778
3805
 
@@ -3880,7 +3907,7 @@ function setCorsHeaders(res) {
3880
3907
  }
3881
3908
 
3882
3909
  // src/main/windows/service-host-window/create.ts
3883
- import { app as app9, BrowserWindow as BrowserWindow4 } from "electron";
3910
+ import { app as app10, BrowserWindow as BrowserWindow4 } from "electron";
3884
3911
  import path15 from "node:path";
3885
3912
  import { pathToFileURL as pathToFileURL2 } from "node:url";
3886
3913
  var SERVICE_HOST_PARTITION = SHARED_MINIAPP_PARTITION;
@@ -3921,7 +3948,7 @@ function navigateServiceHost(win, url) {
3921
3948
  () => void 0,
3922
3949
  () => void 0
3923
3950
  );
3924
- if (!app9.isPackaged && process.env.NODE_ENV !== "test") {
3951
+ if (!app10.isPackaged && process.env.NODE_ENV !== "test") {
3925
3952
  const w = win;
3926
3953
  if (w.__diminaDevToolsHook) {
3927
3954
  try {
@@ -4318,7 +4345,7 @@ function createConsoleForwarder(bridge) {
4318
4345
  }
4319
4346
 
4320
4347
  // src/main/services/simulator-storage/index.ts
4321
- import { app as app10, webContents as wcStatic } from "electron";
4348
+ import { app as app11, webContents as wcStatic } from "electron";
4322
4349
  import { DisposableRegistry as DisposableRegistry5 } from "@dimina-kit/electron-deck/main";
4323
4350
 
4324
4351
  // src/main/services/simulator-storage/service-storage-ops.ts
@@ -4688,9 +4715,9 @@ function setupSimulatorStorage(host, options) {
4688
4715
  }
4689
4716
  }
4690
4717
  };
4691
- app10.on("web-contents-created", onWcCreated);
4718
+ app11.on("web-contents-created", onWcCreated);
4692
4719
  registry.add(() => {
4693
- app10.removeListener("web-contents-created", onWcCreated);
4720
+ app11.removeListener("web-contents-created", onWcCreated);
4694
4721
  });
4695
4722
  registry.add(async () => {
4696
4723
  const subs = Array.from(wcSubs.values());
@@ -5864,7 +5891,7 @@ function makeHostEnv(snapshot) {
5864
5891
  windowWidth: 390,
5865
5892
  windowHeight: 844,
5866
5893
  statusBarHeight: 24,
5867
- language: app11.getLocale(),
5894
+ language: app12.getLocale(),
5868
5895
  theme: "light",
5869
5896
  ...snapshot
5870
5897
  };
@@ -8661,7 +8688,7 @@ function setupSimulatorSessionPolicy() {
8661
8688
  }
8662
8689
 
8663
8690
  // src/main/services/update/update-manager.ts
8664
- import { app as app12, shell as shell3 } from "electron";
8691
+ import { app as app13, shell as shell3 } from "electron";
8665
8692
  var UpdateManager = class {
8666
8693
  checker;
8667
8694
  mainWindow;
@@ -8684,7 +8711,7 @@ var UpdateManager = class {
8684
8711
  constructor(opts) {
8685
8712
  this.checker = opts.checker;
8686
8713
  this.mainWindow = opts.mainWindow;
8687
- this.getCurrentVersion = opts.getCurrentVersion ?? (() => app12.getVersion());
8714
+ this.getCurrentVersion = opts.getCurrentVersion ?? (() => app13.getVersion());
8688
8715
  this.ipc = new IpcRegistry(opts.senderPolicy);
8689
8716
  this.registerIpc();
8690
8717
  this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
@@ -8725,7 +8752,7 @@ var UpdateManager = class {
8725
8752
  install() {
8726
8753
  if (this.downloadedPath) {
8727
8754
  shell3.openPath(this.downloadedPath);
8728
- app12.quit();
8755
+ app13.quit();
8729
8756
  }
8730
8757
  }
8731
8758
  /**
@@ -8759,7 +8786,7 @@ var UpdateManager = class {
8759
8786
  };
8760
8787
 
8761
8788
  // src/main/services/update/github-release-checker.ts
8762
- import { app as app13 } from "electron";
8789
+ import { app as app14 } from "electron";
8763
8790
 
8764
8791
  // src/main/app/app.ts
8765
8792
  import { toDisposable as toDisposable8 } from "@dimina-kit/electron-deck/main";
@@ -8834,7 +8861,8 @@ function createConfiguredMainWindow(config, rendererDir2) {
8834
8861
  width: config.window?.width,
8835
8862
  height: config.window?.height,
8836
8863
  minWidth: config.window?.minWidth,
8837
- minHeight: config.window?.minHeight
8864
+ minHeight: config.window?.minHeight,
8865
+ autoShow: config.window?.autoShow
8838
8866
  });
8839
8867
  if (config.icon) {
8840
8868
  const icon = nativeImage.createFromPath(config.icon);
@@ -8858,7 +8886,8 @@ function createContext(config, mainWindow, rendererDir2) {
8858
8886
  projectsProvider: config.projectsProvider,
8859
8887
  projectTemplates: config.projectTemplates,
8860
8888
  builtinTemplates: config.builtinTemplates,
8861
- customCreateProjectDialog: config.customCreateProjectDialog
8889
+ customCreateProjectDialog: config.customCreateProjectDialog,
8890
+ onBeforeOpenProject: config.onBeforeOpenProject
8862
8891
  });
8863
8892
  }
8864
8893
  function registerBuiltinModules(config, context) {
@@ -8903,7 +8932,7 @@ async function setupAutomation(instance) {
8903
8932
  function setupMcp() {
8904
8933
  const settings = loadWorkbenchSettings();
8905
8934
  if (!settings.mcp.enabled) return null;
8906
- const cdpPortSwitch = app14.commandLine.getSwitchValue("remote-debugging-port");
8935
+ const cdpPortSwitch = app15.commandLine.getSwitchValue("remote-debugging-port");
8907
8936
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
8908
8937
  return startMcpServer(cdpPort2, settings.mcp.port);
8909
8938
  }
@@ -8914,6 +8943,7 @@ function wireAppWindowEvents(config, instance) {
8914
8943
  context,
8915
8944
  onResize: () => context.views.repositionAll(),
8916
8945
  onClose: async (e) => {
8946
+ if (isAppQuitting()) return;
8917
8947
  if (!context.workspace.hasActiveSession()) return;
8918
8948
  e.preventDefault();
8919
8949
  if (closing) return;
@@ -8931,7 +8961,7 @@ function wireAppWindowEvents(config, instance) {
8931
8961
  });
8932
8962
  }
8933
8963
  function enableDevRendererAutoReload(rendererDir2) {
8934
- if (app14.isPackaged) {
8964
+ if (app15.isPackaged) {
8935
8965
  return toDisposable8(() => {
8936
8966
  });
8937
8967
  }
@@ -8954,7 +8984,7 @@ function enableDevRendererAutoReload(rendererDir2) {
8954
8984
  }
8955
8985
  function runDevtoolsBootstrap(config = {}) {
8956
8986
  try {
8957
- app14.setName(config.appName ?? "Dimina DevTools");
8987
+ app15.setName(config.appName ?? "Dimina DevTools");
8958
8988
  } catch {
8959
8989
  }
8960
8990
  setupCdpPort();
@@ -8962,7 +8992,7 @@ function runDevtoolsBootstrap(config = {}) {
8962
8992
  registerDifileScheme();
8963
8993
  }
8964
8994
  async function createDevtoolsRuntime(config = {}) {
8965
- await app14.whenReady();
8995
+ await app15.whenReady();
8966
8996
  applyTheme(loadWorkbenchSettings().theme);
8967
8997
  const rendererDir2 = config.rendererDir ?? rendererDir;
8968
8998
  const mainWindow = createConfiguredMainWindow(config, rendererDir2);
@@ -9111,18 +9141,6 @@ async function createDevtoolsRuntime(config = {}) {
9111
9141
  return instance;
9112
9142
  }
9113
9143
 
9114
- // src/main/app/lifecycle.ts
9115
- import { app as app15, globalShortcut as globalShortcut2 } from "electron";
9116
- function registerAppLifecycle() {
9117
- app15.on("window-all-closed", () => {
9118
- globalShortcut2.unregisterAll();
9119
- app15.quit();
9120
- });
9121
- app15.on("before-quit", () => {
9122
- globalShortcut2.unregisterAll();
9123
- });
9124
- }
9125
-
9126
9144
  // src/main/runtime/devtools-backend.ts
9127
9145
  function createDevtoolsBackend(config = {}) {
9128
9146
  let instance = null;
@@ -9133,9 +9151,11 @@ function createDevtoolsBackend(config = {}) {
9133
9151
  // NOTE: `ownsWindows` is NOT structurally required — the framework's
9134
9152
  // `mainWindowWebPreferences()` + `onMainWindowCreated()` hooks can supply both,
9135
9153
  // and the project close→back lifecycle maps onto the Window facade's
9136
- // `onClose`/`newSession` (probe-validated). Retained for now; migrating to
9137
- // `runtime.windows.main` is the documented next step. (The `persist:simulator`
9138
- // partition is a fixed session used by child WCVs, not main-window state.)
9154
+ // `onClose`/`newSession` (probe-validated). It is RETAINED BY DECISION, not
9155
+ // inertia: migrating to `runtime.windows.main` buys no user value at high
9156
+ // regression cost (see docs/deck-adoption-decision.md, F1). (The
9157
+ // `persist:simulator` partition is a fixed session used by child WCVs, not
9158
+ // main-window state.)
9139
9159
  ownsWindows: true,
9140
9160
  // Pre-ready: app name / CDP port / CSP / privileged scheme — must run before
9141
9161
  // the framework awaits app.whenReady().
@@ -12,6 +12,11 @@ import type { WorkbenchAppConfig } from '../../shared/types.js';
12
12
  * backend is present), so `assemble` ignores the framework `runtime` and builds
13
13
  * the real devtools main window itself. Trust/frame unification and surfacing
14
14
  * the runtime facade are deferred (see framework-extraction-v2.md §7).
15
+ *
16
+ * NOT migrating onto deck's high-level host API (Window facade / runtime.view /
17
+ * DeckSession / grants) is a DELIBERATE, reviewed decision — not a backlog item.
18
+ * The ROI matrix (every candidate NO-GO) + revisit conditions live in
19
+ * docs/deck-adoption-decision.md. Read it before re-proposing such a migration.
15
20
  */
16
21
  export declare function createDevtoolsBackend(config?: WorkbenchAppConfig): RuntimeBackend;
17
22
  //# sourceMappingURL=devtools-backend.d.ts.map
@@ -12,6 +12,11 @@ import { registerAppLifecycle } from '../app/lifecycle.js';
12
12
  * backend is present), so `assemble` ignores the framework `runtime` and builds
13
13
  * the real devtools main window itself. Trust/frame unification and surfacing
14
14
  * the runtime facade are deferred (see framework-extraction-v2.md §7).
15
+ *
16
+ * NOT migrating onto deck's high-level host API (Window facade / runtime.view /
17
+ * DeckSession / grants) is a DELIBERATE, reviewed decision — not a backlog item.
18
+ * The ROI matrix (every candidate NO-GO) + revisit conditions live in
19
+ * docs/deck-adoption-decision.md. Read it before re-proposing such a migration.
15
20
  */
16
21
  export function createDevtoolsBackend(config = {}) {
17
22
  // Hoisted so `onShutdown` can reach the assembled context (assigned by `assemble`).
@@ -23,9 +28,11 @@ export function createDevtoolsBackend(config = {}) {
23
28
  // NOTE: `ownsWindows` is NOT structurally required — the framework's
24
29
  // `mainWindowWebPreferences()` + `onMainWindowCreated()` hooks can supply both,
25
30
  // and the project close→back lifecycle maps onto the Window facade's
26
- // `onClose`/`newSession` (probe-validated). Retained for now; migrating to
27
- // `runtime.windows.main` is the documented next step. (The `persist:simulator`
28
- // partition is a fixed session used by child WCVs, not main-window state.)
31
+ // `onClose`/`newSession` (probe-validated). It is RETAINED BY DECISION, not
32
+ // inertia: migrating to `runtime.windows.main` buys no user value at high
33
+ // regression cost (see docs/deck-adoption-decision.md, F1). (The
34
+ // `persist:simulator` partition is a fixed session used by child WCVs, not
35
+ // main-window state.)
29
36
  ownsWindows: true,
30
37
  // Pre-ready: app name / CDP port / CSP / privileged scheme — must run before
31
38
  // the framework awaits app.whenReady().
@@ -15,8 +15,10 @@
15
15
  * syntax: under strictFunctionTypes method signatures compare bivariantly,
16
16
  * which would let a wrongly-narrowed implementation or host override slip
17
17
  * past the drift sentinel.
18
- * - `workspace.openProject` stays writable (no `readonly`): qdmp gates
19
- * project permissions by reassigning it (documented monkey-patch contract).
18
+ * - `workspace.openProject` stays writable (no `readonly`) for backward
19
+ * compat: hosts MAY still gate permissions by reassigning it. The preferred
20
+ * path is now the declarative `WorkbenchAppConfig.onBeforeOpenProject` hook
21
+ * (runs before any side effect, throw to veto) — see host-migration.md §7.
20
22
  *
21
23
  * `asMiniappRuntime(ctx)` is an identity return — the contract is a typed
22
24
  * VIEW onto the live context, not a snapshot/projection. That is what makes
@@ -74,6 +74,13 @@ export interface WorkbenchContext {
74
74
  parentWindow: BrowserWindow;
75
75
  templates: ProjectTemplate[];
76
76
  }) => Promise<CustomCreateProjectDialogResult>;
77
+ /**
78
+ * Host permission gate consulted by `workspace.openProject` BEFORE any side
79
+ * effect. Throwing vetoes the open (see `WorkbenchAppConfig.onBeforeOpenProject`).
80
+ * Wired from the config by `createDevtoolsRuntime`; undefined for the
81
+ * single-tenant default.
82
+ */
83
+ onBeforeOpenProject?: (projectPath: string) => void | Promise<void>;
77
84
  /**
78
85
  * Trust predicate consulted by `IpcRegistry` for every incoming IPC.
79
86
  * Resolves the currently-trusted senders (main renderer + overlays)
@@ -183,6 +190,8 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
183
190
  builtinTemplates?: 'all' | 'none' | readonly string[];
184
191
  /** Host-supplied "新建项目" dialog hook. */
185
192
  customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
193
+ /** Host permission gate run before a project opens (throw to veto). */
194
+ onBeforeOpenProject?: WorkbenchContext['onBeforeOpenProject'];
186
195
  }
187
196
  export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
188
197
  export { sanitizeTemplates };
@@ -37,6 +37,7 @@ export function createWorkbenchContext(opts) {
37
37
  ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
38
38
  ctx.projectTemplates = resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
39
39
  ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
40
+ ctx.onBeforeOpenProject = opts.onBeforeOpenProject;
40
41
  ctx.workspace = createWorkspaceService(ctx);
41
42
  ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
42
43
  return ctx;
@@ -80,6 +80,27 @@ export function createWorkspaceService(ctx) {
80
80
  ? provider.validateProjectDir(dirPath)
81
81
  : null,
82
82
  async openProject(projectPath) {
83
+ // Host permission gate — runs BEFORE any side effect (referer reset,
84
+ // session teardown, compile/dev-server spin-up). A throwing hook vetoes
85
+ // the open: return early with the error and leave the currently-active
86
+ // session fully intact. The declarative replacement for monkey-patching
87
+ // this method. See WorkbenchAppConfig.onBeforeOpenProject.
88
+ if (ctx.onBeforeOpenProject) {
89
+ try {
90
+ await ctx.onBeforeOpenProject(projectPath);
91
+ }
92
+ catch (err) {
93
+ // Surface the veto to the status bar — symmetric with the
94
+ // validateProjectDir rejection below (which already emits an error
95
+ // status). The framework caught the error, so it owns the minimal
96
+ // user-visible feedback; a host gate must not have to thread
97
+ // `notify` through a singleton just to report why the open was
98
+ // denied. The host can still layer richer UX (e.g. a dialog).
99
+ const error = err instanceof Error ? err.message : String(err);
100
+ sendStatus('error', error);
101
+ return { success: false, error };
102
+ }
103
+ }
83
104
  clearSimulatorServicewechatReferer();
84
105
  // Invalidate the outgoing session's onLog BEFORE teardown starts (same
85
106
  // order as closeProject below): the dying compile worker flushes
@@ -6,6 +6,12 @@ export interface WindowOptions {
6
6
  minWidth?: number;
7
7
  minHeight?: number;
8
8
  indexHtml: string;
9
+ /**
10
+ * Auto-show the window on `ready-to-show` in non-test envs. Defaults to
11
+ * `true`. `false` lets a login-gating host keep the window hidden and call
12
+ * `show()` itself. The test env always uses `showInactive()` regardless.
13
+ */
14
+ autoShow?: boolean;
9
15
  }
10
16
  export declare function createMainWindow(opts: WindowOptions): BrowserWindow;
11
17
  //# sourceMappingURL=create.d.ts.map
@@ -22,17 +22,24 @@ export function createMainWindow(opts) {
22
22
  },
23
23
  });
24
24
  mainWindow.once('ready-to-show', () => {
25
- if (process.env.NODE_ENV === 'test') {
26
- mainWindow.showInactive();
25
+ const isTest = process.env.NODE_ENV === 'test';
26
+ // Visibility is governed by `autoShow` in BOTH envs. A login-gating host
27
+ // opts out via `autoShow: false` and reveals the window itself once auth
28
+ // passes — don't flash an un-authed window. The env only chooses HOW to
29
+ // show: test uses showInactive() so e2e windows don't steal focus, prod
30
+ // uses show(). The framework must never force-show when the host opted
31
+ // out, in test env either (that would fight the host's own reveal handler).
32
+ if (opts.autoShow !== false) {
33
+ if (isTest)
34
+ mainWindow.showInactive();
35
+ else
36
+ mainWindow.show();
27
37
  }
28
- else {
29
- mainWindow.show();
30
- // Don't auto-open a detached DevTools for the devtools UI shell itself
31
- // it's noise for normal use (the mini-app's Console lives in the embedded
32
- // right-panel DevTools, not here). Opt in via env for debugging the shell.
33
- if (!app.isPackaged && process.env.DIMINA_DEVTOOLS_MAIN_INSPECTOR === '1') {
34
- mainWindow.webContents.openDevTools({ mode: 'detach' });
35
- }
38
+ // Don't auto-open a detached DevTools for the devtools UI shell itself —
39
+ // it's noise for normal use (the mini-app's Console lives in the embedded
40
+ // right-panel DevTools, not here). Opt in via env for debugging the shell.
41
+ if (!isTest && !app.isPackaged && process.env.DIMINA_DEVTOOLS_MAIN_INSPECTOR === '1') {
42
+ mainWindow.webContents.openDevTools({ mode: 'detach' });
36
43
  }
37
44
  });
38
45
  const rendererDir = path.dirname(opts.indexHtml);