@dimina-kit/devtools 0.4.0-dev.20260615070430 → 0.4.0-dev.20260616024534

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 (60) hide show
  1. package/README.md +0 -82
  2. package/dist/main/api.d.ts +1 -1
  3. package/dist/main/app/app.d.ts +0 -4
  4. package/dist/main/app/app.js +11 -6
  5. package/dist/main/app/lifecycle.d.ts +1 -0
  6. package/dist/main/app/lifecycle.js +10 -0
  7. package/dist/main/index.bundle.js +111 -143
  8. package/dist/main/ipc/app.d.ts +1 -3
  9. package/dist/main/ipc/app.js +0 -80
  10. package/dist/main/runtime/miniapp-runtime.d.ts +4 -2
  11. package/dist/main/services/notifications/renderer-notifier.d.ts +0 -4
  12. package/dist/main/services/notifications/renderer-notifier.js +1 -7
  13. package/dist/main/services/safe-area/index.d.ts +6 -4
  14. package/dist/main/services/safe-area/index.js +20 -14
  15. package/dist/main/services/simulator-storage/index.js +4 -2
  16. package/dist/main/services/views/view-manager.js +40 -8
  17. package/dist/main/services/workbench-context.d.ts +10 -13
  18. package/dist/main/services/workbench-context.js +1 -4
  19. package/dist/main/services/workspace/workspace-service.js +13 -0
  20. package/dist/main/utils/theme.d.ts +15 -0
  21. package/dist/main/utils/theme.js +34 -1
  22. package/dist/main/windows/main-window/create.d.ts +6 -0
  23. package/dist/main/windows/main-window/create.js +5 -1
  24. package/dist/preload/runtime/native-host.d.ts +3 -0
  25. package/dist/preload/runtime/native-host.js +2 -0
  26. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  27. package/dist/preload/windows/main.cjs +3 -3
  28. package/dist/preload/windows/main.cjs.map +2 -2
  29. package/dist/preload/windows/simulator.cjs +1 -0
  30. package/dist/preload/windows/simulator.cjs.map +2 -2
  31. package/dist/preload/windows/simulator.js +1 -0
  32. package/dist/renderer/assets/index-BFwsvw4p.js +50 -0
  33. package/dist/renderer/assets/{input-J1MQ5fra.js → input-hGF71bn3.js} +2 -2
  34. package/dist/renderer/assets/{ipc-transport-21qDpt9Y.js → ipc-transport-BKWkvwKK.js} +2 -2
  35. package/dist/renderer/assets/ipc-transport-Cg1q1gwD.css +1 -0
  36. package/dist/renderer/assets/{popover-ChIUUEdj.js → popover-NNQNyjgI.js} +2 -2
  37. package/dist/renderer/assets/select-Bb1AKeSq.js +2 -0
  38. package/dist/renderer/assets/{settings-CGea5k_R.js → settings-7uwimev-.js} +2 -2
  39. package/dist/renderer/assets/settings-api-DkrPHroq.js +2 -0
  40. package/dist/renderer/assets/{workbenchSettings-CFrdVOiP.js → workbenchSettings-EaMYvKHG.js} +2 -2
  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 -7
  46. package/dist/shared/ipc-channels.js +7 -6
  47. package/dist/shared/ipc-schemas.d.ts +0 -1
  48. package/dist/shared/ipc-schemas.js +0 -1
  49. package/dist/shared/types.d.ts +17 -61
  50. package/dist/simulator/assets/device-shell-BWX7Yopg.js +2 -0
  51. package/dist/simulator/assets/{device-shell-DsdcNEkt.css → device-shell-BiFE9Otv.css} +1 -1
  52. package/dist/simulator/assets/{simulator-mini-app-CYtW6bMd.js → simulator-mini-app-BfNuDF3R.js} +2 -2
  53. package/dist/simulator/assets/{simulator-cV1PERi6.js → simulator-sf-D0mhw.js} +3 -3
  54. package/dist/simulator/simulator.html +1 -1
  55. package/package.json +4 -4
  56. package/dist/renderer/assets/index-Bx4XqFQ2.js +0 -50
  57. package/dist/renderer/assets/ipc-transport-CVp38I_M.css +0 -1
  58. package/dist/renderer/assets/select-BgWAbMot.js +0 -2
  59. package/dist/renderer/assets/settings-api-Dit4oL6S.js +0 -2
  60. package/dist/simulator/assets/device-shell-BEnc2k-W.js +0 -2
