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

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 +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 +0 -6
  5. package/dist/main/index.bundle.js +51 -101
  6. package/dist/main/ipc/app.d.ts +1 -3
  7. package/dist/main/ipc/app.js +0 -80
  8. package/dist/main/services/notifications/renderer-notifier.d.ts +0 -4
  9. package/dist/main/services/notifications/renderer-notifier.js +1 -7
  10. package/dist/main/services/safe-area/index.d.ts +6 -4
  11. package/dist/main/services/safe-area/index.js +20 -14
  12. package/dist/main/services/simulator-storage/index.js +4 -2
  13. package/dist/main/services/views/view-manager.js +40 -8
  14. package/dist/main/services/workbench-context.d.ts +1 -13
  15. package/dist/main/services/workbench-context.js +0 -4
  16. package/dist/main/utils/theme.d.ts +15 -0
  17. package/dist/main/utils/theme.js +34 -1
  18. package/dist/preload/runtime/native-host.d.ts +3 -0
  19. package/dist/preload/runtime/native-host.js +2 -0
  20. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  21. package/dist/preload/windows/main.cjs +3 -3
  22. package/dist/preload/windows/main.cjs.map +2 -2
  23. package/dist/preload/windows/simulator.cjs +1 -0
  24. package/dist/preload/windows/simulator.cjs.map +2 -2
  25. package/dist/preload/windows/simulator.js +1 -0
  26. package/dist/renderer/assets/index-BFwsvw4p.js +50 -0
  27. package/dist/renderer/assets/{input-J1MQ5fra.js → input-hGF71bn3.js} +2 -2
  28. package/dist/renderer/assets/{ipc-transport-21qDpt9Y.js → ipc-transport-BKWkvwKK.js} +2 -2
  29. package/dist/renderer/assets/ipc-transport-Cg1q1gwD.css +1 -0
  30. package/dist/renderer/assets/{popover-ChIUUEdj.js → popover-NNQNyjgI.js} +2 -2
  31. package/dist/renderer/assets/select-Bb1AKeSq.js +2 -0
  32. package/dist/renderer/assets/{settings-CGea5k_R.js → settings-7uwimev-.js} +2 -2
  33. package/dist/renderer/assets/settings-api-DkrPHroq.js +2 -0
  34. package/dist/renderer/assets/{workbenchSettings-CFrdVOiP.js → workbenchSettings-EaMYvKHG.js} +2 -2
  35. package/dist/renderer/entries/main/index.html +6 -6
  36. package/dist/renderer/entries/popover/index.html +5 -5
  37. package/dist/renderer/entries/settings/index.html +5 -5
  38. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  39. package/dist/shared/ipc-channels.d.ts +1 -7
  40. package/dist/shared/ipc-channels.js +7 -6
  41. package/dist/shared/ipc-schemas.d.ts +0 -1
  42. package/dist/shared/ipc-schemas.js +0 -1
  43. package/dist/shared/types.d.ts +0 -61
  44. package/dist/simulator/assets/device-shell-BWX7Yopg.js +2 -0
  45. package/dist/simulator/assets/{device-shell-DsdcNEkt.css → device-shell-BiFE9Otv.css} +1 -1
  46. package/dist/simulator/assets/{simulator-mini-app-CYtW6bMd.js → simulator-mini-app-BfNuDF3R.js} +2 -2
  47. package/dist/simulator/assets/{simulator-cV1PERi6.js → simulator-sf-D0mhw.js} +3 -3
  48. package/dist/simulator/simulator.html +1 -1
  49. package/package.json +4 -4
  50. package/dist/renderer/assets/index-Bx4XqFQ2.js +0 -50
  51. package/dist/renderer/assets/ipc-transport-CVp38I_M.css +0 -1
  52. package/dist/renderer/assets/select-BgWAbMot.js +0 -2
  53. package/dist/renderer/assets/settings-api-Dit4oL6S.js +0 -2
  54. package/dist/simulator/assets/device-shell-BEnc2k-W.js +0 -2
