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

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 (49) hide show
  1. package/README.md +86 -4
  2. package/dist/main/api.d.ts +1 -1
  3. package/dist/main/app/app.d.ts +4 -0
  4. package/dist/main/app/app.js +6 -0
  5. package/dist/main/index.bundle.js +147 -14
  6. package/dist/main/ipc/app.d.ts +3 -1
  7. package/dist/main/ipc/app.js +80 -0
  8. package/dist/main/ipc/bridge-router.js +35 -8
  9. package/dist/main/ipc/views.js +10 -1
  10. package/dist/main/services/notifications/renderer-notifier.d.ts +15 -0
  11. package/dist/main/services/notifications/renderer-notifier.js +10 -1
  12. package/dist/main/services/views/view-manager.d.ts +16 -4
  13. package/dist/main/services/views/view-manager.js +32 -8
  14. package/dist/main/services/workbench-context.d.ts +13 -1
  15. package/dist/main/services/workbench-context.js +4 -0
  16. package/dist/main/services/workspace/workspace-service.js +39 -0
  17. package/dist/preload/index.d.ts +2 -2
  18. package/dist/preload/index.js +2 -2
  19. package/dist/preload/windows/host-toolbar-runtime.cjs +11 -1
  20. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  21. package/dist/preload/windows/main.cjs +9 -7
  22. package/dist/preload/windows/main.cjs.map +2 -2
  23. package/dist/preload/windows/simulator.cjs.map +1 -1
  24. package/dist/renderer/assets/index-Bx4XqFQ2.js +50 -0
  25. package/dist/renderer/assets/{input-6fpCXV-n.js → input-J1MQ5fra.js} +2 -2
  26. package/dist/renderer/assets/{ipc-transport-D22e4dv7.js → ipc-transport-21qDpt9Y.js} +2 -2
  27. package/dist/renderer/assets/ipc-transport-CVp38I_M.css +1 -0
  28. package/dist/renderer/assets/{popover-CdGdKkkc.js → popover-ChIUUEdj.js} +2 -2
  29. package/dist/renderer/assets/{select-X0sbjwdP.js → select-BgWAbMot.js} +2 -2
  30. package/dist/renderer/assets/{settings-CS4jYaCw.js → settings-CGea5k_R.js} +2 -2
  31. package/dist/renderer/assets/{settings-api-CCWTAao_.js → settings-api-Dit4oL6S.js} +2 -2
  32. package/dist/renderer/assets/{workbenchSettings-Bpzaj3bm.js → workbenchSettings-CFrdVOiP.js} +2 -2
  33. package/dist/renderer/entries/main/index.html +6 -6
  34. package/dist/renderer/entries/popover/index.html +5 -5
  35. package/dist/renderer/entries/settings/index.html +5 -5
  36. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  37. package/dist/shared/ipc-channels.d.ts +17 -0
  38. package/dist/shared/ipc-channels.js +20 -0
  39. package/dist/shared/types.d.ts +61 -0
  40. package/dist/simulator/assets/bridge-channels-BUQ5AbvJ.js +2 -0
  41. package/dist/simulator/assets/device-shell-BEnc2k-W.js +2 -0
  42. package/dist/simulator/assets/{simulator-DHylZf9Z.js → simulator-cV1PERi6.js} +3 -3
  43. package/dist/simulator/assets/simulator-mini-app-CYtW6bMd.js +2 -0
  44. package/dist/simulator/simulator.html +1 -1
  45. package/package.json +4 -4
  46. package/dist/renderer/assets/index-DLzjz1wv.js +0 -50
  47. package/dist/renderer/assets/ipc-transport-Bs8Sf1B2.css +0 -1
  48. package/dist/simulator/assets/device-shell-fH2cI-3q.js +0 -2
  49. package/dist/simulator/assets/simulator-mini-app-BDNu5n26.js +0 -2
@@ -58,17 +58,26 @@ const API_CALL_TIMEOUT_MS = 5_000;
58
58
  * rather than assuming a single session.
59
59
  */
