@dimina-kit/devtools 0.3.2-dev.20260522110734 → 0.3.2-dev.20260522142649

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 (54) hide show
  1. package/README.md +90 -155
  2. package/dist/main/api.d.ts +3 -11
  3. package/dist/main/api.js +2 -13
  4. package/dist/main/app/app.d.ts +14 -1
  5. package/dist/main/app/app.js +93 -2
  6. package/dist/main/index.bundle.js +140 -52
  7. package/dist/main/ipc/app.d.ts +1 -1
  8. package/dist/main/ipc/app.js +3 -0
  9. package/dist/main/ipc/simulator.d.ts +1 -1
  10. package/dist/main/ipc/simulator.js +2 -3
  11. package/dist/main/ipc/toolbar.d.ts +1 -1
  12. package/dist/main/ipc/toolbar.js +14 -4
  13. package/dist/main/services/layout/index.d.ts +4 -10
  14. package/dist/main/services/layout/index.js +11 -19
  15. package/dist/main/services/module.d.ts +1 -2
  16. package/dist/main/services/simulator/custom-apis.d.ts +0 -1
  17. package/dist/main/services/simulator/custom-apis.js +0 -1
  18. package/dist/main/services/toolbar/toolbar-store.d.ts +23 -0
  19. package/dist/main/services/toolbar/toolbar-store.js +23 -0
  20. package/dist/main/services/views/view-manager.d.ts +6 -0
  21. package/dist/main/services/views/view-manager.js +6 -3
  22. package/dist/main/services/workbench-context.d.ts +40 -14
  23. package/dist/main/services/workbench-context.js +6 -1
  24. package/dist/main/utils/disposable.d.ts +5 -0
  25. package/dist/main/utils/disposable.js +7 -0
  26. package/dist/main/utils/ipc-registry.js +5 -1
  27. package/dist/main/utils/sender-policy.d.ts +4 -1
  28. package/dist/main/utils/sender-policy.js +6 -0
  29. package/dist/preload/runtime/custom-apis.js +72 -1
  30. package/dist/preload/windows/simulator.js +41 -1
  31. package/dist/renderer/assets/index-4lkyF644.js +46 -0
  32. package/dist/renderer/assets/{input-CoWcfa8u.js → input-D4byFOLS.js} +2 -2
  33. package/dist/renderer/assets/{ipc-transport-Cd2xPrsa.js → ipc-transport-BzPzxPwf.js} +2 -2
  34. package/dist/renderer/assets/{popover-Nq5gT9DO.js → popover-cxKrg_os.js} +2 -2
  35. package/dist/renderer/assets/{select-By5KH-UG.js → select-BgSkkdHW.js} +2 -2
  36. package/dist/renderer/assets/{settings-api-Do0_Smq1.js → settings-api-BXCw9vOZ.js} +2 -2
  37. package/dist/renderer/assets/{settings-Dj_N70qR.js → settings-oD2K1jdT.js} +2 -2
  38. package/dist/renderer/assets/{workbenchSettings-CazSLGYt.js → workbenchSettings-CTNBpTft.js} +2 -2
  39. package/dist/renderer/entries/main/index.html +4 -4
  40. package/dist/renderer/entries/popover/index.html +4 -4
  41. package/dist/renderer/entries/settings/index.html +4 -4
  42. package/dist/renderer/entries/workbench-settings/index.html +3 -3
  43. package/dist/shared/ipc-channels.d.ts +3 -2
  44. package/dist/shared/ipc-channels.js +4 -3
  45. package/dist/shared/ipc-schemas.d.ts +2 -0
  46. package/dist/shared/ipc-schemas.js +2 -0
  47. package/dist/shared/types.d.ts +55 -3
  48. package/dist/simulator/assets/simulator-CILbo0Ji.js +9 -0
  49. package/dist/simulator/simulator.html +1 -1
  50. package/package.json +3 -35
  51. package/dist/main/simulator-apis.d.ts +0 -23
  52. package/dist/main/simulator-apis.js +0 -24
  53. package/dist/renderer/assets/index-D8VFYBh-.js +0 -46
  54. package/dist/simulator/assets/simulator-CZc88Jeh.js +0 -9