@@ -1,91 +1,11 @@
1
1
  import { AppChannel } from '../../shared/ipc-channels.js';
2
2
  import { IpcRegistry } from '../utils/ipc-registry.js';
3
- function readString(value) {
4
- if (typeof value !== 'string')
5
- return undefined;
6
- const trimmed = value.trim();
7
- return trimmed.length > 0 ? trimmed : undefined;
8
- }
9
- function normalizeHeaderAvatar(value) {
10
- if (!value || typeof value !== 'object')
11
- return null;
12
- const raw = value;
13
- const avatar = {};
14
- const displayName = readString(raw.displayName);
15
- const displayInitial = readString(raw.displayInitial);
16
- const avatarUrl = readString(raw.avatarUrl);
17
- const tooltip = readString(raw.tooltip);
18
- if (displayName)
19
- avatar.displayName = displayName;
20
- if (displayInitial)
21
- avatar.displayInitial = displayInitial;
22
- if (avatarUrl)
23
- avatar.avatarUrl = avatarUrl;
24
- if (tooltip)
25
- avatar.tooltip = tooltip;
26
- return avatar.displayName || avatar.displayInitial || avatar.avatarUrl ? avatar : null;
27
- }
28
- function readPlacement(value) {
29
- return value === 'left' || value === 'center' || value === 'right' ? value : undefined;
30
- }
31
- function normalizeHeaderAction(value) {
32
- if (!value || typeof value !== 'object')
33
- return null;
34
- const raw = value;
35
- const id = readString(raw.id);
36
- const label = readString(raw.label);
37
- if (!id || !label)
38
- return null;
39
- const action = { id, label };
40
- const tooltip = readString(raw.tooltip);
41
- const placement = readPlacement(raw.placement);
42
- if (tooltip)
43
- action.tooltip = tooltip;
44
- if (placement)
45
- action.placement = placement;
46
- if (typeof raw.disabled === 'boolean')
47
- action.disabled = raw.disabled;
48
- return action;
49
- }
50
- function normalizeHeaderActions(value) {
51
- if (!Array.isArray(value))
52
- return [];
53
- const actions = [];
54
- const seen = new Set();
55
- for (const item of value) {
56
- const action = normalizeHeaderAction(item);
57
- if (!action || seen.has(action.id))
58
- continue;
59
- seen.add(action.id);
60
- actions.push(action);
61
- }
62
- return actions.slice(0, 8);
63
- }
64
3
  export function registerAppIpc(ctx) {
65
4
  return new IpcRegistry(ctx.senderPolicy)
66
5
  .handle(AppChannel.GetBranding, async () => {
67
6
  if (ctx.brandingProvider)
68
7
  return ctx.brandingProvider();
69
8
  return { appName: ctx.appName };
70
- })
71
- .handle(AppChannel.GetHeaderAvatar, async () => {
72
- if (!ctx.headerAvatarProvider)
73
- return null;
74
- return normalizeHeaderAvatar(await ctx.headerAvatarProvider());
75
- })
76
- .handle(AppChannel.InvokeHeaderAvatar, async () => {
77
- await ctx.headerAvatarActionHandler?.();
78
- })
79
- .handle(AppChannel.GetHeaderActions, async () => {
80
- if (!ctx.headerActionsProvider)
81
- return [];
82
- return normalizeHeaderActions(await ctx.headerActionsProvider());
83
- })
84
- .handle(AppChannel.InvokeHeaderAction, async (_event, id) => {
85
- const actionId = readString(id);
86
- if (!actionId)
87
- return;
88
- await ctx.headerActionHandler?.(actionId);
89
9
  });
90
10
  }