60
60
  function resolveCurrentApp(state, ctx, appId) {
61
+ // Same-appId matches prefer the MOST RECENT spawn (Maps preserve insertion
62
+ // order): after a respawn/reopen the newest session is the live one — the
63
+ // first match could be a just-superseded session mid-teardown.
61
64
  if (appId) {
65
+ let match;
62
66
  for (const ap of state.appSessions.values())
63
67
  if (ap.appId === appId)
64
- return ap;
68
+ match = ap;
69
+ if (match)
70
+ return match;
65
71
  }
66
72
  const appInfo = ctx.workspace?.getSession?.()?.appInfo;
67
73
  const activeAppId = appInfo?.appId;
68
74
  if (activeAppId) {
75
+ let match;
69
76
  for (const ap of state.appSessions.values())
70
77
  if (ap.appId === activeAppId)
71
- return ap;
78
+ match = ap;
79
+ if (match)
80
+ return match;
72
81
  }
73
82
  // Maps preserve insertion order; the last entry is the most recent spawn.
74
83
  let last;
@@ -87,6 +96,12 @@ export function installBridgeRouter(ctx) {
87
96
  emitRenderEvent: () => { },
88
97
  connections: ctx.connections,
89
98
  debugTap: createDebugTap({ enabled: resolveDebugTapEnabled() }),
99
+ evictAppDataBridges: (ap) => {
100
+ if (!ctx.appData)
101
+ return;
102
+ for (const page of ap.pages.values())
103
+ ctx.appData.evictBridge(ap.appId, page.bridgeId);
104
+ },
90
105
  };
91
106
  // Opt-in (default OFF) pre-warm pool for service-host windows. When enabled,
92
107
  // handleSpawn acquires a warm window instead of constructing one per spawn.
@@ -298,12 +313,8 @@ export function installBridgeRouter(ctx) {
298
313
  console.warn(`[bridge-router] DISPOSE rejected: sender belongs to ${senderApp.appSessionId}, target ${target.appSessionId}`);
299
314
  return;
300
315
  }
301
- // Native-host AppData: clear the app's accumulated bridges so a re-opened
302
- // project doesn't show ghost tabs from the prior session.
303
- if (ctx.appData) {
304
- for (const page of target.pages.values())
305
- ctx.appData.evictBridge(target.appId, page.bridgeId);
306
- }
316
+ // AppData bridge eviction happens inside disposeAppSession (single
317
+ // chokepoint shared with the simulator-WCV 'destroyed' path).
307
318
  void disposeAppSession(state, target.appSessionId);
308
319
  };
309
320
  ipcMain.on(C.DISPOSE, onDispose);
@@ -517,6 +528,19 @@ async function handleSpawn(state, ctx, event, opts) {
517
528
  };
518
529
  appSession.onServiceClosed = onServiceClosed;
519
530
  serviceWindow.once('closed', onServiceClosed);
531
+ // The simulator WCV owns the app's UI lifetime. When it is destroyed —
532
+ // project close (`views.disposeAll`/detach) or a DeviceShell respawn
533
+ // (`attachNativeSimulator`, e.g. watcher hot reload) — the guest never gets
534
+ // to send its graceful `C.DISPOSE`, so without this hook the app session and
535
+ // its hidden service-host window leak, and `resolveCurrentApp` keeps
536
+ // resolving the STALE session for the same appId: `getActiveRenderWc` then
537
+ // dereferences dead pages and every panel pull (WXML/elements) returns null
538
+ // for the whole next session. Disposing here is idempotent with the graceful
539
+ // path (`disposeAppSession` early-returns once the session is gone).
540
+ const onSimulatorDestroyed = () => {
541
+ void disposeAppSession(state, appSessionId);
542
+ };
543
+ simulatorWc.once('destroyed', onSimulatorDestroyed);
520
544
  if (usedPool) {
521
545
  // A pooled/fallback window passes through about:blank (warm load or the
522
546
  // fallback's initial load). Boot only once the REAL service.html navigation
@@ -1144,6 +1168,9 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
1144
1168
  if (!ap)
1145
1169
  return;
1146
1170
  state.appSessions.delete(appSessionId);
1171
+ // Evict AppData bridges FIRST — eviction enumerates `ap.pages`, which the
1172
+ // page teardown below progressively empties (and finally clears).
1173
+ state.evictAppDataBridges(ap);
1147
1174
  // Drain any pending API calls owned by this app session. One-shot calls
1148
1175
  // normally self-clean on response/timeout, but persistent (`keep: true`)
1149
1176
  // subscriptions (e.g. audioListen) live with their timer cleared until
@@ -30,7 +30,16 @@ export function registerViewsIpc(ctx) {
30
30
  .handle(ViewChannel.HostToolbarBounds, (_event, ...args) => {
31
31
  const [bounds] = validate(ViewChannel.HostToolbarBounds, ViewBoundsSchema, args);
32
32
  ctx.views.setHostToolbarBounds(bounds);
33
- });
33
+ })
34
+ // Height replay pull: a freshly-mounted main-renderer placeholder asks for
35
+ // the last NOTIFIED toolbar height (main retains it — the toolbar's
36
+ // size-advertiser deduplicates and never re-pushes, so a push that fired
37
+ // while no project view was mounted is otherwise lost: cold start races
38
+ // it, close-project → reopen hits it always). Rides the SAME
39
+ // senderPolicy-gated registry as HostToolbarBounds: the toolbar WCV's
40
+ // arbitrary host content must not reach this — only the trusted main
41
+ // renderer pulls. Live delegation, not a registration-time snapshot.
42
+ .handle(ViewChannel.HostToolbarGetHeight, () => ctx.views.getHostToolbarHeight());
34
43
  // Reverse size-advertiser: the toolbar WCV's OWN renderer sends this, and the