@@ -124,6 +124,13 @@ function toDisposable(fn) {
124
124
  var DisposableRegistry = class {
125
125
  entries = [];
126
126
  _disposed = false;
127
+ /**
128
+ * Number of live entries. A wrapper's `dispose` splices its entry out and
129
+ * `disposeAll` clears the array, so `entries.length` is the live count.
130
+ */
131
+ get size() {
132
+ return this.entries.length;
133
+ }
127
134
  add(d) {
128
135
  if (this._disposed) {
129
136
  throw new Error("cannot add to disposed registry");
@@ -391,6 +398,7 @@ function createWorkbenchSenderPolicy(ctx) {
391
398
  if (sender.isDestroyed()) return false;
392
399
  if (ctx.windows.isMainSender(sender.id)) return true;
393
400
  if (ctx.windows.isSettingsWindowSender(sender.id)) return true;
401
+ if (ctx.trustedWindowSenderIds.has(sender.id)) return true;
394
402
  const settingsViewId = ctx.views.getSettingsWebContentsId();
395
403
  if (settingsViewId != null && sender.id === settingsViewId) return true;
396
404
  const popoverViewId = ctx.views.getPopoverWebContentsId();
@@ -490,15 +498,16 @@ var PopoverChannel = {
490
498
  var ToolbarChannel = {
491
499
  GetActions: "toolbar:getActions",
492
500
  ActionsChanged: "toolbar:actionsChanged",
493
- /** Prefix for dynamic action channels: `toolbar:action:${actionId}` */
494
- ActionPrefix: "toolbar:action:"
501
+ /** Invoke a toolbar action by id: `invoke(Invoke, actionId)`. */
502
+ Invoke: "toolbar:invoke"
495
503
  };
496
504
  var WindowChannel = {
497
505
  NavigateBack: "window:navigateBack"
498
506
  };
499
507
  var AppChannel = {
500
508
  GetPreloadPath: "app:getPreloadPath",
501
- GetBranding: "app:getBranding"
509
+ GetBranding: "app:getBranding",
510
+ GetHeaderHeight: "app:getHeaderHeight"
502
511
  };
503
512
  var AutomationChannel = {
504
513
  GetPort: "automation:port"
@@ -578,43 +587,43 @@ import { WebContentsView as WebContentsView2, webContents } from "electron";
578
587
  import path6 from "path";
579
588
 
580
589
  // src/main/services/layout/index.ts
581
- var HEADER_H = 40;
582
590
  var SPLITTER_W = 4;
583
591
  function getRightX(simWidth) {
584
592
  return simWidth + SPLITTER_W;
585
593
  }
586
- function computeRightPanelBounds(contentWidth, contentHeight, simWidth) {
594
+ function computeRightPanelBounds(contentWidth, contentHeight, simWidth, headerHeight) {
587
595
  const x = getRightX(simWidth);
588
596
  return {
589
597
  x,
590
- y: HEADER_H,
598
+ y: headerHeight,
591
599
  width: Math.max(1, contentWidth - x),
592
- height: Math.max(1, contentHeight - HEADER_H)
600
+ height: Math.max(1, contentHeight - headerHeight)
593
601
  };
594
602
  }
595
- function computeSimulatorBounds(contentWidth, contentHeight, simWidth) {
596
- return computeRightPanelBounds(contentWidth, contentHeight, simWidth);
603
+ function computeSimulatorBounds(contentWidth, contentHeight, simWidth, headerHeight) {
604
+ return computeRightPanelBounds(contentWidth, contentHeight, simWidth, headerHeight);
597
605
  }
598
606
  var SETTINGS_W = 320;
599
- function computeSettingsBounds(contentWidth, contentHeight) {
607
+ function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
600
608
  return {
601
609
  x: Math.max(0, contentWidth - SETTINGS_W),
602
- y: HEADER_H,
610
+ y: headerHeight,
603
611
  width: SETTINGS_W,
604
- height: Math.max(1, contentHeight - HEADER_H)
612
+ height: Math.max(1, contentHeight - headerHeight)
605
613
  };
606
614
  }
607
- function computePopoverBounds(contentWidth, contentHeight) {
615
+ function computePopoverBounds(contentWidth, contentHeight, headerHeight) {
608
616
  return {
609
617
  x: 0,
610
- y: HEADER_H,
618
+ y: headerHeight,
611
619
  width: contentWidth,
612
- height: contentHeight - HEADER_H
620
+ height: contentHeight - headerHeight
613
621
  };
614
622
  }
615
623
 
616
624
  // src/main/services/views/view-manager.ts
617
625
  function createViewManager(ctx) {
626
+ const headerHeight = ctx.headerHeight ?? 40;
618
627
  let simulatorView = null;
619
628
  let simulatorViewAdded = false;
620
629
  let settingsView = null;
@@ -640,17 +649,17 @@ function createViewManager(ctx) {
640
649
  function applySimulatorBounds(simWidth) {
641
650
  if (!simulatorView || ctx.windows.mainWindow.isDestroyed()) return;
642
651
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
643
- simulatorView.setBounds(computeSimulatorBounds(w, h, simWidth));
652
+ simulatorView.setBounds(computeSimulatorBounds(w, h, simWidth, headerHeight));
644
653
  }
645
654
  function applySettingsBounds() {
646
655
  if (!settingsView || ctx.windows.mainWindow.isDestroyed()) return;
647
656
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
648
- settingsView.setBounds(computeSettingsBounds(w, h));
657
+ settingsView.setBounds(computeSettingsBounds(w, h, headerHeight));
649
658
  }
650
659
  function applyPopoverBounds() {
651
660
  if (!popoverView || ctx.windows.mainWindow.isDestroyed()) return;
652
661
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
653
- popoverView.setBounds(computePopoverBounds(w, h));
662
+ popoverView.setBounds(computePopoverBounds(w, h, headerHeight));
654
663
  }
655
664
  function attachSimulator(simWcId, simWidth) {
656
665
  const sim = webContents.fromId(simWcId);
@@ -1248,6 +1257,53 @@ function createLocalProjectsProvider() {
1248
1257
  };
1249
1258
  }
1250
1259
 
1260
+ // src/main/services/simulator/custom-apis.ts
1261
+ function createSimulatorApiRegistry() {
1262
+ const handlers2 = /* @__PURE__ */ new Map();
1263
+ return {
1264
+ register(name, handler) {
1265
+ handlers2.set(name, handler);
1266
+ return () => {
1267
+ if (handlers2.get(name) === handler) handlers2.delete(name);
1268
+ };
1269
+ },
1270
+ list() {
1271
+ return Array.from(handlers2.keys());
1272
+ },
1273
+ async invoke(name, params) {
1274
+ const handler = handlers2.get(name);
1275
+ if (!handler) throw new Error(`Simulator API "${name}" is not registered`);
1276
+ return await handler(params);
1277
+ },
1278
+ clear() {
1279
+ handlers2.clear();
1280
+ }
1281
+ };
1282
+ }
1283
+
1284
+ // src/main/services/toolbar/toolbar-store.ts
1285
+ function createToolbarStore() {
1286
+ let actions = [];
1287
+ return {
1288
+ set(next) {
1289
+ const seen = /* @__PURE__ */ new Set();
1290
+ for (const action of next) {
1291
+ if (seen.has(action.id)) {
1292
+ throw new Error(`Duplicate toolbar action id "${action.id}"`);
1293
+ }
1294
+ seen.add(action.id);
1295
+ }
1296
+ actions = next.slice();
1297
+ },
1298
+ list() {
1299
+ return actions.map(({ id, label }) => ({ id, label }));
1300
+ },
1301
+ getHandler(id) {
1302
+ return actions.find((a) => a.id === id)?.handler;
1303
+ }
1304
+ };
1305
+ }
1306
+
1251
1307
  // src/main/services/projects/templates.ts
1252
1308
  function resolveTemplates(builtin, injected, mode) {
1253
1309
  let kept;
@@ -1309,10 +1365,13 @@ function createWorkbenchContext(opts) {
1309
1365
  panels: opts.panels ?? ["wxml", "console", "appdata", "storage"],
1310
1366
  apiNamespaces: opts.apiNamespaces ?? [],
1311
1367
  appName: opts.appName ?? "Dimina DevTools",
1312
- toolbarActions: opts.toolbarActions,
1368
+ headerHeight: opts.headerHeight ?? 40,
1313
1369
  brandingProvider: opts.brandingProvider
1314
1370
  };
1315
1371
  ctx.registry = new DisposableRegistry();
1372
+ ctx.trustedWindowSenderIds = /* @__PURE__ */ new Map();
1373
+ ctx.simulatorApis = createSimulatorApiRegistry();
1374
+ ctx.toolbar = createToolbarStore();
1316
1375
  ctx.windows = createWindowService(opts.mainWindow);
1317
1376
  ctx.views = createViewManager(ctx);
1318
1377
  ctx.notify = createRendererNotifier(ctx);
@@ -1461,7 +1520,7 @@ var IpcRegistry = class {
1461
1520
  registry = new DisposableRegistry();
1462
1521
  handle(channel, fn) {
1463
1522
  const policy = this.policy;
1464
- const guarded = policy ? (event, ...args) => {
1523
+ const guarded = policy ? async (event, ...args) => {
1465
1524
  const sender = event.sender;
1466
1525
  if (!policy(sender)) {
1467
1526
  console.warn(
@@ -1516,6 +1575,8 @@ function registerAppIpc(ctx) {
1516
1575
  }).handle(AppChannel.GetBranding, async () => {
1517
1576
  if (ctx.brandingProvider) return ctx.brandingProvider();
1518
1577
  return { appName: ctx.appName };
1578
+ }).handle(AppChannel.GetHeaderHeight, () => {
1579
+ return ctx.headerHeight;
1519
1580
  });
1520
1581
  }
1521
1582
 
@@ -1549,6 +1610,7 @@ var SimulatorCustomApiInvokeSchema = z.tuple([
1549
1610
  z.string().min(1).max(256),
1550
1611
  z.unknown()
1551
1612
  ]);
1613
+ var ToolbarInvokeSchema = z.tuple([z.string().min(1).max(256)]);
1552
1614
  var ProjectGetPagesSchema = z.tuple([AbsolutePath]);
1553
1615
  var ProjectGetCompileConfigSchema = z.tuple([AbsolutePath]);
1554
1616
  var ProjectsRemoveSchema = z.tuple([AbsolutePath]);
@@ -1585,31 +1647,6 @@ var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
1585
1647
  var ProjectCaptureThumbnailSchema = z.tuple([AbsolutePath]);
1586
1648
  var ProjectGetThumbnailSchema = z.tuple([AbsolutePath]);
1587
1649
 
1588
- // src/main/services/simulator/custom-apis.ts
1589
- function createSimulatorApiRegistry() {
1590
- const handlers2 = /* @__PURE__ */ new Map();
1591
- return {
1592
- register(name, handler) {
1593
- handlers2.set(name, handler);
1594
- return () => {
1595
- if (handlers2.get(name) === handler) handlers2.delete(name);
1596
- };
1597
- },
1598
- list() {
1599
- return Array.from(handlers2.keys());
1600
- },
1601
- async invoke(name, params) {
1602
- const handler = handlers2.get(name);
1603
- if (!handler) throw new Error(`Simulator API "${name}" is not registered`);
1604
- return await handler(params);
1605
- },
1606
- clear() {
1607
- handlers2.clear();
1608
- }
1609
- };
1610
- }
1611
- var simulatorApiRegistry = createSimulatorApiRegistry();
1612
-
1613
1650
  // src/main/ipc/simulator.ts
1614
1651
  function registerSimulatorIpc(ctx) {
1615
1652
  return new IpcRegistry(ctx.senderPolicy).handle(SimulatorChannel.Attach, (_, ...args) => {
@@ -1624,10 +1661,10 @@ function registerSimulatorIpc(ctx) {
1624
1661
  const [visible, simWidth] = validate(SimulatorChannel.SetVisible, SimulatorSetVisibleSchema, args);
1625
1662
  ctx.views.setVisible(visible, simWidth);
1626
1663
  }).handle(SimulatorCustomApiChannel.List, () => {
1627
- return simulatorApiRegistry.list();
1664
+ return ctx.simulatorApis.list();
1628
1665
  }).handle(SimulatorCustomApiChannel.Invoke, (_, ...args) => {
1629
1666
  const [name, params] = validate(SimulatorCustomApiChannel.Invoke, SimulatorCustomApiInvokeSchema, args);
1630
- return simulatorApiRegistry.invoke(name, params);
1667
+ return ctx.simulatorApis.invoke(name, params);
1631
1668
  });
1632
1669
  }
1633
1670
 
@@ -2000,9 +2037,15 @@ var settingsModule = {
2000
2037
 
2001
2038
  // src/main/ipc/toolbar.ts
2002
2039
  function registerToolbarIpc(ctx) {
2003
- return new IpcRegistry(ctx.senderPolicy).handle(ToolbarChannel.GetActions, async () => {
2004
- if (ctx.toolbarActions) return ctx.toolbarActions();
2005
- return [];
2040
+ return new IpcRegistry(ctx.senderPolicy).handle(ToolbarChannel.GetActions, () => {
2041
+ return ctx.toolbar.list();
2042
+ }).handle(ToolbarChannel.Invoke, async (_, ...args) => {
2043
+ const [id] = validate(ToolbarChannel.Invoke, ToolbarInvokeSchema, args);
2044
+ const handler = ctx.toolbar.getHandler(id);
2045
+ if (!handler) {
2046
+ throw new Error(`Toolbar action "${id}" is not registered`);
2047
+ }
2048
+ await handler();
2006
2049
  });
2007
2050
  }
2008
2051
 
@@ -3594,6 +3637,27 @@ var BUILTIN_MODULES = {
3594
3637
  popover: popoverModule,
3595
3638
  settings: settingsModule
3596
3639
  };
3640
+ function registerTrustedWindow(context, win) {
3641
+ const senderId = win.webContents.id;
3642
+ const counts = context.trustedWindowSenderIds;
3643
+ counts.set(senderId, (counts.get(senderId) ?? 0) + 1);
3644
+ let removed = false;
3645
+ const onClosed = () => {
3646
+ counts.delete(senderId);
3647
+ win.removeListener("closed", onClosed);
3648
+ };
3649
+ function remove() {
3650
+ if (removed) return;
3651
+ removed = true;
3652
+ win.removeListener("closed", onClosed);
3653
+ const count = counts.get(senderId);
3654
+ if (count === void 0) return;
3655
+ if (count <= 1) counts.delete(senderId);
3656
+ else counts.set(senderId, count - 1);
3657
+ }
3658
+ win.once("closed", onClosed);
3659
+ return toDisposable(remove);
3660
+ }
3597
3661
  function parseAutoArgs() {
3598
3662
  const argv = process.argv;
3599
3663
  let auto = false;
@@ -3652,8 +3716,8 @@ function createContext(config, mainWindow, rendererDir2) {
3652
3716
  panels: config.panels,
3653
3717
  appName: config.appName,
3654
3718
  apiNamespaces: config.apiNamespaces,
3719
+ headerHeight: config.headerHeight,
3655
3720
  brandingProvider: config.brandingProvider,
3656
- toolbarActions: config.toolbarActions,
3657
3721
  // The host-supplied ProjectsProvider / template types in `shared/types`
3658
3722
  // are structurally compatible with the main-process equivalents —
3659
3723
  // these casts are safe; we re-narrow at the workspace-service /
@@ -3670,9 +3734,18 @@ function registerBuiltinModules(config, context) {
3670
3734
  if (modules[moduleId]) context.registry.add(BUILTIN_MODULES[moduleId].setup(context));
3671
3735
  });
3672
3736
  }
3737
+ function toMenuContext(context) {
3738
+ const menuContext = { ...context };
3739
+ delete menuContext.registry;
3740
+ delete menuContext.senderPolicy;
3741
+ delete menuContext.trustedWindowSenderIds;
3742
+ delete menuContext.simulatorApis;
3743
+ delete menuContext.toolbar;
3744
+ return menuContext;
3745
+ }
3673
3746
  function installMenu(config, mainWindow, context) {
3674
3747
  if (config.menuBuilder) {
3675
- config.menuBuilder(mainWindow, context);
3748
+ config.menuBuilder(mainWindow, toMenuContext(context));
3676
3749
  } else {
3677
3750
  installAppMenu(context);
3678
3751
  }
@@ -3746,9 +3819,23 @@ function createWorkbenchApp(config = {}) {
3746
3819
  context.registry.add(installThemeBackgroundSync());
3747
3820
  registerBuiltinModules(config, context);
3748
3821
  installMenu(config, mainWindow, context);
3822
+ const hostIpc = new IpcRegistry(context.senderPolicy);
3823
+ context.registry.add(hostIpc);
3749
3824
  const instance = {
3750
3825
  mainWindow,
3751
3826
  context,
3827
+ ipc: hostIpc,
3828
+ // Return the registry wrapper, not the raw disposable: disposing the
3829
+ // wrapper splices the registry entry out AND drives the underlying
3830
+ // teardown, so a single dispose leaves no dead entry behind.
3831
+ registerTrustedWindow: (win) => context.registry.add(registerTrustedWindow(context, win)),
3832
+ registerSimulatorApi: (name, handler) => context.registry.add(toDisposable(context.simulatorApis.register(name, handler))),
3833
+ toolbar: {
3834
+ set: (actions) => {
3835
+ context.toolbar.set(actions);
3836
+ context.notify.toolbarActionsChanged();
3837
+ }
3838
+ },
3752
3839
  dispose: () => disposeContext(context)
3753
3840
  };
3754
3841
  if (config.onSetup) {
@@ -3758,6 +3845,7 @@ function createWorkbenchApp(config = {}) {
3758
3845
  instance.updateManager = new UpdateManager({
3759
3846
  checker: config.updateChecker,
3760
3847
  mainWindow,
3848
+ senderPolicy: context.senderPolicy,
3761
3849
  checkInterval: config.updateOptions?.checkInterval,
3762
3850
  initialDelay: config.updateOptions?.initialDelay,
3763
3851
  getCurrentVersion: config.updateOptions?.getCurrentVersion
@@ -1,4 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '../utils/disposable.js';
3
- export declare function registerAppIpc(ctx: Pick<WorkbenchContext, 'preloadPath' | 'brandingProvider' | 'appName' | 'senderPolicy'>): Disposable;
3
+ export declare function registerAppIpc(ctx: Pick<WorkbenchContext, 'preloadPath' | 'brandingProvider' | 'appName' | 'headerHeight' | 'senderPolicy'>): Disposable;
4
4
  //# sourceMappingURL=app.d.ts.map
@@ -10,6 +10,9 @@ export function registerAppIpc(ctx) {
10
10
  if (ctx.brandingProvider)
11
11
  return ctx.brandingProvider();
12
12
  return { appName: ctx.appName };
13
+ })
14
+ .handle(AppChannel.GetHeaderHeight, () => {
15
+ return ctx.headerHeight;
13
16
  });
14
17
  }
15
18
  //# sourceMappingURL=app.js.map
@@ -1,4 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '../utils/disposable.js';
3
- export declare function registerSimulatorIpc(ctx: Pick<WorkbenchContext, 'views' | 'notify' | 'senderPolicy'>): Disposable;
3
+ export declare function registerSimulatorIpc(ctx: Pick<WorkbenchContext, 'views' | 'notify' | 'senderPolicy' | 'simulatorApis'>): Disposable;
4
4
  //# sourceMappingURL=simulator.d.ts.map
@@ -1,6 +1,5 @@
1
1
  import { SimulatorChannel, SimulatorCustomApiChannel } from '../../shared/ipc-channels.js';
2
2
  import { SimulatorAttachSchema, SimulatorCustomApiInvokeSchema, SimulatorResizeSchema, SimulatorSetVisibleSchema, } from '../../shared/ipc-schemas.js';
3
- import { simulatorApiRegistry } from '../services/simulator/custom-apis.js';
4
3
  import { validate } from '../utils/ipc-schema.js';
5
4
  import { IpcRegistry } from '../utils/ipc-registry.js';
6
5
  export function registerSimulatorIpc(ctx) {
@@ -21,11 +20,11 @@ export function registerSimulatorIpc(ctx) {
21
20
  ctx.views.setVisible(visible, simWidth);
22
21
  })
23
22
  .handle(SimulatorCustomApiChannel.List, () => {
24
- return simulatorApiRegistry.list();
23
+ return ctx.simulatorApis.list();
25
24
  })
26
25
  .handle(SimulatorCustomApiChannel.Invoke, (_, ...args) => {
27
26
  const [name, params] = validate(SimulatorCustomApiChannel.Invoke, SimulatorCustomApiInvokeSchema, args);
28
- return simulatorApiRegistry.invoke(name, params);
27
+ return ctx.simulatorApis.invoke(name, params);
29
28
  });
30
29
  }
31
30
  //# sourceMappingURL=simulator.js.map
@@ -1,4 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '../utils/disposable.js';
3
- export declare function registerToolbarIpc(ctx: Pick<WorkbenchContext, 'toolbarActions' | 'senderPolicy'>): Disposable;
3
+ export declare function registerToolbarIpc(ctx: Pick<WorkbenchContext, 'toolbar' | 'senderPolicy'>): Disposable;
4
4
  //# sourceMappingURL=toolbar.d.ts.map
@@ -1,11 +1,21 @@
1
1
  import { ToolbarChannel } from '../../shared/ipc-channels.js';
2
+ import { ToolbarInvokeSchema } from '../../shared/ipc-schemas.js';
3
+ import { validate } from '../utils/ipc-schema.js';
2
4
  import { IpcRegistry } from '../utils/ipc-registry.js';
3
5
  export function registerToolbarIpc(ctx) {
4
6
  return new IpcRegistry(ctx.senderPolicy)
5
- .handle(ToolbarChannel.GetActions, async () => {
6
- if (ctx.toolbarActions)
7
- return ctx.toolbarActions();
8
- return [];
7
+ .handle(ToolbarChannel.GetActions, () => {
8
+ // Project to {id,label}[] — the non-serialisable handler never leaves
9
+ // the main process.
10
+ return ctx.toolbar.list();
11
+ })
12
+ .handle(ToolbarChannel.Invoke, async (_, ...args) => {
13
+ const [id] = validate(ToolbarChannel.Invoke, ToolbarInvokeSchema, args);
14
+ const handler = ctx.toolbar.getHandler(id);
15
+ if (!handler) {
16
+ throw new Error(`Toolbar action "${id}" is not registered`);
17
+ }
18
+ await handler();
9
19
  });
10
20
  }
11
21
  //# sourceMappingURL=toolbar.js.map
@@ -1,11 +1,5 @@
1
1
  /** Layout constants (px) */
2
- export declare let HEADER_H: number;
3
2
  export declare const SPLITTER_W = 4;
4
- /**
5
- * Override the header height used for layout calculations.
6
- * Call this before any views are positioned (e.g. in onSetup).
7
- */
8
- export declare function setHeaderHeight(h: number): void;
9
3
  /**
10
4
  * X coordinate where the right panel starts (simulator width + splitter).
11
5
  */
@@ -16,12 +10,12 @@ export interface Bounds {
16
10
  width: number;
17
11
  height: number;
18
12
  }
19
- export declare function computeRightPanelBounds(contentWidth: number, contentHeight: number, simWidth: number): Bounds;
13
+ export declare function computeRightPanelBounds(contentWidth: number, contentHeight: number, simWidth: number, headerHeight: number): Bounds;
20
14
  /**
21
15
  * Compute bounds for DevTools view (same as right panel, overlays it).
22
16
  */
23
- export declare function computeSimulatorBounds(contentWidth: number, contentHeight: number, simWidth: number): Bounds;
17
+ export declare function computeSimulatorBounds(contentWidth: number, contentHeight: number, simWidth: number, headerHeight: number): Bounds;
24
18
  export declare const SETTINGS_W = 320;
25
- export declare function computeSettingsBounds(contentWidth: number, contentHeight: number): Bounds;
26
- export declare function computePopoverBounds(contentWidth: number, contentHeight: number): Bounds;
19
+ export declare function computeSettingsBounds(contentWidth: number, contentHeight: number, headerHeight: number): Bounds;
20
+ export declare function computePopoverBounds(contentWidth: number, contentHeight: number, headerHeight: number): Bounds;
27
21
  //# sourceMappingURL=index.d.ts.map
@@ -1,49 +1,41 @@
1
1
  /** Layout constants (px) */
2
- export let HEADER_H = 40;
3
2
  export const SPLITTER_W = 4;
4
- /**
5
- * Override the header height used for layout calculations.
6
- * Call this before any views are positioned (e.g. in onSetup).
7
- */
8
- export function setHeaderHeight(h) {
9
- HEADER_H = h;
10
- }
11
3
  /**
12
4
  * X coordinate where the right panel starts (simulator width + splitter).
13
5
  */
14
6
  export function getRightX(simWidth) {
15
7
  return simWidth + SPLITTER_W;
16
8
  }
17
- export function computeRightPanelBounds(contentWidth, contentHeight, simWidth) {
9
+ export function computeRightPanelBounds(contentWidth, contentHeight, simWidth, headerHeight) {
18
10
  const x = getRightX(simWidth);
19
11
  return {
20
12
  x,
21
- y: HEADER_H,
13
+ y: headerHeight,
22
14
  width: Math.max(1, contentWidth - x),
23
- height: Math.max(1, contentHeight - HEADER_H),
15
+ height: Math.max(1, contentHeight - headerHeight),
24
16
  };
25
17
  }
26
18
  /**
27
19
  * Compute bounds for DevTools view (same as right panel, overlays it).
28
20
  */
29
- export function computeSimulatorBounds(contentWidth, contentHeight, simWidth) {
30
- return computeRightPanelBounds(contentWidth, contentHeight, simWidth);
21
+ export function computeSimulatorBounds(contentWidth, contentHeight, simWidth, headerHeight) {
22
+ return computeRightPanelBounds(contentWidth, contentHeight, simWidth, headerHeight);
31
23
  }
32
24
  export const SETTINGS_W = 320;
33
- export function computeSettingsBounds(contentWidth, contentHeight) {
25
+ export function computeSettingsBounds(contentWidth, contentHeight, headerHeight) {
34
26
  return {
35
27
  x: Math.max(0, contentWidth - SETTINGS_W),
36
- y: HEADER_H,
28
+ y: headerHeight,
37
29
  width: SETTINGS_W,
38
- height: Math.max(1, contentHeight - HEADER_H),
30
+ height: Math.max(1, contentHeight - headerHeight),
39
31
  };
40
32
  }
41
- export function computePopoverBounds(contentWidth, contentHeight) {
33
+ export function computePopoverBounds(contentWidth, contentHeight, headerHeight) {
42
34
  return {
43
35
  x: 0,
44
- y: HEADER_H,
36
+ y: headerHeight,
45
37
  width: contentWidth,
46
- height: contentHeight - HEADER_H,
38
+ height: contentHeight - headerHeight,
47
39
  };
48
40
  }
49
41
  //# sourceMappingURL=index.js.map
@@ -4,8 +4,7 @@ import type { WorkbenchContext } from './workbench-context.js';
4
4
  * A composable unit of workbench functionality.
5
5
  *
6
6
  * Built-in modules (projects, session, simulator, popover, settings) are
7
- * implemented as `WorkbenchModule` values; hosts may also inject extra
8
- * modules via `WorkbenchAppConfig.extraModules`.
7
+ * implemented as `WorkbenchModule` values.
9
8
  *
10
9
  * `setup` is invoked once during workbench bootstrap. The returned
11
10
  * Disposable is added to `ctx.registry` so module teardown is symmetric
@@ -6,5 +6,4 @@ export interface SimulatorApiRegistry {
6
6
  clear(): void;
7
7
  }
8
8
  export declare function createSimulatorApiRegistry(): SimulatorApiRegistry;
9
- export declare const simulatorApiRegistry: SimulatorApiRegistry;
10
9
  //# sourceMappingURL=custom-apis.d.ts.map
@@ -22,5 +22,4 @@ export function createSimulatorApiRegistry() {
22
22
  },
23
23
  };
24
24
  }
25
- export const simulatorApiRegistry = createSimulatorApiRegistry();
26
25
  //# sourceMappingURL=custom-apis.js.map
@@ -0,0 +1,23 @@
1
+ import type { ToolbarAction, ToolbarActionInput } from '../../../shared/types.js';
2
+ /**
3
+ * Per-context store for host-registered toolbar actions.
4
+ *
5
+ * The toolbar is modelled as "one table the host recomputes from its current
6
+ * state": `set()` replaces the whole table atomically. The non-serialisable
7
+ * `handler` is kept main-process side; `list()` projects to `{id,label}[]`
8
+ * for the IPC boundary, and `getHandler()` resolves a handler by id.
9
+ */
10
+ export interface ToolbarStore {
11
+ /**
12
+ * Atomically replace the whole toolbar table. Validates that every `id` is
13
+ * unique up-front; on a duplicate it throws WITHOUT mutating the store, so
14
+ * a rejected batch leaves the previous table intact.
15
+ */
16
+ set(actions: ToolbarActionInput[]): void;
17
+ /** Project the current table to `{id,label}[]` — no `handler` leaks out. */
18
+ list(): ToolbarAction[];
19
+ /** Resolve the handler stored under `id`, or `undefined` if unknown. */
20
+ getHandler(id: string): (() => void | Promise<void>) | undefined;
21
+ }
22
+ export declare function createToolbarStore(): ToolbarStore;
23
+ //# sourceMappingURL=toolbar-store.d.ts.map
@@ -0,0 +1,23 @@
1
+ export function createToolbarStore() {
2
+ let actions = [];
3
+ return {
4
+ set(next) {
5
+ const seen = new Set();
6
+ for (const action of next) {
7
+ if (seen.has(action.id)) {
8
+ throw new Error(`Duplicate toolbar action id "${action.id}"`);
9
+ }
10
+ seen.add(action.id);
11
+ }
12
+ // Copy so a later host mutation of the passed array can't reach in.
13
+ actions = next.slice();
14
+ },
15
+ list() {
16
+ return actions.map(({ id, label }) => ({ id, label }));
17
+ },
18
+ getHandler(id) {
19
+ return actions.find((a) => a.id === id)?.handler;
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=toolbar-store.js.map
@@ -9,6 +9,12 @@ export interface ViewManagerContext {
9
9
  rendererDir: string;
10
10
  panels: string[];
11
11
  notify: WorkbenchContext['notify'];
12
+ /**
13
+ * Header bar height in px, used to position overlay views below the header.
14
+ * Optional here so partial test contexts compile; `createWorkbenchContext`
15
+ * always supplies it (default 40).
16
+ */
17
+ headerHeight?: number;
12
18
  }
13
19
  /**
14
20
  * Unified lifecycle manager for Electron WebContentsView overlays.
@@ -12,6 +12,9 @@ import { getDefaultTab } from '../workbench-context.js';
12
12
  * on the context object.
13
13
  */
14
14
  export function createViewManager(ctx) {
15
+ // Resolve once: full WorkbenchContext always provides headerHeight; partial
16
+ // test contexts may omit it, in which case fall back to the default 40.
17
+ const headerHeight = ctx.headerHeight ?? 40;
15
18
  // ── Private mutable state ───────────────────────────────────────────────
16
19
  let simulatorView = null;
17
20
  let simulatorViewAdded = false;
@@ -41,19 +44,19 @@ export function createViewManager(ctx) {
41
44
  if (!simulatorView || ctx.windows.mainWindow.isDestroyed())
42
45
  return;
43
46
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
44
- simulatorView.setBounds(layout.computeSimulatorBounds(w, h, simWidth));
47
+ simulatorView.setBounds(layout.computeSimulatorBounds(w, h, simWidth, headerHeight));
45
48
  }
46
49
  function applySettingsBounds() {
47
50
  if (!settingsView || ctx.windows.mainWindow.isDestroyed())
48
51
  return;
49
52
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
50
- settingsView.setBounds(layout.computeSettingsBounds(w, h));
53
+ settingsView.setBounds(layout.computeSettingsBounds(w, h, headerHeight));
51
54
  }
52
55
  function applyPopoverBounds() {
53
56
  if (!popoverView || ctx.windows.mainWindow.isDestroyed())
54
57
  return;
55
58
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
56
- popoverView.setBounds(layout.computePopoverBounds(w, h));
59
+ popoverView.setBounds(layout.computePopoverBounds(w, h, headerHeight));
57
60
  }
58
61
  // ── ViewManager methods ─────────────────────────────────────────────────
59
62
  function attachSimulator(simWcId, simWidth) {