91
11
  //# sourceMappingURL=app.js.map
@@ -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
@@ -65,10 +65,6 @@ export interface RendererNotifier {
65
65
  * placeholder div resizes (closes the host-toolbar dynamic-height loop).
66
66
  */
67
67
  hostToolbarHeightChanged(height: number): void;
68
- /** Ask the main renderer to re-fetch the host-provided header avatar DTO. */
69
- headerAvatarChanged(): void;
70
- /** Ask the main renderer to re-fetch the host-provided header actions. */
71
- headerActionsChanged(): void;
72
68
  /**
73
69
  * Ask the main renderer's Monaco editor to open a project file at a position.
74
70
  * Drives the "click a console file link → open in editor" pipeline.
@@ -1,4 +1,4 @@
1
- import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchSettingsChannel, EditorChannel, ViewChannel, AppChannel, } from '../../../shared/ipc-channels.js';
1
+ import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchSettingsChannel, EditorChannel, ViewChannel, } from '../../../shared/ipc-channels.js';
2
2
  /** Safely resolve a WebContents, skipping destroyed / missing targets. */
3
3
  function liveWebContents(wc) {
4
4
  if (!wc)
@@ -39,12 +39,6 @@ export function createRendererNotifier(ctx) {
39
39
  hostToolbarHeightChanged(height) {
40
40
  sendToMain(ViewChannel.HostToolbarHeightChanged, height);
41
41
  },
42
- headerAvatarChanged() {
43
- sendToMain(AppChannel.HeaderAvatarChanged);
44
- },
45
- headerActionsChanged() {
46
- sendToMain(AppChannel.HeaderActionsChanged);
47
- },
48
42
  editorOpenFile(payload) {
49
43
  sendToMain(EditorChannel.OpenFile, payload);
50
44
  },
@@ -3,10 +3,12 @@ import type { ConnectionRegistry } from '@dimina-kit/electron-deck/main';
3
3
  import type { NativeDeviceInfo } from '../../../shared/ipc-channels.js';
4
4
  export interface SafeAreaController {
5
5
  /** Attach the debugger to a freshly-attached render-host guest and push the
6
- * current device's insets. No-op (warn) if the guest is already claimed by an
7
- * external CDP client env then stays 0 rather than throwing. */
8
- applyToGuest(guestWc: WebContents, device: NativeDeviceInfo | null): void;
9
- /** Re-push insets to every still-attached guest after a device change. */
6
+ * current device's insets. `isTabPage` selects the bottom-inset policy (0 for
7
+ * tab pages, the real inset for full-bleed non-tab pages). No-op (warn) if the
8
+ * guest is already claimed by an external CDP client — env then stays 0. */
9
+ applyToGuest(guestWc: WebContents, device: NativeDeviceInfo | null, isTabPage: boolean): void;
10
+ /** Re-push insets to every still-attached guest after a device change (each
11
+ * guest keeps the page type it attached with). */
10
12
  reapplyAll(device: NativeDeviceInfo | null): void;
11
13
  /** Detach from all guests (teardown). */
12
14
  dispose(): void;
@@ -1,25 +1,31 @@
1
- function guestInsets(device) {
1
+ function guestInsets(device, isTabPage) {
2
2
  const top = device?.safeAreaInsets.top ?? 0;
3
- return { top, topMax: top, right: 0, rightMax: 0, bottom: 0, bottomMax: 0, left: 0, leftMax: 0 };
3
+ // A tab page's content sits above the shell-drawn tabBar (which fills the
4
+ // bottom safe area), so it never borders the bottom unsafe zone. A non-tab
5
+ // page is full-bleed to the device bottom, so surface the real inset for its
6
+ // own `env(safe-area-inset-bottom)` opt-in.
7
+ const bottom = isTabPage ? 0 : (device?.safeAreaInsets.bottom ?? 0);
8
+ return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
4
9
  }
5
10
  export function createSafeAreaController(options = {}) {
6
- // Guests we successfully attached `wc.debugger` to (so we don't re-attach,
7
- // which throws). Pruned on guest destroy.
8
- const attached = new Set();
9
- function override(wc, device) {
11
+ // Guests we successfully attached `wc.debugger` to (value = the page's
12
+ // `isTabPage`, fixed for the guest's life — it's one page). So we don't
13
+ // re-attach (throws), and a device-change reapply reuses the same policy.
14
+ const attached = new Map();
15
+ function override(wc, device, isTabPage) {
10
16
  if (wc.isDestroyed())
11
17
  return;
12
18
  void wc.debugger
13
- .sendCommand('Emulation.setSafeAreaInsetsOverride', { insets: guestInsets(device) })
19
+ .sendCommand('Emulation.setSafeAreaInsetsOverride', { insets: guestInsets(device, isTabPage) })
14
20
  .catch((err) => {
15
21
  console.warn('[safe-area] setSafeAreaInsetsOverride failed:', err instanceof Error ? err.message : err);
16
22
  });
17
23
  }
18
24
  return {
19
- applyToGuest: (wc, device) => {
25
+ applyToGuest: (wc, device, isTabPage) => {
20
26
  if (!wc || wc.isDestroyed() || attached.has(wc)) {
21
27
  if (wc && !wc.isDestroyed() && attached.has(wc))
22
- override(wc, device);
28
+ override(wc, device, isTabPage);
23
29
  return;
24
30
  }
25
31
  try {
@@ -31,21 +37,21 @@ export function createSafeAreaController(options = {}) {
31
37
  console.warn('[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:', err instanceof Error ? err.message : err);
32
38
  return;
33
39
  }
34
- attached.add(wc);
40
+ attached.set(wc, isTabPage);
35
41
  if (options.connections) {
36
42
  options.connections.acquire(wc).own(() => attached.delete(wc));
37
43
  }
38
44
  else {
39
45
  wc.once('destroyed', () => attached.delete(wc));
40
46
  }
41
- override(wc, device);
47
+ override(wc, device, isTabPage);
42
48
  },
43
49
  reapplyAll: (device) => {
44
- for (const wc of attached)
45
- override(wc, device);
50
+ for (const [wc, isTabPage] of attached)
51
+ override(wc, device, isTabPage);
46
52
  },
47
53
  dispose: () => {
48
- for (const wc of attached) {
54
+ for (const wc of attached.keys()) {
49
55
  try {
50
56
  if (!wc.isDestroyed())
51
57
  wc.debugger.detach();
@@ -403,8 +403,10 @@ export function setupSimulatorStorage(host, options) {
403
403
  const filtered = prefix
404
404
  ? result.entries.filter(([key]) => key.startsWith(prefix))
405
405
  : result.entries;
406
- // Keys are returned with their `${appId}_` prefix intact; stripping is
407
- // a follow-up so the panel UI can stay compatible with the raw keys.
406
+ // Keys are returned with their `${appId}_` prefix intact: every write
407
+ // path (Set/Remove) keys off the raw storage key. The Storage panel
408
+ // display-strips the prefix for readability (storage-panel.tsx), so the
409
+ // wire keys stay raw while the UI shows clean, Chrome-like keys.
408
410
  return filtered.map(([key, value]) => ({ key, value }));
409
411
  });
410
412
  }
@@ -1,6 +1,7 @@
1
- import { ipcMain, shell, WebContentsView, webContents } from 'electron';
1
+ import { ipcMain, nativeTheme, shell, WebContentsView, webContents } from 'electron';
2
2
  import path from 'path';
3
3
  import { cjsSiblingPreloadPath, mainPreloadPath } from '../../utils/paths.js';
4
+ import { simDeskBg } from '../../utils/theme.js';
4
5
  import { applyNavigationHardening, handleWindowOpenExternal, } from '../../windows/navigation-hardening.js';
5
6
  import { SimulatorCustomApiBridgeChannel } from '../../../shared/ipc-channels.js';
6
7
  import { OPEN_IN_EDITOR_SCHEME, decodeOpenInEditorUrl, resourceUrlToProjectRelativePath, } from '../../../shared/open-in-editor.js';
@@ -806,12 +807,27 @@ export function createViewManager(ctx) {
806
807
  },
807
808
  });
808
809
  nativeSimulatorView = view;
809
- // Paint the WCV surface the simulator-panel background ( --color-sim-bg
810
- // hsl(0 0% 7%)) so a height-resize that grows the region never flashes white
811
- // in the newly-exposed strip before DeviceShell's desk repaints — the WCV,
812
- // the desk, and the renderer placeholder behind it are all the same color.
813
- view.setBackgroundColor('#121212');
810
+ // Paint the WCV surface the themed desk color (simDeskBg(): dark #121212 /
811
+ // light #e8e8e8) so a height-resize that grows the region never flashes a
812
+ // mismatched strip before DeviceShell's desk repaints — the WCV, the desk,
813
+ // and the renderer placeholder behind it are all the same color.
814
+ view.setBackgroundColor(simDeskBg());
814
815
  const simWc = view.webContents;
816
+ // Keep the WCV surface in sync with the active color scheme. The
817
+ // process-wide installThemeBackgroundSync() re-syncs BrowserWindows on a
818
+ // theme switch, but this top-level WebContentsView is not a window, so its
819
+ // creation-time backgroundColor would otherwise freeze. Mirror simDeskBg()
820
+ // here on every nativeTheme `updated`; the listener is owned by the wc's
821
+ // connection so it detaches when the simulator view is torn down.
822
+ const syncDeskBg = () => {
823
+ try {
824
+ if (!simWc.isDestroyed())
825
+ view.setBackgroundColor(simDeskBg());
826
+ }
827
+ catch { /* view/wc gone */ }
828
+ };
829
+ nativeTheme.on('updated', syncDeskBg);
830
+ ctx.connections.acquire(simWc).own(() => nativeTheme.removeListener('updated', syncDeskBg));
815
831
  // Service the simulator-side `__diminaCustomApis` bridge: this top-level
816
832
  // WebContentsView has no embedder renderer to proxy through, so dispatch its
817
833
  // `sendToHost` requests straight to `ctx.simulatorApis` from main.
@@ -822,12 +838,23 @@ export function createViewManager(ctx) {
822
838
  // them with contextIsolation/sandbox off so the render runtime + its preload
823
839
  // share the page realm. (A top-level WebContentsView can host these guests; a
824
840
  // `<webview>` guest cannot — that's the whole point of Option A.)
841
+ // Page type (`isTab`) of each attaching guest, captured from its render-host
842
+ // URL in will-attach (where `params.src` carries the full URL) and consumed
843
+ // FIFO in the matching did-attach — `guestWc.getURL()` is still empty there.
844
+ // Per-attach scope: a fresh simWc + handlers are built on every (re)attach.
845
+ const pendingGuestIsTab = [];
825
846
  simWc.on('will-attach-webview', (_event, webPreferences, params) => {
826
847
  ;
827
848
  webPreferences.partition = partition;
828
849
  params.partition = partition;
829
850
  webPreferences.contextIsolation = false;
830
851
  webPreferences.sandbox = false;
852
+ let isTab = false;
853
+ try {
854
+ isTab = new URL(params.src).searchParams.get('isTab') === '1';
855
+ }
856
+ catch { /* keep false */ }
857
+ pendingGuestIsTab.push(isTab);
831
858
  });
832
859
  simWc.on('did-attach-webview', (_event, guestWc) => {
833
860
  // Scale the nested render-host page with the device zoom. The host WCV is
@@ -841,8 +868,13 @@ export function createViewManager(ctx) {
841
868
  }
842
869
  catch { /* guest not ready; setNativeSimulatorViewBounds re-applies */ }
843
870
  // Simulate this device's CSS env(safe-area-inset-*) on the fresh guest
844
- // before it paints, so notch-aware page layout resolves correctly.
845
- safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null);
871
+ // before it paints, so notch-aware page layout resolves correctly. The
872
+ // bottom inset is page-type-dependent (see services/safe-area): a tab
873
+ // page's content sits above the shell tabBar (bottom 0); a non-tab page
874
+ // is full-bleed (real bottom inset). The page type was captured from the
875
+ // render-host URL in will-attach (FIFO).
876
+ const isTabGuest = pendingGuestIsTab.shift() ?? false;
877
+ safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null, isTabGuest);
846
878
  guestWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
847
879
  guestWc.on('will-navigate', (e, url) => {
848
880
  try {
@@ -1,5 +1,5 @@
1
1
  import type { BrowserWindow } from 'electron';
2
- import type { CompilationAdapter, HeaderActionsProvider, HeaderAvatarProvider, WorkbenchConfig } from '../../shared/types.js';
2
+ import type { CompilationAdapter, WorkbenchConfig } from '../../shared/types.js';
3
3
  import type { BridgeRouterHandle } from '../ipc/bridge-router.js';
4
4
  import type { ConsoleForwarder } from './console-forward/index.js';
5
5
  import type { NetworkForwarder } from './network-forward/index.js';
@@ -35,14 +35,6 @@ export interface WorkbenchContext {
35
35
  }> | {
36
36
  appName: string;
37
37
  };
38
- /** Host-injected profile provider for the built-in header avatar slot. */
39
- headerAvatarProvider?: HeaderAvatarProvider;
40
- /** Host-injected click handler for the built-in header avatar slot. */
41
- headerAvatarActionHandler?: () => void | Promise<void>;
42
- /** Host-injected compact action provider for the built-in project header. */
43
- headerActionsProvider?: HeaderActionsProvider;
44
- /** Host-injected click handler for the built-in project header actions. */
45
- headerActionHandler?: (id: string) => void | Promise<void>;
46
38
  /** Unified lifecycle manager for all overlay WebContentsViews */
47
39
  views: ViewManager;
48
40
  /** Owns BrowserWindow instances (main + settings) and their sender-id checks. */
@@ -82,6 +74,13 @@ export interface WorkbenchContext {
82
74
  parentWindow: BrowserWindow;
83
75
  templates: ProjectTemplate[];
84
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>;
85
84
  /**
86
85
  * Trust predicate consulted by `IpcRegistry` for every incoming IPC.
87
86
  * Resolves the currently-trusted senders (main renderer + overlays)
@@ -183,10 +182,6 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
183
182
  preloadPath: string;
184
183
  rendererDir: string;
185
184
  brandingProvider?: WorkbenchContext['brandingProvider'];
186
- headerAvatarProvider?: WorkbenchContext['headerAvatarProvider'];
187
- headerAvatarActionHandler?: WorkbenchContext['headerAvatarActionHandler'];
188
- headerActionsProvider?: WorkbenchContext['headerActionsProvider'];
189
- headerActionHandler?: WorkbenchContext['headerActionHandler'];
190
185
  /** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
191
186
  projectsProvider?: ProjectsProvider;
192
187
  /** Templates injected by the host; same-id overrides a built-in. */
@@ -195,6 +190,8 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
195
190
  builtinTemplates?: 'all' | 'none' | readonly string[];
196
191
  /** Host-supplied "新建项目" dialog hook. */
197
192
  customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
193
+ /** Host permission gate run before a project opens (throw to veto). */
194
+ onBeforeOpenProject?: WorkbenchContext['onBeforeOpenProject'];
198
195
  }
199
196
  export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
200
197
  export { sanitizeTemplates };
@@ -18,10 +18,6 @@ export function createWorkbenchContext(opts) {
18
18
  apiNamespaces: opts.apiNamespaces ?? [],
19
19
  appName: opts.appName ?? 'Dimina DevTools',
20
20
  brandingProvider: opts.brandingProvider,
21
- headerAvatarProvider: opts.headerAvatarProvider,
22
- headerAvatarActionHandler: opts.headerAvatarActionHandler,
23
- headerActionsProvider: opts.headerActionsProvider,
24
- headerActionHandler: opts.headerActionHandler,
25
21
  };
26
22
  ctx.registry = new DisposableRegistry();
27
23
  // Empty connection registry as a first-class context field. Connections are
@@ -41,6 +37,7 @@ export function createWorkbenchContext(opts) {
41
37
  ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
42
38
  ctx.projectTemplates = resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
43
39
  ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
40
+ ctx.onBeforeOpenProject = opts.onBeforeOpenProject;
44
41
  ctx.workspace = createWorkspaceService(ctx);
45
42
  ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
46
43
  return ctx;
@@ -80,6 +80,19 @@ 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
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
94
+ }
95
+ }
83
96
  clearSimulatorServicewechatReferer();
84
97
  // Invalidate the outgoing session's onLog BEFORE teardown starts (same
85
98
  // order as closeProject below): the dying compile worker flushes
@@ -8,6 +8,21 @@ import { type Disposable } from '@dimina-kit/electron-deck/main';
8
8
  * Light: hsl(0 0% 98%) ≈ #fafafa (--color-bg)
9
9
  */
10
10
  export declare function themeBg(): string;
11
+ /**
12
+ * Backdrop ("desk") color for the native simulator WebContentsView — the
13
+ * surface the simulated phone sits on. Unlike {@link themeBg} (the window bg),
14
+ * the desk is a neutral grey kept a touch off the window so the light-colored
15
+ * phone keeps contrast against it in BOTH schemes.
16
+ *
17
+ * Dark: hsl(0 0% 7%) ≈ #121212 (the long-standing desk color — unchanged)
18
+ * Light: hsl(0 0% 91%) ≈ #e8e8e8 (neutral grey; the white phone reads on it)
19
+ *
20
+ * MUST stay equal to the renderer's `--color-sim-bg` (design.css) and the
21
+ * simulator page's `.device-shell-root` background (device-shell.css): the WCV,
22
+ * the desk, and the placeholder behind it are the same color so a height-resize
23
+ * never flashes a mismatched strip. Update all three together.
24
+ */
25
+ export declare function simDeskBg(): string;
11
26
  /**
12
27
  * Install a single process-wide listener that keeps every window's native
13
28
  * `backgroundColor` in sync with the active color scheme.
@@ -1,5 +1,6 @@
1
1
  import { BrowserWindow, nativeTheme } from 'electron';
2
2
  import { toDisposable } from '@dimina-kit/electron-deck/main';
3
+ import { WorkbenchSettingsChannel } from '../../shared/ipc-channels.js';
3
4
  /**
4
5
  * Background color that matches the current system color scheme.
5
6
  * Pass as `backgroundColor` when creating BrowserWindows to prevent
@@ -11,6 +12,23 @@ import { toDisposable } from '@dimina-kit/electron-deck/main';
11
12
  export function themeBg() {
12
13
  return nativeTheme.shouldUseDarkColors ? '#1a1a1a' : '#fafafa';
13
14
  }
15
+ /**
16
+ * Backdrop ("desk") color for the native simulator WebContentsView — the
17
+ * surface the simulated phone sits on. Unlike {@link themeBg} (the window bg),
18
+ * the desk is a neutral grey kept a touch off the window so the light-colored
19
+ * phone keeps contrast against it in BOTH schemes.
20
+ *
21
+ * Dark: hsl(0 0% 7%) ≈ #121212 (the long-standing desk color — unchanged)
22
+ * Light: hsl(0 0% 91%) ≈ #e8e8e8 (neutral grey; the white phone reads on it)
23
+ *
24
+ * MUST stay equal to the renderer's `--color-sim-bg` (design.css) and the
25
+ * simulator page's `.device-shell-root` background (device-shell.css): the WCV,
26
+ * the desk, and the placeholder behind it are the same color so a height-resize
27
+ * never flashes a mismatched strip. Update all three together.
28
+ */
29
+ export function simDeskBg() {
30
+ return nativeTheme.shouldUseDarkColors ? '#121212' : '#e8e8e8';
31
+ }
14
32
  /**
15
33
  * Install a single process-wide listener that keeps every window's native
16
34
  * `backgroundColor` in sync with the active color scheme.
@@ -36,10 +54,25 @@ export function themeBg() {
36
54
  export function installThemeBackgroundSync() {
37
55
  const apply = () => {
38
56
  const bg = themeBg();
57
+ const isDark = nativeTheme.shouldUseDarkColors;
39
58
  for (const win of BrowserWindow.getAllWindows()) {
40
- if (!win.isDestroyed()) {
59
+ if (win.isDestroyed())
60
+ continue;
61
+ // Isolate each window: a window closing mid-loop (or a renderer torn down
62
+ // between the isDestroyed() check and the send) must not abort the sync
63
+ // for the remaining windows.
64
+ try {
41
65
  win.setBackgroundColor(bg);
66
+ // Notify renderer JS consumers that can't observe the CSS
67
+ // `prefers-color-scheme` change (Monaco's theme). Electron does not
68
+ // dispatch the renderer's matchMedia change event for programmatic
69
+ // `nativeTheme.themeSource` flips, so push it from here — the one place
70
+ // that already centralizes color-scheme reactions.
71
+ if (!win.webContents.isDestroyed()) {
72
+ win.webContents.send(WorkbenchSettingsChannel.ThemeChanged, isDark);
73
+ }
42
74
  }
75
+ catch { /* window/webContents gone mid-loop */ }
43
76
  }
44
77
  };
45
78
  nativeTheme.on('updated', apply);
@@ -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
@@ -26,7 +26,11 @@ export function createMainWindow(opts) {
26
26
  mainWindow.showInactive();
27
27
  }
28
28
  else {
29
- mainWindow.show();
29
+ // A login-gating host opts out via `autoShow: false` and shows the
30
+ // window itself once auth passes — don't flash an un-authed window.
31
+ if (opts.autoShow !== false) {
32
+ mainWindow.show();
33
+ }
30
34
  // Don't auto-open a detached DevTools for the devtools UI shell itself —
31
35
  // it's noise for normal use (the mini-app's Console lives in the embedded
32
36
  // right-panel DevTools, not here). Opt in via env for debugging the shell.
@@ -4,6 +4,9 @@ export interface RenderHostUrlOptions {
4
4
  bridgeId: string;
5
5
  appId: string;
6
6
  pagePath: string;
7
+ /** Whether this page is a tabBar page. Surfaced on the URL so main can pick
8
+ * the bottom safe-area policy at `did-attach-webview` (services/safe-area). */
9
+ isTab?: boolean;
7
10
  }
8
11
  export interface DiminaNativeHostBridge {
9
12
  enabled: boolean;
@@ -55,6 +55,8 @@ function buildBridge(cfg) {
55
55
  url.searchParams.set('bridgeId', opts.bridgeId);
56
56
  url.searchParams.set('appId', opts.appId);
57
57
  url.searchParams.set('pagePath', opts.pagePath);
58
+ if (opts.isTab)
59
+ url.searchParams.set('isTab', '1');
58
60
  return url.toString();
59
61
  },
60
62
  renderPreloadUrl: cfg.renderPreloadUrl,