35
44
  // host loads ARBITRARY content into that WCV. We DELIBERATELY do NOT add the
36
45
  // toolbar wc to the global sender policy — that would trust it for ALL ~72
@@ -12,6 +12,15 @@ export interface ProjectStatusPayload {
12
12
  /** True when the status update is emitted by the file-watcher rebuild loop. */
13
13
  hotReload?: boolean;
14
14
  }
15
+ /**
16
+ * Payload for the `project:compileLog` push — one filtered dmcc log line.
17
+ * `at` is stamped in the main process when the line is captured.
18
+ */
19
+ export interface CompileLogPayload {
20
+ at: number;
21
+ stream: 'stdout' | 'stderr';
22
+ text: string;
23
+ }
15
24
  /**
16
25
  * Payload for the `settings:init` event sent into the embedded settings overlay
17
26
  * right after it is shown.
@@ -43,6 +52,8 @@ export interface WorkbenchSettingsInitPayload {
43
52
  export interface RendererNotifier {
44
53
  /** Broadcast project compile status transitions to the main renderer. */
45
54
  projectStatus(payload: ProjectStatusPayload): void;
55
+ /** Push one per-line dmcc compile-log entry to the main renderer. */
56
+ compileLog(payload: CompileLogPayload): void;
46
57
  /** Ask the main renderer to navigate back to its landing screen. */
47
58
  windowNavigateBack(): void;
48
59
  /** Tell the main renderer the compile popover has been closed. */