package/README.md CHANGED
@@ -42,12 +42,6 @@ launch({
42
42
  rendererDir,
43
43
  apiNamespaces: ['my'],
44
44
  brandingProvider: () => ({ appName: '我的开发工具' }),
45
- headerAvatarProvider: () => {
46
- const user = getCurrentUser()
47
- return user
48
- ? { displayName: user.name, displayInitial: user.name, avatarUrl: user.avatarUrl }
49
- : null
50
- },
51
45
  icon: '/path/to/icon.png',
52
46
  menuBuilder: (mainWindow, menuCtx) => {
53
47
  // menuCtx 是 MenuContext(只读 menu 相关状态)
@@ -187,10 +181,6 @@ src/
187
181
  | `preloadPath` | `string` | 内置 | 统一的 host 级 preload 入口;native-host simulator(WCV)自动跑其 `.cjs` sibling(`cjsSiblingPreloadPath`) |
188
182
  | `apiNamespaces` | `string[]` | `[]` | 自定义 API 命名空间(如 `['qd']`) |
189
183
  | `brandingProvider` | `() => { appName }` | — | 品牌信息 provider |
190
- | `headerAvatarProvider` | `() => HeaderAvatarInfo \| null \| Promise<HeaderAvatarInfo \| null>` | — | 内置 header 头像 slot 的宿主数据源;返回 `null` / `undefined` 时隐藏 |
191
- | `headerAvatarActionHandler` | `() => void \| Promise<void>` | — | 内置 header 头像 slot 的点击处理;未提供时点击无业务副作用 |
192
- | `headerActionsProvider` | `() => HeaderActionInfo[] \| null \| Promise<HeaderActionInfo[] \| null>` | — | 内置 header 三栏里的少量宿主命令按钮 |
193
- | `headerActionHandler` | `(id: string) => void \| Promise<void>` | — | 处理 `headerActionsProvider` 返回按钮的点击 |
194
184
  | `headerHeight` | `number` | — | **已废弃,运行时忽略**:头部栏恒为 40px(`HEADER_H`);需要自定义工具栏请用 host toolbar WCV |
195
185
 
196
186
  ### WorkbenchAppConfig(扩展 WorkbenchConfig)
@@ -218,78 +208,6 @@ src/
218
208
  | `instance.registerSimulatorApi(name, handler)` | 注册 simulator 自定义 API,小程序里 `wx.<name>()` 调用(详见下方"Simulator 自定义 API")。返回 `Disposable` |
219
209
  | `instance.ipc` | `IpcRegistry` 实例,`instance.ipc.handle(channel, fn)` 注册自定义 IPC;已绑定 `senderPolicy` 网关 |
220
210
  | `instance.registerTrustedWindow(win)` | 把 host 自有弹窗 `BrowserWindow` 加入受信 sender 集,否则其发起的 `instance.ipc` 调用会被网关拒绝。窗口关闭即移除 |
221
- | `instance.refreshHeaderAvatar()` | 通知 main renderer 重新调用 `headerAvatarProvider`,用于宿主登录态 / 头像变更后刷新内置 header 头像 |
222
- | `instance.refreshHeaderActions()` | 通知 main renderer 重新调用 `headerActionsProvider`,用于宿主 action 文案 / disabled 状态变化后刷新 |
223
-
224
- ### Header Avatar
225
-
226
- `headerAvatarProvider` 是内置 project header 头像 slot 的唯一数据源。devtools 会通过 `app:getHeaderAvatar` 拉取一个可序列化 DTO 并在固定 header toolbar 内渲染;宿主不需要、也不应该依赖 renderer 内部组件。
227
-
228
- 该 slot 只承载当前账号 / 身份入口,不是通用业务按钮扩展点。少量必须贴合内置 header 三栏布局的命令按钮使用下方的 `headerActionsProvider`;完整自定义工具栏、复杂状态展示、表单/筛选器、可变高度内容仍应使用 host toolbar WCV(`instance.context.views.hostToolbar.loadURL/loadFile` + `send/onMessage`),由宿主拥有内容、样式和高度。
229
-
230
- ```typescript
231
- launch({
232
- headerAvatarProvider: () => {
233
- const user = qdmpSession.currentUser()
234
- return user
235
- ? {
236
- displayName: user.nickname,
237
- displayInitial: user.nickname,
238
- avatarUrl: user.avatarUrl,
239
- tooltip: user.nickname,
240
- }
241
- : null
242
- },
243
- onSetup(instance) {
244
- qdmpSession.onUserChanged(() => instance.refreshHeaderAvatar())
245
- },
246
- })
247
- ```
248
-
249
- `HeaderAvatarInfo` 字段:
250
-
251
- | 字段 | 说明 |
252
- | --- | --- |
253
- | `displayName` | 用户名;用于 tooltip / aria 文本和 fallback 首字 |
254
- | `displayInitial` | 可选 fallback 文本;头像图片缺失或加载失败时取首字显示 |
255
- | `avatarUrl` | 头像图片 URL 或 data URL |
256
- | `tooltip` | 可选 tooltip 覆盖;默认用 `displayName` |
257
-
258
- ### Header Actions
259
-
260
- `headerActionsProvider` 用于把少量宿主命令渲染到内置 project header 的三栏中。它不是 host toolbar WCV 的替代品:只适合 1-4 个短文本命令,且按钮高度固定、宽度受 header 约束。
261
-
262
- placement 对应内置 header 的三个区域:
263
-
264
- | placement | 渲染位置 |
265
- | --- | --- |
266
- | `left` | 左区最右侧,位于头像 / 面板显隐 controls 之后 |
267
- | `center` | 中区最右侧,位于编译模式 / 重新编译 / 编译状态之后 |
268
- | `right` | 右区最左侧,位于布局 controls / 设置按钮之前 |
269
-
270
- ```typescript
271
- launch({
272
- headerActionsProvider: () => [
273
- { id: 'open', label: '打开', placement: 'left' },
274
- { id: 'bindApp', label: '绑定应用', placement: 'left' },
275
- { id: 'preview', label: '真机预览', placement: 'center' },
276
- { id: 'upload', label: '上传', placement: 'right' },
277
- ],
278
- headerActionHandler: async (id) => {
279
- await commands.run(id)
280
- },
281
- })
282
- ```
283
-
284
- `HeaderActionInfo` 字段:
285
-
286
- | 字段 | 说明 |
287
- | --- | --- |
288
- | `id` | 稳定 action id;点击时传给 `headerActionHandler` |
289
- | `label` | 短按钮文案 |
290
- | `tooltip` | 可选 tooltip 覆盖;默认用 `label` |
291
- | `placement` | `left` / `center` / `right`;默认 `right` |
292
- | `disabled` | 可选禁用态 |
293
211
 
294
212
  ### 内置面板 ID
295
213
 
@@ -16,5 +16,5 @@ export { IpcRegistry } from './utils/ipc-registry.js';
16
16
  export type { SenderPolicy } from './utils/ipc-registry.js';
17
17
  export { UpdateManager, createGitHubReleaseChecker } from './services/update/index.js';
18
18
  export type { UpdateManagerOptions, GitHubReleaseCheckerOptions, PickAssetContext, VersionScheme, } from './services/update/index.js';
19
- export type { WorkbenchConfig, WorkbenchAppConfig, CompilationAdapter, ProjectSession, HeaderAvatarInfo, HeaderAvatarProvider, HeaderActionInfo, HeaderActionPlacement, HeaderActionsProvider, BuiltinPanelId, BuiltinModuleId, UpdateChecker, UpdateInfo, } from '../shared/types.js';
19
+ export type { WorkbenchConfig, WorkbenchAppConfig, CompilationAdapter, ProjectSession, BuiltinPanelId, BuiltinModuleId, UpdateChecker, UpdateInfo, } from '../shared/types.js';
20
20
  //# sourceMappingURL=api.d.ts.map
@@ -15,10 +15,6 @@ export interface WorkbenchAppInstance {
15
15
  registerTrustedWindow: (win: BrowserWindow) => Disposable;
16
16
  /** Registers a simulator custom API into this context's registry. */
17
17
  registerSimulatorApi: (name: string, handler: SimulatorApiHandler) => Disposable;
18
- /** Ask the main renderer to re-read the header avatar provider. */
19
- refreshHeaderAvatar: () => void;
20
- /** Ask the main renderer to re-read the header actions provider. */
21
- refreshHeaderActions: () => void;
22
18
  automationServer?: AutomationServer;
23
19
  updateManager?: UpdateManager;
24
20
  dispose: () => Promise<void>;
@@ -149,10 +149,6 @@ function createContext(config, mainWindow, rendererDir) {
149
149
  appName: config.appName,
150
150
  apiNamespaces: config.apiNamespaces,
151
151
  brandingProvider: config.brandingProvider,
152
- headerAvatarProvider: config.headerAvatarProvider,
153
- headerAvatarActionHandler: config.headerAvatarActionHandler,
154
- headerActionsProvider: config.headerActionsProvider,
155
- headerActionHandler: config.headerActionHandler,
156
152
  // The host-supplied ProjectsProvider / template types in `shared/types`
157
153
  // are structurally compatible with the main-process equivalents —
158
154
  // these casts are safe; we re-narrow at the workspace-service /
@@ -356,8 +352,6 @@ export async function createDevtoolsRuntime(config = {}) {
356
352
  // teardown, so a single dispose leaves no dead entry behind.
357
353
  registerTrustedWindow: (win) => context.registry.add(registerTrustedWindow(context, win)),
358
354
  registerSimulatorApi: (name, handler) => context.registry.add(toDisposable(context.simulatorApis.register(name, handler))),
359
- refreshHeaderAvatar: () => context.notify.headerAvatarChanged(),
360
- refreshHeaderActions: () => context.notify.headerActionsChanged(),
361
355
  dispose: () => disposeContext(context),
362
356
  };
363
357
  if (config.onSetup) {
@@ -365,7 +365,14 @@ var WorkbenchSettingsChannel = {
365
365
  SetTheme: "workbenchSettings:setTheme",
366
366
  GetCdpStatus: "workbenchSettings:getCdpStatus",
367
367
  GetMcpStatus: "workbenchSettings:getMcpStatus",
368
- Init: "workbenchSettings:init"
368
+ Init: "workbenchSettings:init",
369
+ // Main → renderer push: the active color scheme flipped (OS change or in-app
370
+ // SetTheme). Payload is `isDark: boolean`. The app's CSS reacts to
371
+ // `prefers-color-scheme` automatically; this exists for the few JS consumers
372
+ // (Monaco's theme) that can't observe that media change — Electron does NOT
373
+ // dispatch the renderer's `matchMedia('(prefers-color-scheme)')` change event
374
+ // for programmatic `nativeTheme.themeSource` assignments.
375
+ ThemeChanged: "workbenchSettings:themeChanged"
369
376
  };
370
377
  var ProjectChannel = {
371
378
  Open: "project:open",
@@ -467,13 +474,7 @@ var WindowChannel = {
467
474
  NavigateBack: "window:navigateBack"
468
475
  };
469
476
  var AppChannel = {
470
- GetBranding: "app:getBranding",
471
- GetHeaderAvatar: "app:getHeaderAvatar",
472
- InvokeHeaderAvatar: "app:invokeHeaderAvatar",
473
- HeaderAvatarChanged: "app:headerAvatarChanged",
474
- GetHeaderActions: "app:getHeaderActions",
475
- InvokeHeaderAction: "app:invokeHeaderAction",
476
- HeaderActionsChanged: "app:headerActionsChanged"
477
+ GetBranding: "app:getBranding"
477
478
  };
478
479
  var AutomationChannel = {
479
480
  GetPort: "automation:port"
@@ -801,12 +802,21 @@ import { toDisposable } from "@dimina-kit/electron-deck/main";
801
802
  function themeBg() {
802
803
  return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
803
804
  }
805
+ function simDeskBg() {
806
+ return nativeTheme2.shouldUseDarkColors ? "#121212" : "#e8e8e8";
807
+ }
804
808
  function installThemeBackgroundSync() {
805
809
  const apply = () => {
806
810
  const bg = themeBg();
811
+ const isDark = nativeTheme2.shouldUseDarkColors;
807
812
  for (const win of BrowserWindow.getAllWindows()) {
808
- if (!win.isDestroyed()) {
813
+ if (win.isDestroyed()) continue;
814
+ try {
809
815
  win.setBackgroundColor(bg);
816
+ if (!win.webContents.isDestroyed()) {
817
+ win.webContents.send(WorkbenchSettingsChannel.ThemeChanged, isDark);
818
+ }
819
+ } catch {
810
820
  }
811
821
  }
812
822
  };
@@ -1001,12 +1011,6 @@ function createRendererNotifier(ctx) {
1001
1011
  hostToolbarHeightChanged(height) {
1002
1012
  sendToMain(ViewChannel.HostToolbarHeightChanged, height);
1003
1013
  },
1004
- headerAvatarChanged() {
1005
- sendToMain(AppChannel.HeaderAvatarChanged);
1006
- },
1007
- headerActionsChanged() {
1008
- sendToMain(AppChannel.HeaderActionsChanged);
1009
- },
1010
1014
  editorOpenFile(payload) {
1011
1015
  sendToMain(EditorChannel.OpenFile, payload);
1012
1016
  },
@@ -1030,7 +1034,7 @@ function createRendererNotifier(ctx) {
1030
1034
  }
1031
1035
 
1032
1036
  // src/main/services/views/view-manager.ts
1033
- import { ipcMain as ipcMain2, shell as shell2, WebContentsView as WebContentsView2, webContents } from "electron";
1037
+ import { ipcMain as ipcMain2, nativeTheme as nativeTheme3, shell as shell2, WebContentsView as WebContentsView2, webContents } from "electron";
1034
1038
  import path7 from "path";
1035
1039
 
1036
1040
  // src/shared/open-in-editor.ts
@@ -1087,22 +1091,23 @@ function resourceUrlToProjectRelativePath(resourceUrl, expectedOrigin) {
1087
1091
  }
1088
1092
 
1089
1093
  // src/main/services/safe-area/index.ts
1090
- function guestInsets(device) {
1094
+ function guestInsets(device, isTabPage2) {
1091
1095
  const top = device?.safeAreaInsets.top ?? 0;
1092
- return { top, topMax: top, right: 0, rightMax: 0, bottom: 0, bottomMax: 0, left: 0, leftMax: 0 };
1096
+ const bottom = isTabPage2 ? 0 : device?.safeAreaInsets.bottom ?? 0;
1097
+ return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
1093
1098
  }
1094
1099
  function createSafeAreaController(options = {}) {
1095
- const attached = /* @__PURE__ */ new Set();
1096
- function override(wc, device) {
1100
+ const attached = /* @__PURE__ */ new Map();
1101
+ function override(wc, device, isTabPage2) {
1097
1102
  if (wc.isDestroyed()) return;
1098
- void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device) }).catch((err) => {
1103
+ void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device, isTabPage2) }).catch((err) => {
1099
1104
  console.warn("[safe-area] setSafeAreaInsetsOverride failed:", err instanceof Error ? err.message : err);
1100
1105
  });
1101
1106
  }
1102
1107
  return {
1103
- applyToGuest: (wc, device) => {
1108
+ applyToGuest: (wc, device, isTabPage2) => {
1104
1109
  if (!wc || wc.isDestroyed() || attached.has(wc)) {
1105
- if (wc && !wc.isDestroyed() && attached.has(wc)) override(wc, device);
1110
+ if (wc && !wc.isDestroyed() && attached.has(wc)) override(wc, device, isTabPage2);
1106
1111
  return;
1107
1112
  }
1108
1113
  try {
@@ -1111,19 +1116,19 @@ function createSafeAreaController(options = {}) {
1111
1116
  console.warn("[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:", err instanceof Error ? err.message : err);
1112
1117
  return;
1113
1118
  }
1114
- attached.add(wc);
1119
+ attached.set(wc, isTabPage2);
1115
1120
  if (options.connections) {
1116
1121
  options.connections.acquire(wc).own(() => attached.delete(wc));
1117
1122
  } else {
1118
1123
  wc.once("destroyed", () => attached.delete(wc));
1119
1124
  }
1120
- override(wc, device);
1125
+ override(wc, device, isTabPage2);
1121
1126
  },
1122
1127
  reapplyAll: (device) => {
1123
- for (const wc of attached) override(wc, device);
1128
+ for (const [wc, isTabPage2] of attached) override(wc, device, isTabPage2);
1124
1129
  },
1125
1130
  dispose: () => {
1126
- for (const wc of attached) {
1131
+ for (const wc of attached.keys()) {
1127
1132
  try {
1128
1133
  if (!wc.isDestroyed()) wc.debugger.detach();
1129
1134
  } catch {
@@ -2325,22 +2330,38 @@ function createViewManager(ctx) {
2325
2330
  }
2326
2331
  });
2327
2332
  nativeSimulatorView = view;
2328
- view.setBackgroundColor("#121212");
2333
+ view.setBackgroundColor(simDeskBg());
2329
2334
  const simWc = view.webContents;
2335
+ const syncDeskBg = () => {
2336
+ try {
2337
+ if (!simWc.isDestroyed()) view.setBackgroundColor(simDeskBg());
2338
+ } catch {
2339
+ }
2340
+ };
2341
+ nativeTheme3.on("updated", syncDeskBg);
2342
+ ctx.connections.acquire(simWc).own(() => nativeTheme3.removeListener("updated", syncDeskBg));
2330
2343
  attachNativeCustomApiBridge(simWc);
2344
+ const pendingGuestIsTab = [];
2331
2345
  simWc.on("will-attach-webview", (_event, webPreferences, params) => {
2332
2346
  ;
2333
2347
  webPreferences.partition = partition;
2334
2348
  params.partition = partition;
2335
2349
  webPreferences.contextIsolation = false;
2336
2350
  webPreferences.sandbox = false;
2351
+ let isTab = false;
2352
+ try {
2353
+ isTab = new URL(params.src).searchParams.get("isTab") === "1";
2354
+ } catch {
2355
+ }
2356
+ pendingGuestIsTab.push(isTab);
2337
2357
  });
2338
2358
  simWc.on("did-attach-webview", (_event, guestWc) => {
2339
2359
  try {
2340
2360
  guestWc.setZoomFactor(currentZoomFactor);
2341
2361
  } catch {
2342
2362
  }
2343
- safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null);
2363
+ const isTabGuest = pendingGuestIsTab.shift() ?? false;
2364
+ safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null, isTabGuest);
2344
2365
  guestWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
2345
2366
  guestWc.on("will-navigate", (e, url) => {
2346
2367
  try {
@@ -3119,11 +3140,7 @@ function createWorkbenchContext(opts) {
3119
3140
  rendererDir: opts.rendererDir,
3120
3141
  apiNamespaces: opts.apiNamespaces ?? [],
3121
3142
  appName: opts.appName ?? "Dimina DevTools",
3122
- brandingProvider: opts.brandingProvider,
3123
- headerAvatarProvider: opts.headerAvatarProvider,
3124
- headerAvatarActionHandler: opts.headerAvatarActionHandler,
3125
- headerActionsProvider: opts.headerActionsProvider,
3126
- headerActionHandler: opts.headerActionHandler
3143
+ brandingProvider: opts.brandingProvider
3127
3144
  };
3128
3145
  ctx.registry = new DisposableRegistry3();
3129
3146
  ctx.connections = createConnectionRegistry();
@@ -3223,70 +3240,10 @@ function installAppMenu(ctx) {
3223
3240
  }
3224
3241
 
3225
3242
  // src/main/ipc/app.ts
3226
- function readString(value) {
3227
- if (typeof value !== "string") return void 0;
3228
- const trimmed = value.trim();
3229
- return trimmed.length > 0 ? trimmed : void 0;
3230
- }
3231
- function normalizeHeaderAvatar(value) {
3232
- if (!value || typeof value !== "object") return null;
3233
- const raw = value;
3234
- const avatar = {};
3235
- const displayName = readString(raw.displayName);
3236
- const displayInitial = readString(raw.displayInitial);
3237
- const avatarUrl = readString(raw.avatarUrl);
3238
- const tooltip = readString(raw.tooltip);
3239
- if (displayName) avatar.displayName = displayName;
3240
- if (displayInitial) avatar.displayInitial = displayInitial;
3241
- if (avatarUrl) avatar.avatarUrl = avatarUrl;
3242
- if (tooltip) avatar.tooltip = tooltip;
3243
- return avatar.displayName || avatar.displayInitial || avatar.avatarUrl ? avatar : null;
3244
- }
3245
- function readPlacement(value) {
3246
- return value === "left" || value === "center" || value === "right" ? value : void 0;
3247
- }
3248
- function normalizeHeaderAction(value) {
3249
- if (!value || typeof value !== "object") return null;
3250
- const raw = value;
3251
- const id = readString(raw.id);
3252
- const label = readString(raw.label);
3253
- if (!id || !label) return null;
3254
- const action = { id, label };
3255
- const tooltip = readString(raw.tooltip);
3256
- const placement = readPlacement(raw.placement);
3257
- if (tooltip) action.tooltip = tooltip;
3258
- if (placement) action.placement = placement;
3259
- if (typeof raw.disabled === "boolean") action.disabled = raw.disabled;
3260
- return action;
3261
- }
3262
- function normalizeHeaderActions(value) {
3263
- if (!Array.isArray(value)) return [];
3264
- const actions = [];
3265
- const seen = /* @__PURE__ */ new Set();
3266
- for (const item of value) {
3267
- const action = normalizeHeaderAction(item);
3268
- if (!action || seen.has(action.id)) continue;
3269
- seen.add(action.id);
3270
- actions.push(action);
3271
- }
3272
- return actions.slice(0, 8);
3273
- }
3274
3243
  function registerAppIpc(ctx) {
3275
3244
  return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetBranding, async () => {
3276
3245
  if (ctx.brandingProvider) return ctx.brandingProvider();
3277
3246
  return { appName: ctx.appName };
3278
- }).handle(AppChannel.GetHeaderAvatar, async () => {
3279
- if (!ctx.headerAvatarProvider) return null;
3280
- return normalizeHeaderAvatar(await ctx.headerAvatarProvider());
3281
- }).handle(AppChannel.InvokeHeaderAvatar, async () => {
3282
- await ctx.headerAvatarActionHandler?.();
3283
- }).handle(AppChannel.GetHeaderActions, async () => {
3284
- if (!ctx.headerActionsProvider) return [];
3285
- return normalizeHeaderActions(await ctx.headerActionsProvider());
3286
- }).handle(AppChannel.InvokeHeaderAction, async (_event, id) => {
3287
- const actionId = readString(id);
3288
- if (!actionId) return;
3289
- await ctx.headerActionHandler?.(actionId);
3290
3247
  });
3291
3248
  }
3292
3249
 
@@ -3334,7 +3291,6 @@ var SimulatorSetDeviceInfoSchema = z3.tuple([
3334
3291
  screenWidth: z3.number().int().min(100).max(4e3),
3335
3292
  screenHeight: z3.number().int().min(100).max(4e3),
3336
3293
  statusBarHeight: z3.number().finite().min(0).max(400),
3337
- safeAreaBottom: z3.number().finite().min(0).max(400),
3338
3294
  notchType: z3.enum(["none", "notch", "dynamic-island"]),
3339
3295
  safeAreaInsets: z3.object({
3340
3296
  top: z3.number().finite().min(0).max(400),
@@ -8895,10 +8851,6 @@ function createContext(config, mainWindow, rendererDir2) {
8895
8851
  appName: config.appName,
8896
8852
  apiNamespaces: config.apiNamespaces,
8897
8853
  brandingProvider: config.brandingProvider,
8898
- headerAvatarProvider: config.headerAvatarProvider,
8899
- headerAvatarActionHandler: config.headerAvatarActionHandler,
8900
- headerActionsProvider: config.headerActionsProvider,
8901
- headerActionHandler: config.headerActionHandler,
8902
8854
  // The host-supplied ProjectsProvider / template types in `shared/types`
8903
8855
  // are structurally compatible with the main-process equivalents —
8904
8856
  // these casts are safe; we re-narrow at the workspace-service /
@@ -9035,8 +8987,6 @@ async function createDevtoolsRuntime(config = {}) {
9035
8987
  // teardown, so a single dispose leaves no dead entry behind.
9036
8988
  registerTrustedWindow: (win) => context.registry.add(registerTrustedWindow(context, win)),
9037
8989
  registerSimulatorApi: (name, handler) => context.registry.add(toDisposable8(context.simulatorApis.register(name, handler))),
9038
- refreshHeaderAvatar: () => context.notify.headerAvatarChanged(),
9039
- refreshHeaderActions: () => context.notify.headerActionsChanged(),
9040
8990
  dispose: () => disposeContext(context)
9041
8991
  };
9042
8992
  if (config.onSetup) {
@@ -1,6 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '@dimina-kit/electron-deck/main';
3
- type AppIpcContext = Pick<WorkbenchContext, 'brandingProvider' | 'appName' | 'headerAvatarProvider' | 'headerAvatarActionHandler' | 'headerActionsProvider' | 'headerActionHandler' | 'senderPolicy'>;
4
- export declare function registerAppIpc(ctx: AppIpcContext): Disposable;
5
- export {};
3
+ export declare function registerAppIpc(ctx: Pick<WorkbenchContext, 'brandingProvider' | 'appName' | 'senderPolicy'>): Disposable;
6
4
  //# sourceMappingURL=app.d.ts.map
@@ -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
@@ -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
  }