@@ -54,6 +65,10 @@ export interface RendererNotifier {
54
65
  * placeholder div resizes (closes the host-toolbar dynamic-height loop).
55
66
  */
56
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;
57
72
  /**
58
73
  * Ask the main renderer's Monaco editor to open a project file at a position.
59
74
  * Drives the "click a console file link → open in editor" pipeline.
@@ -1,4 +1,4 @@
1
- import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchSettingsChannel, EditorChannel, ViewChannel, } from '../../../shared/ipc-channels.js';
1
+ import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchSettingsChannel, EditorChannel, ViewChannel, AppChannel, } from '../../../shared/ipc-channels.js';
2
2
  /** Safely resolve a WebContents, skipping destroyed / missing targets. */
3
3
  function liveWebContents(wc) {
4
4
  if (!wc)
@@ -24,6 +24,9 @@ export function createRendererNotifier(ctx) {
24
24
  projectStatus(payload) {
25
25
  sendToMain(ProjectChannel.Status, payload);
26
26
  },
27
+ compileLog(payload) {
28
+ sendToMain(ProjectChannel.CompileLog, payload);
29
+ },
27
30
  windowNavigateBack() {
28
31
  sendToMain(WindowChannel.NavigateBack);
29
32
  },
@@ -36,6 +39,12 @@ export function createRendererNotifier(ctx) {
36
39
  hostToolbarHeightChanged(height) {
37
40
  sendToMain(ViewChannel.HostToolbarHeightChanged, height);
38
41
  },
42
+ headerAvatarChanged() {
43
+ sendToMain(AppChannel.HeaderAvatarChanged);
44
+ },
45
+ headerActionsChanged() {
46
+ sendToMain(AppChannel.HeaderActionsChanged);
47
+ },
39
48
  editorOpenFile(payload) {
40
49
  sendToMain(EditorChannel.OpenFile, payload);
41
50
  },
@@ -112,6 +112,16 @@ export interface ViewManager {
112
112
  * must trust its id — see `createWorkbenchSenderPolicy`.
113
113
  */
114
114
  getHostToolbarWebContentsId(): number | null;
115
+ /**
116
+ * Return the last host-toolbar height NOTIFIED to the main-window renderer
117
+ * (an advertiser report in `'auto'` mode, a `setHeightMode({ fixed })` pin,
118
+ * or 0 after `hostToolbar.hide()`); 0 before any notify. The renderer pulls
119
+ * this on project-view mount to REPLAY a height whose push it missed — the
120
+ * toolbar's size-advertiser deduplicates and never re-reports, so a notify
121
+ * fired while no project view is mounted (cold start on the project list;
122
+ * always on close-project → reopen) would otherwise be lost forever.
123
+ */
124
+ getHostToolbarHeight(): number;
115
125
  /**
116
126
  * NATIVE-HOST ONLY. Position the simulator content WebContentsView over the
117
127
  * renderer-measured simulator panel REGION rect (the flex:1 placeholder slot,
@@ -168,10 +178,12 @@ export interface ViewManager {
168
178
  }): void;
169
179
  /**
170
180
  * Reverse size-advertiser sink: the toolbar WCV's own renderer advertises
171
- * its intrinsic content height (block-axis extent); we store it and push it
172
- * to the main-window renderer so the placeholder div resizes (closing the
173
- * dynamic-height loop). Ignored while a `{ fixed }` height mode is pinned
174
- * via `hostToolbar.setHeightMode` (the session-resident advertiser always
181
+ * its intrinsic content height (block-axis extent); we retain it as the
182
+ * last-notified height (`getHostToolbarHeight`) and push it to the
183
+ * main-window renderer so the placeholder div resizes (closing the
184
+ * dynamic-height loop). Ignored ENTIRELY while a `{ fixed }` height mode is
185
+ * pinned via `hostToolbar.setHeightMode` — dropped reports neither notify
186
+ * nor touch the retained value (the session-resident advertiser always
175
187
  * runs, so its reports must not fight a host-pinned height).
176
188
  */
177
189
  setHostToolbarHeight(extent: number): void;
@@ -95,6 +95,13 @@ export function createViewManager(ctx) {
95
95
  // Placeholder height authority: 'auto' = advertiser reports forward to the
96
96
  // renderer; { fixed } = host-pinned, advertiser reports are dropped.
97
97
  let hostToolbarHeightMode = 'auto';
98
+ // Last toolbar height NOTIFIED to the main-window renderer — the replay
99
+ // source behind `getHostToolbarHeight()`. Updated ONLY inside
100
+ // `notifyHostToolbarHeight` so the retained value can never diverge from
101
+ // what the renderer was told (an advertiser report dropped by a `{ fixed }`
102
+ // pin must not pollute it, and a setHeightMode validation reject leaves it
103
+ // untouched).
104
+ let hostToolbarLastHeight = 0;
98
105
  // Gated narrow channel to the toolbar PAGE (per-load MessagePort handshake;
99
106
  // see host-toolbar-port-channel.ts). Control-level registry — created with
100
107
  // the manager so onMessage() works before any toolbar view exists.
@@ -248,18 +255,30 @@ export function createViewManager(ctx) {
248
255
  }
249
256
  view.setBounds(bounds);
250
257
  }
258
+ // Single funnel for the height notify: retain-then-push, so the retained
259
+ // value is exactly the last value the renderer was told. Every height
260
+ // notify site MUST go through here — the renderer pulls the retained value
261
+ // on project-view mount to replay a push it missed (the toolbar's
262
+ // size-advertiser deduplicates and never re-reports).
263
+ function notifyHostToolbarHeight(height) {
264
+ hostToolbarLastHeight = height;
265
+ ctx.notify.hostToolbarHeightChanged(height);
266
+ }
251
267
  function setHostToolbarHeight(extent) {
252
268
  // While the host pins a fixed height, drop advertiser reports entirely —
253
269
  // the session-resident advertiser is always installed, so forwarding its
254
270
  // reports would make the strip oscillate between the pinned and measured
255
- // heights on every content resize.
271
+ // heights on every content resize. Dropped reports must not touch the
272
+ // retained value either: retention records what was NOTIFIED, not what
273
+ // was reported.
256
274
  if (hostToolbarHeightMode !== 'auto')
257
275
  return;
258
276
  // Push the reserved height back to the main-window renderer so its
259
- // placeholder div resizes (closing the dynamic-height loop). The height is
260
- // not retained in main the renderer placeholder is the single source of
261
- // truth, and the forward anchor re-reports bounds from it.
262
- ctx.notify.hostToolbarHeightChanged(extent);
277
+ // placeholder div resizes (closing the dynamic-height loop). The notified
278
+ // height IS retained in main (`getHostToolbarHeight`) so a renderer that
279
+ // mounts later can pull/replay it; the renderer placeholder remains the
280
+ // geometry authority — the forward anchor re-reports bounds from it.
281
+ notifyHostToolbarHeight(extent);
263
282
  }
264
283
  function hideHostToolbar() {
265
284
  if (hostToolbarView && hostToolbarViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
@@ -273,7 +292,9 @@ export function createViewManager(ctx) {
273
292
  // non-zero reserved height and re-publishes bounds on the next window
274
293
  // resize, silently re-adding the view we just hid (unstable hide). Zeroing
275
294
  // the height flips the anchor to `present:false` so it stops re-publishing.
276
- ctx.notify.hostToolbarHeightChanged(0);
295
+ // Through the funnel so the retained value follows to 0 — a renderer
296
+ // mounting after the hide must replay 0, not the stale pre-hide height.
297
+ notifyHostToolbarHeight(0);
277
298
  }
278
299
  const hostToolbar = {
279
300
  async loadURL(url) {
@@ -317,11 +338,13 @@ export function createViewManager(ctx) {
317
338
  if (mode !== 'auto') {
318
339
  // Pin immediately: a preload-less/static toolbar never advertises, so
319
340
  // waiting for the next report would leave the strip at height 0.
320
- ctx.notify.hostToolbarHeightChanged(mode.fixed);
341
+ notifyHostToolbarHeight(mode.fixed);
321
342
  }
322
343
  // Switching back to 'auto' deliberately does NOT synthesize a notify —
323
344
  // replaying a stale cached height would flash the old size; the NEXT
324
- // advertiser report drives the placeholder again.
345
+ // advertiser report drives the placeholder again. The RETAINED value
346
+ // survives the switch though: a freshly-mounting renderer still needs
347
+ // the pinned height until that next report lands.
325
348
  },
326
349
  onMessage(channel, handler) {
327
350
  return hostToolbarPort.onMessage(channel, handler);
@@ -1146,6 +1169,7 @@ export function createViewManager(ctx) {
1146
1169
  return popoverView.webContents.id;
1147
1170
  },
1148
1171
  getHostToolbarWebContentsId: () => liveHostToolbarWebContents()?.id ?? null,
1172
+ getHostToolbarHeight: () => hostToolbarLastHeight,
1149
1173
  setNativeSimulatorViewBounds,
1150
1174
  resize,
1151
1175
  setSimulatorDevtoolsBounds,
@@ -1,5 +1,5 @@
1
1
  import type { BrowserWindow } from 'electron';
2
- import type { CompilationAdapter, WorkbenchConfig } from '../../shared/types.js';
2
+ import type { CompilationAdapter, HeaderActionsProvider, HeaderAvatarProvider, 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,6 +35,14 @@ 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>;
38
46
  /** Unified lifecycle manager for all overlay WebContentsViews */
39
47
  views: ViewManager;
40
48
  /** Owns BrowserWindow instances (main + settings) and their sender-id checks. */
@@ -175,6 +183,10 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
175
183
  preloadPath: string;
176
184
  rendererDir: string;
177
185
  brandingProvider?: WorkbenchContext['brandingProvider'];
186
+ headerAvatarProvider?: WorkbenchContext['headerAvatarProvider'];
187
+ headerAvatarActionHandler?: WorkbenchContext['headerAvatarActionHandler'];
188
+ headerActionsProvider?: WorkbenchContext['headerActionsProvider'];
189
+ headerActionHandler?: WorkbenchContext['headerActionHandler'];
178
190
  /** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
179
191
  projectsProvider?: ProjectsProvider;
180
192
  /** Templates injected by the host; same-id overrides a built-in. */
@@ -18,6 +18,10 @@ 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,
21
25
  };
22
26
  ctx.registry = new DisposableRegistry();
23
27
  // Empty connection registry as a first-class context field. Connections are
@@ -18,6 +18,16 @@ export function createWorkspaceService(ctx) {
18
18
  // accept an in-flight write that targets the just-closed project. Reset at
19
19
  // the start of every `openProject` so it never accumulates a stale root.
20
20
  let lastClosedProjectPath = '';
21
+ // Session generation for the onLog closure handed to the adapter: a closed
22
+ // (or switched-away-from) session's compile worker dies asynchronously, so
23
+ // buffered log lines can still arrive through the OLD closure after
24
+ // closeProject/openProject. Each openProject claims a new generation and
25
+ // closeProject invalidates the current one; a stale closure sees the
26
+ // mismatch and drops the line instead of polluting the next project's
27
+ // compile panel. (Deliberately NOT a `currentSession !== null` check: the
28
+ // first compile's log lines arrive while the adapter promise is still
29
+ // pending, before currentSession is assigned.)
30
+ let logGeneration = 0;
21
31
  function sendStatus(status, message, hotReload) {
22
32
  ctx.notify.projectStatus(hotReload ? { status, message, hotReload: true } : { status, message });
23
33
  }
@@ -71,6 +81,14 @@ export function createWorkspaceService(ctx) {
71
81
  : null,
72
82
  async openProject(projectPath) {
73
83
  clearSimulatorServicewechatReferer();
84
+ // Invalidate the outgoing session's onLog BEFORE teardown starts (same
85
+ // order as closeProject below): the dying compile worker flushes
86
+ // buffered lines DURING disposeSession(), and with the old generation
87
+ // still current they would pass the staleness guard and pollute the
88
+ // incoming project's compile-log timeline. The new session claims its
89
+ // own fresh generation further down (after dispose), so its onLog
90
+ // forwards normally.
91
+ logGeneration++;
74
92
  await disposeSession();
75
93
  currentProjectPath = '';
76
94
  // A fresh open starts a clean sandbox window — drop any previously
@@ -92,6 +110,9 @@ export function createWorkspaceService(ctx) {
92
110
  }
93
111
  sendStatus('compiling', '正在编译...');
94
112
  const { compile } = loadWorkbenchSettings();
113
+ // This open owns the log channel until the next open/close bumps the
114
+ // generation — the onLog closure below checks it per line.
115
+ const sessionGeneration = ++logGeneration;
95
116
  let session;
96
117
  try {
97
118
  session = await ctx.adapter.openProject({
@@ -100,6 +121,21 @@ export function createWorkspaceService(ctx) {
100
121
  watch: compile.watch,
101
122
  onRebuild: () => sendStatus('ready', '编译完成,已热更新', true),
102
123
  onBuildError: (err) => sendStatus('error', String(err)),
124
+ // Per-line dmcc log (already filtered in devkit). Stamp the
125
+ // wall-clock capture time here and push verbatim on the dedicated
126
+ // compile-log channel — never through projectStatus, whose
127
+ // one-event-per-payload contract feeds compileEvents. Stale lines
128
+ // (a closed/replaced session's worker flushing its buffers) are
129
+ // dropped via the generation check.
130
+ onLog: (entry) => {
131
+ if (sessionGeneration !== logGeneration)
132
+ return;
133
+ ctx.notify.compileLog({
134
+ stream: entry.stream,
135
+ text: entry.text,
136
+ at: Date.now(),
137
+ });
138
+ },
103
139
  });
104
140
  }
105
141
  catch (err) {
@@ -141,6 +177,9 @@ export function createWorkspaceService(ctx) {
141
177
  };
142
178
  },
143
179
  async closeProject() {
180
+ // Invalidate the active session's onLog BEFORE teardown starts — lines
181
+ // flushed by the dying compile worker must not reach the panel.
182
+ logGeneration++;
144
183
  clearSimulatorServicewechatReferer();
145
184
  await disposeSession();
146
185
  // Record the root being torn down BEFORE clearing it, so a teardown/
@@ -13,7 +13,7 @@ export { installConsoleInstrumentation } from './instrumentation/console.js';
13
13
  export { createAppDataSource } from './instrumentation/app-data.js';
14
14
  export type { AppDataSnapshot } from './instrumentation/app-data.js';
15
15
  /**
16
- * @deprecated Will be removed in the next minor. Under the native-host (sole)
16
+ * @deprecated Will be removed in 0.5.0. Under the native-host (sole)
17
17
  * runtime the simulator top frame is a top-level WebContentsView — the page DOM
18
18
  * lives in child render-host `<webview>` guests, so this top-frame DOM observer
19
19
  * only ever publishes `null`. Panel WXML flows over the main-process
@@ -21,7 +21,7 @@ export type { AppDataSnapshot } from './instrumentation/app-data.js';
21
21
  */
22
22
  export { createWxmlSource } from './instrumentation/wxml.js';
23
23
  /**
24
- * @deprecated Will be removed in the next minor. The host's
24
+ * @deprecated Will be removed in 0.5.0. The host's
25
25
  * `miniapp-snapshot:push/pull` IPC has no receiving end under the native-host
26
26
  * (sole) runtime: the simulator is a top-level WebContentsView without an
27
27
  * embedder (its `sendToHost` fires into the void) and the renderer-side puller
@@ -12,7 +12,7 @@
12
12
  export { installConsoleInstrumentation } from './instrumentation/console.js';
13
13
  export { createAppDataSource } from './instrumentation/app-data.js';
14
14
  /**
15
- * @deprecated Will be removed in the next minor. Under the native-host (sole)
15
+ * @deprecated Will be removed in 0.5.0. Under the native-host (sole)
16
16
  * runtime the simulator top frame is a top-level WebContentsView — the page DOM
17
17
  * lives in child render-host `<webview>` guests, so this top-frame DOM observer
18
18
  * only ever publishes `null`. Panel WXML flows over the main-process
@@ -20,7 +20,7 @@ export { createAppDataSource } from './instrumentation/app-data.js';
20
20
  */
21
21
  export { createWxmlSource } from './instrumentation/wxml.js';
22
22
  /**
23
- * @deprecated Will be removed in the next minor. The host's
23
+ * @deprecated Will be removed in 0.5.0. The host's
24
24
  * `miniapp-snapshot:push/pull` IPC has no receiving end under the native-host
25
25
  * (sole) runtime: the simulator is a top-level WebContentsView without an
26
26
  * embedder (its `sendToHost` fires into the void) and the renderer-side puller
@@ -134,7 +134,17 @@ var ViewChannel = {
134
134
  * main → main-window renderer: push the reserved host-toolbar height so the
135
135
  * renderer placeholder div resizes (closing the dynamic-height loop).
136
136
  */
137
- HostToolbarHeightChanged: "view:host-toolbar:height-changed"
137
+ HostToolbarHeightChanged: "view:host-toolbar:height-changed",
138
+ /**
139
+ * main ← main-window renderer (invoke): pull the last NOTIFIED toolbar
140
+ * height retained in main. Mount-time replay companion to
141
+ * `HostToolbarHeightChanged`: the push listener mounts with the project
142
+ * view and the toolbar's size-advertiser deduplicates (never re-reports),
143
+ * so a height pushed while no project view was mounted would otherwise be
144
+ * lost forever (cold start on the project list races it; close-project →
145
+ * reopen hits it deterministically). No payload; resolves a number.
146
+ */
147
+ HostToolbarGetHeight: "view:host-toolbar:get-height"
138
148
  };
139
149
 
140
150
  // src/preload/runtime/host-toolbar-advertiser.ts