@dimina-kit/devtools 0.4.0-dev.20260803105710 → 0.4.0-dev.20260824071518

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 (113) hide show
  1. package/dist/main/app/app.js +12 -1
  2. package/dist/main/index.bundle.js +584 -323
  3. package/dist/main/ipc/index.d.ts +1 -0
  4. package/dist/main/ipc/index.js +1 -0
  5. package/dist/main/ipc/session.js +11 -0
  6. package/dist/main/ipc/tooltip.d.ts +13 -0
  7. package/dist/main/ipc/tooltip.js +28 -0
  8. package/dist/main/services/layout/index.d.ts +11 -0
  9. package/dist/main/services/layout/index.js +23 -0
  10. package/dist/main/services/network-forward/frontend-dispatch.js +2 -1
  11. package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
  12. package/dist/main/services/notifications/renderer-notifier.js +7 -1
  13. package/dist/main/services/simulator-current-page/index.d.ts +5 -3
  14. package/dist/main/services/simulator-current-page/index.js +8 -1
  15. package/dist/main/services/views/clear-console-filter.d.ts +44 -0
  16. package/dist/main/services/views/clear-console-filter.js +86 -0
  17. package/dist/main/services/views/console-filter-test-fixtures.d.ts +94 -0
  18. package/dist/main/services/views/console-filter-test-fixtures.js +82 -0
  19. package/dist/main/services/views/console-filter.d.ts +79 -130
  20. package/dist/main/services/views/console-filter.js +140 -183
  21. package/dist/main/services/views/devtools-tabs.d.ts +24 -4
  22. package/dist/main/services/views/devtools-tabs.js +40 -21
  23. package/dist/main/services/views/host-toolbar-view.js +5 -18
  24. package/dist/main/services/views/native-simulator-devtools-host.js +64 -45
  25. package/dist/main/services/views/native-simulator-view.js +6 -24
  26. package/dist/main/services/views/native-view-tree.d.ts +25 -0
  27. package/dist/main/services/views/native-view-tree.js +69 -0
  28. package/dist/main/services/views/overlay-panels-view.d.ts +27 -4
  29. package/dist/main/services/views/overlay-panels-view.js +138 -109
  30. package/dist/main/services/views/placement-reconciler.d.ts +4 -16
  31. package/dist/main/services/views/placement-reconciler.js +64 -29
  32. package/dist/main/services/views/placement-test-driver.d.ts +26 -0
  33. package/dist/main/services/views/placement-test-driver.js +26 -0
  34. package/dist/main/services/views/view-manager.d.ts +5 -9
  35. package/dist/main/services/views/view-manager.js +7 -1
  36. package/dist/main/services/views/workbench-view.js +27 -20
  37. package/dist/main/services/workbench-coi-server.d.ts +11 -0
  38. package/dist/main/services/workbench-coi-server.js +32 -0
  39. package/dist/main/services/workspace/rebuild-status.d.ts +21 -8
  40. package/dist/main/services/workspace/rebuild-status.js +24 -8
  41. package/dist/main/utils/sender-policy.d.ts +1 -1
  42. package/dist/main/utils/sender-policy.js +6 -1
  43. package/dist/main/utils/theme.d.ts +7 -2
  44. package/dist/main/utils/theme.js +8 -3
  45. package/dist/native-host/container/pageFrame.css +1 -1
  46. package/dist/native-host/render/render.js +1966 -1816
  47. package/dist/native-host/service/service.js +1 -1
  48. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  49. package/dist/preload/windows/main.cjs +3176 -14
  50. package/dist/preload/windows/main.cjs.map +4 -4
  51. package/dist/preload/windows/main.css +428 -0
  52. package/dist/preload/windows/main.css.map +7 -0
  53. package/dist/preload/windows/simulator.cjs +5 -2
  54. package/dist/preload/windows/simulator.cjs.map +2 -2
  55. package/dist/preload/windows/simulator.js +5 -2
  56. package/dist/renderer/assets/constants-Bdz0LHdy.js +2 -0
  57. package/dist/renderer/assets/index-CtznrmHR.js +49 -0
  58. package/dist/renderer/assets/{input-xdmfv-eH.js → input-Dmm6nob5.js} +2 -2
  59. package/dist/renderer/assets/ipc-transport-BOg0gR9W.js +9 -0
  60. package/dist/renderer/assets/ipc-transport-DgQhZaE9.css +1 -0
  61. package/dist/renderer/assets/popover-Cd1FEGOX.js +2 -0
  62. package/dist/renderer/assets/select-BaYox7If.js +2 -0
  63. package/dist/renderer/assets/settings-BXvwuFaz.js +2 -0
  64. package/dist/renderer/assets/settings-api-Bo9GbYqs.js +2 -0
  65. package/dist/renderer/assets/settings-tab-bar-Beia6lMr.js +2 -0
  66. package/dist/renderer/assets/tooltip-DfQqK3Sk.js +2 -0
  67. package/dist/renderer/assets/view-api-nn69je7A.js +2 -0
  68. package/dist/renderer/assets/workbenchSettings-oq_EyfKf.js +8 -0
  69. package/dist/renderer/entries/main/index.html +8 -7
  70. package/dist/renderer/entries/popover/index.html +7 -6
  71. package/dist/renderer/entries/settings/index.html +7 -5
  72. package/dist/renderer/entries/tooltip/index.html +20 -0
  73. package/dist/renderer/entries/workbench-settings/index.html +7 -5
  74. package/dist/service-host/sync-impls/menu-button-geometry.js +1 -0
  75. package/dist/shared/ipc-channels.d.ts +11 -0
  76. package/dist/shared/ipc-channels.js +28 -2
  77. package/dist/shared/ipc-schemas.d.ts +19 -0
  78. package/dist/shared/ipc-schemas.js +23 -0
  79. package/dist/shared/simulator-route.d.ts +8 -0
  80. package/dist/shared/simulator-route.js +15 -0
  81. package/dist/shared/types.d.ts +7 -0
  82. package/dist/shared/view-ids.d.ts +2 -0
  83. package/dist/shared/view-ids.js +11 -0
  84. package/dist/simulator/assets/device-shell-CmtM1QxP.js +2 -0
  85. package/dist/simulator/assets/device-shell-iFZo1igL.css +1 -0
  86. package/dist/simulator/assets/{simulator-COIE6R49.js → simulator-CIobn3Mh.js} +6 -6
  87. package/dist/simulator/assets/{simulator-mini-app-BLn7XV8e.js → simulator-mini-app-CRe6n9br.js} +2 -2
  88. package/dist/simulator/assets/simulator-ui-BupATgKo.css +1 -0
  89. package/dist/simulator/assets/simulator-ui-C_cP602X.js +2 -0
  90. package/dist/simulator/simulator.html +3 -2
  91. package/dist/vscode-workbench/assets/__vite-browser-external-Bc1vizTf.js +1 -0
  92. package/dist/vscode-workbench/assets/{dist-oHcwN53f.js → dist-CMfmHflW.js} +3 -3
  93. package/dist/vscode-workbench/assets/{iconv-lite-umd-CLMLpdGm.js → iconv-lite-umd-Bx8CgaZq.js} +1 -1
  94. package/dist/vscode-workbench/assets/{index-BE3UJdoH.js → index-CdG6YWhW.js} +436 -436
  95. package/dist/vscode-workbench/assets/{jschardet-BICW8PLJ.js → jschardet-M0qgd2lN.js} +1 -1
  96. package/dist/vscode-workbench/index.html +21 -1
  97. package/package.json +7 -6
  98. package/dist/main/services/views/destroy-child-view.d.ts +0 -9
  99. package/dist/main/services/views/destroy-child-view.js +0 -23
  100. package/dist/renderer/assets/constants-EAMiwL9_.js +0 -2
  101. package/dist/renderer/assets/index-BBg3YI5b.js +0 -49
  102. package/dist/renderer/assets/ipc-transport-B3EpmfNS.js +0 -9
  103. package/dist/renderer/assets/ipc-transport-Blac3sOj.css +0 -1
  104. package/dist/renderer/assets/popover-B88e2hkg.js +0 -2
  105. package/dist/renderer/assets/select-CD9CJyhI.js +0 -2
  106. package/dist/renderer/assets/settings-44hf0QtO.js +0 -2
  107. package/dist/renderer/assets/settings-api-pJKPuP1M.js +0 -2
  108. package/dist/renderer/assets/workbenchSettings-DKD5U1Ql.js +0 -8
  109. package/dist/simulator/assets/device-shell-Cw7c-IkG.js +0 -2
  110. package/dist/simulator/assets/device-shell-DbC5ccs8.css +0 -1
  111. package/dist/simulator/assets/jsx-runtime-DkKmJyBb.js +0 -2
  112. package/dist/simulator/device-shell/menu-button-geometry.js +0 -18
  113. package/dist/vscode-workbench/assets/__vite-browser-external-BON7qP59.js +0 -1
@@ -1,150 +1,179 @@
1
1
  import { WebContentsView } from 'electron';
2
- import path from 'path';
2
+ import { createOverlayPanel } from '@dimina-kit/electron-deck/main';
3
3
  import { mainPreloadPath } from '../../utils/paths.js';
4
4
  import { applyNavigationHardening } from '../../windows/navigation-hardening.js';
5
5
  import * as layout from '../layout/index.js';
6
6
  import { HEADER_H } from '../../../shared/constants.js';
7
7
  import { VIEW_ID, VIEW_LAYER } from '../../../shared/view-ids.js';
8
- import { destroyChildView } from './destroy-child-view.js';
9
8
  export function createOverlayPanelsView(ctx, reconciler, deps) {
10
- let settingsView = null;
11
- let popoverView = null;
12
9
  function overlayHeaderHeight() {
13
10
  return HEADER_H + deps.getHostToolbarHeight();
14
11
  }
15
- // settings/popover bounds are main-computed; publish them into overlayDesired
16
- // and let the reconciler place them (they are top-tier layers, so a reorder
17
- // keeps them above every base overlay — the old raiseTopOverlays is gone).
18
- function applySettingsBounds() {
19
- if (!settingsView || ctx.windows.mainWindow.isDestroyed())
20
- return;
21
- const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
22
- reconciler.setOverlayDesired(VIEW_ID.settings, {
23
- viewId: VIEW_ID.settings,
24
- placement: {
25
- visible: true,
26
- bounds: layout.computeSettingsBounds(w, h, overlayHeaderHeight()),
27
- },
28
- layer: VIEW_LAYER.settings,
29
- });
30
- reconciler.reconcileNow();
31
- }
32
- function applyPopoverBounds() {
33
- if (!popoverView || ctx.windows.mainWindow.isDestroyed())
34
- return;
35
- const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
36
- reconciler.setOverlayDesired(VIEW_ID.popover, {
37
- viewId: VIEW_ID.popover,
38
- placement: {
39
- visible: true,
40
- bounds: layout.computePopoverBounds(w, h, overlayHeaderHeight()),
41
- },
42
- layer: VIEW_LAYER.popover,
43
- });
44
- reconciler.reconcileNow();
12
+ function overlayDesiredSetter(viewId, layer) {
13
+ return (bounds) => {
14
+ if (bounds === null) {
15
+ reconciler.deleteOverlayDesired(viewId);
16
+ }
17
+ else {
18
+ reconciler.setOverlayDesired(viewId, {
19
+ viewId,
20
+ placement: { visible: true, bounds },
21
+ layer,
22
+ });
23
+ }
24
+ reconciler.reconcileNow();
25
+ };
45
26
  }
46
- async function showSettings() {
47
- if (!settingsView) {
48
- settingsView = new WebContentsView({
49
- webPreferences: {
50
- nodeIntegration: false,
51
- contextIsolation: true,
52
- sandbox: false,
53
- preload: mainPreloadPath,
54
- },
27
+ const settingsPanel = createOverlayPanel({
28
+ electron: { createWebContentsView: (opts) => new WebContentsView(opts) },
29
+ rendererDir: ctx.rendererDir,
30
+ entry: 'entries/settings/index.html',
31
+ webPreferences: {
32
+ nodeIntegration: false,
33
+ contextIsolation: true,
34
+ sandbox: false,
35
+ preload: mainPreloadPath,
36
+ },
37
+ hardenNavigation: (wc) => applyNavigationHardening(wc, ctx.rendererDir),
38
+ setDesired: overlayDesiredSetter(VIEW_ID.settings, VIEW_LAYER.settings),
39
+ registerView: (getView) => reconciler.registerView(VIEW_ID.settings, { getView }),
40
+ destroyView: (view) => reconciler.destroyView(VIEW_ID.settings, view),
41
+ readyMode: 'manual',
42
+ });
43
+ const popoverPanel = createOverlayPanel({
44
+ electron: { createWebContentsView: (opts) => new WebContentsView(opts) },
45
+ rendererDir: ctx.rendererDir,
46
+ entry: 'entries/popover/index.html',
47
+ webPreferences: {
48
+ nodeIntegration: false,
49
+ contextIsolation: true,
50
+ sandbox: false,
51
+ preload: mainPreloadPath,
52
+ },
53
+ hardenNavigation: (wc) => applyNavigationHardening(wc, ctx.rendererDir),
54
+ setDesired: overlayDesiredSetter(VIEW_ID.popover, VIEW_LAYER.popover),
55
+ registerView: (getView) => reconciler.registerView(VIEW_ID.popover, { getView }),
56
+ destroyView: (view) => reconciler.destroyView(VIEW_ID.popover, view),
57
+ pushData: (view, data) => ctx.notify.popoverInit(view, data),
58
+ readyMode: 'manual',
59
+ });
60
+ const tooltipPanel = createOverlayPanel({
61
+ electron: { createWebContentsView: (opts) => new WebContentsView(opts) },
62
+ rendererDir: ctx.rendererDir,
63
+ entry: 'entries/tooltip/index.html',
64
+ webPreferences: {
65
+ nodeIntegration: false,
66
+ contextIsolation: true,
67
+ sandbox: false,
68
+ preload: mainPreloadPath,
69
+ },
70
+ hardenNavigation: (wc) => applyNavigationHardening(wc, ctx.rendererDir),
71
+ setDesired: overlayDesiredSetter(VIEW_ID.tooltip, VIEW_LAYER.tooltip),
72
+ registerView: (getView) => reconciler.registerView(VIEW_ID.tooltip, { getView }),
73
+ prepareView: (view) => {
74
+ const [width = 0, height = 0] = ctx.windows.mainWindow.getContentSize();
75
+ reconciler.prepareView(VIEW_ID.tooltip, view, {
76
+ x: 0,
77
+ y: 0,
78
+ width: Math.max(1, width),
79
+ height: Math.max(1, height),
55
80
  });
56
- // Overlay loads mainPreloadPath, so the same navigation rules as the
57
- // main window apply — see navigation-hardening.ts.
58
- applyNavigationHardening(settingsView.webContents, ctx.rendererDir);
59
- // Transparent backing: the settings view now spans the whole content area
60
- // (computeSettingsBounds) and its renderer paints a transparent backdrop +
61
- // an opaque right-side panel, so the underlying editor/simulator show
62
- // through and a backdrop click closes the overlay (mirrors the popover).
63
- settingsView.setBackgroundColor('#00000000');
64
- await settingsView.webContents.loadFile(path.join(ctx.rendererDir, 'entries/settings/index.html'));
65
- }
66
- applySettingsBounds();
81
+ },
82
+ destroyView: (view) => reconciler.destroyView(VIEW_ID.tooltip, view),
83
+ pushData: (view, data) => ctx.notify.tooltipInit(view, data),
84
+ readyMode: 'manual',
85
+ });
86
+ let tooltipRequestId = 0;
87
+ let activeTooltip = null;
88
+ async function showSettings() {
89
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
90
+ settingsPanel.show(undefined, layout.computeSettingsBounds(w, h, overlayHeaderHeight()));
91
+ await settingsPanel.whenReady();
67
92
  }
68
93
  function hideSettings() {
69
- reconciler.deleteOverlayDesired(VIEW_ID.settings);
70
- reconciler.reconcileNow();
94
+ settingsPanel.hide();
71
95
  }
72
96
  function showPopover(data) {
97
+ // Popover carries no state across opens (always fresh init data) — force a
98
+ // brand-new instance rather than reusing/repositioning a live one (and
99
+ // notify like any other close, via the shared hidePopover()).
73
100
  hidePopover();
74
- const popover = new WebContentsView({
75
- webPreferences: {
76
- nodeIntegration: false,
77
- contextIsolation: true,
78
- sandbox: false,
79
- preload: mainPreloadPath,
80
- },
81
- });
82
- // Popover overlay loads mainPreloadPath — same navigation rules apply.
83
- applyNavigationHardening(popover.webContents, ctx.rendererDir);
84
- popoverView = popover;
85
- popover.setBackgroundColor('#00000000');
86
- applyPopoverBounds();
87
- popover.webContents.once('did-finish-load', () => {
88
- ctx.notify.popoverInit(popover, data);
89
- });
90
- popover.webContents.loadFile(path.join(ctx.rendererDir, 'entries/popover/index.html'));
101
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
102
+ popoverPanel.show(data, layout.computePopoverBounds(w, h, overlayHeaderHeight()));
91
103
  }
92
104
  function hidePopover() {
93
- if (!popoverView)
105
+ if (!popoverPanel.isPresent())
94
106
  return;
107
+ popoverPanel.destroy();
95
108
  reconciler.deleteOverlayDesired(VIEW_ID.popover);
96
- // Destroy the WCV first (removeChildView + close), THEN reconcile: the detach
97
- // op then finds the view already gone and does not double-removeChildView.
98
- destroyChildView(ctx.windows.mainWindow, popoverView);
99
- popoverView = null;
100
109
  reconciler.reconcileNow();
101
110
  ctx.notify.popoverClosed();
102
111
  }
112
+ function prepareTooltip() {
113
+ tooltipPanel.prepare();
114
+ }
115
+ function showTooltip(data) {
116
+ const requestId = ++tooltipRequestId;
117
+ activeTooltip = { requestId, anchor: data.anchor };
118
+ const [contentWidth = 0] = ctx.windows.mainWindow.getContentSize();
119
+ tooltipPanel.show({
120
+ requestId,
121
+ text: data.text,
122
+ maxWidth: layout.computeTooltipMaxWidth(contentWidth),
123
+ }, null);
124
+ }
125
+ function hideTooltip() {
126
+ activeTooltip = null;
127
+ tooltipPanel.hide();
128
+ }
129
+ function markOverlayReady(webContentsId) {
130
+ settingsPanel.markReady(webContentsId);
131
+ popoverPanel.markReady(webContentsId);
132
+ tooltipPanel.markReady(webContentsId);
133
+ }
134
+ function applyTooltipMeasurement(webContentsId, measurement) {
135
+ const active = activeTooltip;
136
+ if (!active || active.requestId !== measurement.requestId)
137
+ return;
138
+ if (tooltipPanel.getWebContentsId() !== webContentsId)
139
+ return;
140
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
141
+ tooltipPanel.reposition(layout.computeTooltipBounds(active.anchor, w, h, measurement));
142
+ }
103
143
  function reapplyPresentOverlays() {
104
- if (reconciler.hasOverlayDesired(VIEW_ID.settings))
105
- applySettingsBounds();
106
- if (reconciler.hasOverlayDesired(VIEW_ID.popover))
107
- applyPopoverBounds();
144
+ if (settingsPanel.isPresent() && reconciler.hasOverlayDesired(VIEW_ID.settings))
145
+ void showSettings();
146
+ if (popoverPanel.isPresent() && reconciler.hasOverlayDesired(VIEW_ID.popover)) {
147
+ const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
148
+ popoverPanel.reposition(layout.computePopoverBounds(w, h, overlayHeaderHeight()));
149
+ }
108
150
  }
109
151
  function applySettingsBoundsIfPresent() {
110
- if (reconciler.hasOverlayDesired(VIEW_ID.settings))
111
- applySettingsBounds();
152
+ if (settingsPanel.isPresent() && reconciler.hasOverlayDesired(VIEW_ID.settings))
153
+ void showSettings();
112
154
  }
113
155
  function destroySettings() {
114
- destroyChildView(ctx.windows.mainWindow, settingsView);
115
- settingsView = null;
156
+ activeTooltip = null;
157
+ settingsPanel.destroy();
158
+ tooltipPanel.destroy();
116
159
  }
117
- reconciler.registerView(VIEW_ID.settings, { getView: () => settingsView });
118
- reconciler.registerView(VIEW_ID.popover, { getView: () => popoverView });
119
160
  return {
120
161
  showSettings,
121
162
  hideSettings,
122
163
  showPopover,
123
164
  hidePopover,
165
+ prepareTooltip,
166
+ showTooltip,
167
+ hideTooltip,
168
+ markOverlayReady,
169
+ applyTooltipMeasurement,
124
170
  reapplyPresentOverlays,
125
171
  applySettingsBoundsIfPresent,
126
172
  destroySettings,
127
- getSettingsWebContents: () => {
128
- if (!settingsView)
129
- return null;
130
- if (settingsView.webContents.isDestroyed())
131
- return null;
132
- return settingsView.webContents;
133
- },
134
- getSettingsWebContentsId: () => {
135
- if (!settingsView)
136
- return null;
137
- if (settingsView.webContents.isDestroyed())
138
- return null;
139
- return settingsView.webContents.id;
140
- },
141
- getPopoverWebContentsId: () => {
142
- if (!popoverView)
143
- return null;
144
- if (popoverView.webContents.isDestroyed())
145
- return null;
146
- return popoverView.webContents.id;
147
- },
173
+ getSettingsWebContents: () => settingsPanel.getWebContents(),
174
+ getSettingsWebContentsId: () => settingsPanel.getWebContentsId(),
175
+ getPopoverWebContentsId: () => popoverPanel.getWebContentsId(),
176
+ getTooltipWebContentsId: () => tooltipPanel.getWebContentsId(),
148
177
  };
149
178
  }
150
179
  //# sourceMappingURL=overlay-panels-view.js.map
@@ -12,8 +12,6 @@ import type { ViewManagerContext } from './view-manager.js';
12
12
  export interface ViewSlot {
13
13
  /** The live WebContentsView for this slot, or null when not yet created. */
14
14
  getView(): WebContentsView | null;
15
- /** Record whether the view is currently added to the contentView. */
16
- setAdded?(added: boolean): void;
17
15
  /**
18
16
  * When true, force the desired placement to hidden — the view-creation site
19
17
  * has not produced the WCV yet, so the reconciler must never record an attach
@@ -21,12 +19,6 @@ export interface ViewSlot {
21
19
  * to re-open the gate.
22
20
  */
23
21
  gateHidden?(): boolean;
24
- /**
25
- * Run before the generic attach. Return true when the slot fully handled the
26
- * attach itself (e.g. a lazy async load that adds the view on completion), so
27
- * the reconciler must NOT addChildView here.
28
- */
29
- beforeAttach?(): boolean;
30
22
  /** Lazily create the view on attach (host-toolbar); used instead of getView. */
31
23
  ensureView?(): WebContentsView | null;
32
24
  /** Domain-specific setBounds (simulator zoom rides here). */
@@ -55,14 +47,10 @@ export interface PlacementReconciler {
55
47
  * truth for every managed native view's bounds/visibility/z-order.
56
48
  */
57
49
  setPlacementSnapshot(snapshot: PlacementSnapshot<DevtoolsExtra>): void;
58
- /**
59
- * Forget a view's reconciled mount state so the NEXT rebuilt view is treated
60
- * as a fresh attach. Required wherever a view instance is replaced/destroyed
61
- * via a manual removeChildView that bypasses the reconciler (otherwise the
62
- * level-triggered core still records the old instance as attached and never
63
- * emits the attach op for the replacement — a sticky invisible view).
64
- */
65
- forgetActual(viewId: string): void;
50
+ /** Size a detached view for renderer-side intrinsic measurement. */
51
+ prepareView(viewId: string, view: WebContentsView, bounds: layout.Bounds): void;
52
+ /** Detach and close one concrete view instance through the native-tree owner. */
53
+ destroyView(viewId: string, view: WebContentsView | null): void;
66
54
  setBaseDesired(viewId: string, desired: DesiredView<DevtoolsExtra>): void;
67
55
  deleteBaseDesired(viewId: string): void;
68
56
  setOverlayDesired(viewId: string, desired: DesiredView<DevtoolsExtra>): void;
@@ -1,12 +1,17 @@
1
1
  import { reconcile, createInitialState } from '@dimina-kit/electron-deck/layout';
2
2
  import { applyViewOps } from './apply-view-ops.js';
3
+ import { createNativeViewTreeHost } from './native-view-tree.js';
3
4
  export function createPlacementReconciler(ctx) {
4
5
  let placementState = createInitialState();
5
6
  let epochCounter = 0;
6
7
  let rendererGeneration = 0;
8
+ const appliedActual = new Map();
7
9
  const baseDesired = new Map();
8
10
  const overlayDesired = new Map();
9
11
  const slots = new Map();
12
+ // Sole owner of the main window's native child tree. Every attach, detach,
13
+ // reorder and hard destroy passes through this ledger-backed adapter.
14
+ const contentViewHost = createNativeViewTreeHost(ctx, slots);
10
15
  function gateReadiness(v) {
11
16
  const slot = slots.get(v.viewId);
12
17
  if (slot?.gateHidden?.())
@@ -20,59 +25,67 @@ export function createPlacementReconciler(ctx) {
20
25
  const slot = slots.get(viewId);
21
26
  if (!slot)
22
27
  return;
23
- if (slot.beforeAttach?.())
24
- return;
25
28
  const view = slot.ensureView ? slot.ensureView() : slot.getView();
26
29
  if (!view)
27
30
  return;
28
- ctx.windows.mainWindow.contentView.addChildView(view);
29
- slot.setAdded?.(true);
31
+ if (!contentViewHost.addChildView({ id: viewId }))
32
+ return;
33
+ const previous = appliedActual.get(viewId);
34
+ appliedActual.set(viewId, {
35
+ attached: true,
36
+ visible: true,
37
+ bounds: previous?.bounds,
38
+ extra: previous?.extra,
39
+ });
30
40
  },
31
41
  detach(viewId) {
32
- const slot = slots.get(viewId);
33
- const view = slot?.getView() ?? null;
34
- if (view && !ctx.windows.mainWindow.isDestroyed()) {
35
- try {
36
- ctx.windows.mainWindow.contentView.removeChildView(view);
37
- }
38
- catch { /* already removed */ }
39
- }
40
- slot?.setAdded?.(false);
42
+ if (!contentViewHost.removeChildView({ id: viewId }))
43
+ return;
44
+ appliedActual.delete(viewId);
41
45
  },
42
46
  setBounds(viewId, bounds, extra) {
43
47
  const slot = slots.get(viewId);
44
48
  const view = slot?.getView() ?? null;
45
49
  if (!view || view.webContents.isDestroyed())
46
50
  return;
47
- if (slot?.applyBounds) {
48
- slot.applyBounds(view, bounds, extra);
51
+ try {
52
+ if (slot?.applyBounds)
53
+ slot.applyBounds(view, bounds, extra);
54
+ else
55
+ view.setBounds(bounds);
56
+ }
57
+ catch {
49
58
  return;
50
59
  }
51
- view.setBounds(bounds);
60
+ const previous = appliedActual.get(viewId);
61
+ appliedActual.set(viewId, {
62
+ attached: previous?.attached ?? false,
63
+ visible: previous?.visible ?? false,
64
+ bounds,
65
+ extra,
66
+ });
52
67
  },
53
68
  setVisible(viewId, visible) {
54
69
  const view = slots.get(viewId)?.getView() ?? null;
55
- if (!view)
70
+ if (!view || view.webContents.isDestroyed())
56
71
  return;
57
72
  try {
58
73
  view.setVisible(visible);
59
74
  }
60
- catch { /* stub may lack setVisible */ }
75
+ catch {
76
+ return;
77
+ }
78
+ const previous = appliedActual.get(viewId);
79
+ if (!previous)
80
+ return;
81
+ appliedActual.set(viewId, { ...previous, visible });
61
82
  },
62
83
  reorder(order) {
63
84
  // A single attached view is already in place — nothing to reorder.
64
85
  if (order.length <= 1 || ctx.windows.mainWindow.isDestroyed())
65
86
  return;
66
- const cv = ctx.windows.mainWindow.contentView;
67
- for (const viewId of order) {
68
- const view = slots.get(viewId)?.getView() ?? null;
69
- if (view) {
70
- try {
71
- cv.addChildView(view);
72
- }
73
- catch { /* already attached */ }
74
- }
75
- }
87
+ for (const viewId of order)
88
+ contentViewHost.addChildView({ id: viewId });
76
89
  },
77
90
  };
78
91
  // Create the lazily-built views (host-toolbar, workbench) BEFORE reconcile so
@@ -83,6 +96,9 @@ export function createPlacementReconciler(ctx) {
83
96
  slot.ensureLazy?.(baseDesired.get(viewId));
84
97
  }
85
98
  }
99
+ function cloneAppliedActual() {
100
+ return new Map([...appliedActual].map(([viewId, actual]) => [viewId, { ...actual }]));
101
+ }
86
102
  function reconcileNow() {
87
103
  ensureLazyViews();
88
104
  const views = [];
@@ -90,6 +106,7 @@ export function createPlacementReconciler(ctx) {
90
106
  views.push(gateReadiness(v));
91
107
  for (const v of overlayDesired.values())
92
108
  views.push(v);
109
+ placementState = { ...placementState, actual: cloneAppliedActual() };
93
110
  const result = reconcile(placementState, {
94
111
  generation: rendererGeneration,
95
112
  epoch: ++epochCounter,
@@ -97,6 +114,9 @@ export function createPlacementReconciler(ctx) {
97
114
  });
98
115
  placementState = result.state;
99
116
  applyViewOps(result.ops, viewTarget);
117
+ // The planner's next state is optimistic. Keep only desired/epoch metadata;
118
+ // appliedActual is updated by successful native effects above.
119
+ placementState = { ...placementState, actual: cloneAppliedActual() };
100
120
  }
101
121
  function setPlacementSnapshot(snapshot) {
102
122
  rendererGeneration = snapshot.generation;
@@ -109,7 +129,22 @@ export function createPlacementReconciler(ctx) {
109
129
  registerView: (viewId, slot) => { slots.set(viewId, slot); },
110
130
  reconcileNow,
111
131
  setPlacementSnapshot,
112
- forgetActual: (viewId) => { placementState.actual.delete(viewId); },
132
+ prepareView: (viewId, view, bounds) => {
133
+ if (slots.get(viewId)?.getView() !== view || view.webContents.isDestroyed())
134
+ return;
135
+ try {
136
+ view.setBounds(bounds);
137
+ }
138
+ catch {
139
+ return;
140
+ }
141
+ appliedActual.set(viewId, { attached: false, visible: false, bounds });
142
+ },
143
+ destroyView: (viewId, view) => {
144
+ contentViewHost.destroyView(viewId, view);
145
+ appliedActual.delete(viewId);
146
+ placementState.actual.delete(viewId);
147
+ },
113
148
  setBaseDesired: (viewId, desired) => { baseDesired.set(viewId, desired); },
114
149
  deleteBaseDesired: (viewId) => { baseDesired.delete(viewId); },
115
150
  setOverlayDesired: (viewId, desired) => { overlayDesired.set(viewId, desired); },
@@ -13,9 +13,35 @@ interface SnapshotSink {
13
13
  views: DesiredView<DevtoolsExtra>[];
14
14
  }): void;
15
15
  }
16
+ interface OverlaySink {
17
+ showSettings(): Promise<void>;
18
+ showPopover(data: unknown): void;
19
+ showTooltip(data: {
20
+ anchor: Rect;
21
+ text: string;
22
+ }): void;
23
+ getSettingsWebContentsId(): number | null;
24
+ getPopoverWebContentsId(): number | null;
25
+ getTooltipWebContentsId(): number | null;
26
+ markOverlayReady(webContentsId: number): void;
27
+ applyTooltipMeasurement(webContentsId: number, measurement: {
28
+ requestId: number;
29
+ width: number;
30
+ height: number;
31
+ }): void;
32
+ }
16
33
  export declare function hostToolbarBounds(mgr: SnapshotSink, rect: Rect): void;
17
34
  export declare function simulatorDevtoolsBounds(mgr: SnapshotSink, rect: Rect): void;
18
35
  export declare function workbenchBounds(mgr: SnapshotSink, rect: Rect): void;
36
+ export declare function showSettingsReady(mgr: OverlaySink): Promise<void>;
37
+ export declare function showPopoverReady(mgr: OverlaySink, data: unknown): void;
38
+ export declare function showTooltipReady(mgr: OverlaySink, data: {
39
+ anchor: Rect;
40
+ text: string;
41
+ }, size?: {
42
+ width: number;
43
+ height: number;
44
+ }): void;
19
45
  export declare function simulatorBounds(mgr: SnapshotSink, params: Rect & {
20
46
  zoom: number;
21
47
  }): void;
@@ -12,6 +12,7 @@ import { VIEW_ID, VIEW_LAYER } from '../../../shared/view-ids.js';
12
12
  // pushes on one manager never look stale.
13
13
  let globalEpoch = 0;
14
14
  const desiredByMgr = new WeakMap();
15
+ const tooltipRequestByMgr = new WeakMap();
15
16
  function toPlacement(r) {
16
17
  return r.width > 0 && r.height > 0 ? { visible: true, bounds: r } : { visible: false };
17
18
  }
@@ -33,6 +34,31 @@ export function simulatorDevtoolsBounds(mgr, rect) {
33
34
  export function workbenchBounds(mgr, rect) {
34
35
  pushView(mgr, { viewId: VIEW_ID.workbench, placement: toPlacement(rect), layer: VIEW_LAYER.base });
35
36
  }
37
+ export async function showSettingsReady(mgr) {
38
+ const shown = mgr.showSettings();
39
+ const webContentsId = mgr.getSettingsWebContentsId();
40
+ if (webContentsId === null)
41
+ throw new Error('settings view was not created');
42
+ mgr.markOverlayReady(webContentsId);
43
+ await shown;
44
+ }
45
+ export function showPopoverReady(mgr, data) {
46
+ mgr.showPopover(data);
47
+ const webContentsId = mgr.getPopoverWebContentsId();
48
+ if (webContentsId === null)
49
+ throw new Error('popover view was not created');
50
+ mgr.markOverlayReady(webContentsId);
51
+ }
52
+ export function showTooltipReady(mgr, data, size = { width: 80, height: 28 }) {
53
+ mgr.showTooltip(data);
54
+ const webContentsId = mgr.getTooltipWebContentsId();
55
+ if (webContentsId === null)
56
+ throw new Error('tooltip view was not created');
57
+ mgr.markOverlayReady(webContentsId);
58
+ const requestId = (tooltipRequestByMgr.get(mgr) ?? 0) + 1;
59
+ tooltipRequestByMgr.set(mgr, requestId);
60
+ mgr.applyTooltipMeasurement(webContentsId, { requestId, ...size });
61
+ }
36
62
  export function simulatorBounds(mgr, params) {
37
63
  const { zoom, ...rect } = params;
38
64
  pushView(mgr, { viewId: VIEW_ID.simulator, placement: toPlacement(rect), layer: VIEW_LAYER.base, extra: { zoom } });
@@ -4,6 +4,8 @@ import { type WorkbenchContext } from '../workbench-context.js';
4
4
  import type { HostToolbarMessageSubscription } from './host-toolbar-port-channel.js';
5
5
  import type { PlacementSnapshot } from '@dimina-kit/electron-deck/layout';
6
6
  import type { DevtoolsExtra } from '../../../shared/view-ids.js';
7
+ import { type OverlayPanelsView, type TooltipShowPayload } from './overlay-panels-view.js';
8
+ export type { TooltipShowPayload };
7
9
  export { resolveProjectEditorTarget, type ProjectEditorTarget, } from './resolve-project-editor-target.js';
8
10
  /**
9
11
  * Context surface used by the ViewManager. We only need a small slice of the
@@ -89,7 +91,7 @@ export interface ViewManagerContext {
89
91
  * panels) and shares a single injected placement reconciler; this file wires
90
92
  * them together and exposes their combined method surface.
91
93
  */
92
- export interface ViewManager {
94
+ export interface ViewManager extends Pick<OverlayPanelsView, 'showSettings' | 'hideSettings' | 'showPopover' | 'hidePopover' | 'prepareTooltip' | 'showTooltip' | 'hideTooltip' | 'markOverlayReady' | 'applyTooltipMeasurement'> {
93
95
  /**
94
96
  * NATIVE-HOST ONLY. Create the simulator itself as a top-level
95
97
  * WebContentsView (not a renderer `<webview>` guest) loading `simulatorUrl`,
@@ -132,14 +134,6 @@ export interface ViewManager {
132
134
  * `windows/views.ts` module, which every detach call relied on.
133
135
  */
134
136
  detachSimulator(): void;
135
- /** Lazy-create and show the settings overlay view. */
136
- showSettings(): Promise<void>;
137
- /** Remove the settings overlay view (kept around for next open). */
138
- hideSettings(): void;
139
- /** Create and show the popover overlay with the given init payload. */
140
- showPopover(data: unknown): void;
141
- /** Destroy the popover overlay and notify the renderer. */
142
- hidePopover(): void;
143
137
  /** Re-apply layout for every currently visible overlay (on window resize). */
144
138
  repositionAll(): void;
145
139
  /**
@@ -170,6 +164,8 @@ export interface ViewManager {
170
164
  getSettingsWebContentsId(): number | null;
171
165
  /** Return the webContents ID of the popover overlay if alive, else null. */
172
166
  getPopoverWebContentsId(): number | null;
167
+ /** Return the webContents ID of the tooltip overlay if alive, else null. */
168
+ getTooltipWebContentsId(): number | null;
173
169
  /**
174
170
  * Return the webContents ID of the host-toolbar overlay if alive, else null.
175
171
  * The host-toolbar WCV is the one overlay whose OWN renderer drives an IPC
@@ -3,7 +3,7 @@ import { createPlacementReconciler } from './placement-reconciler.js';
3
3
  import { createWorkbenchView } from './workbench-view.js';
4
4
  import { createDevtoolsHost } from './native-simulator-devtools-host.js';
5
5
  import { createHostToolbarView } from './host-toolbar-view.js';
6
- import { createOverlayPanelsView } from './overlay-panels-view.js';
6
+ import { createOverlayPanelsView, } from './overlay-panels-view.js';
7
7
  import { createNativeSimulatorView } from './native-simulator-view.js';
8
8
  export { resolveProjectEditorTarget, } from './resolve-project-editor-target.js';
9
9
  /**
@@ -77,6 +77,11 @@ export function createViewManager(ctx) {
77
77
  hideSettings: overlayPanels.hideSettings,
78
78
  showPopover: overlayPanels.showPopover,
79
79
  hidePopover: overlayPanels.hidePopover,
80
+ prepareTooltip: overlayPanels.prepareTooltip,
81
+ showTooltip: overlayPanels.showTooltip,
82
+ hideTooltip: overlayPanels.hideTooltip,
83
+ markOverlayReady: overlayPanels.markOverlayReady,
84
+ applyTooltipMeasurement: overlayPanels.applyTooltipMeasurement,
80
85
  repositionAll: () => overlayPanels.reapplyPresentOverlays(),
81
86
  disposeProjectViews,
82
87
  disposeAll,
@@ -86,6 +91,7 @@ export function createViewManager(ctx) {
86
91
  getSettingsWebContents: overlayPanels.getSettingsWebContents,
87
92
  getSettingsWebContentsId: overlayPanels.getSettingsWebContentsId,
88
93
  getPopoverWebContentsId: overlayPanels.getPopoverWebContentsId,
94
+ getTooltipWebContentsId: overlayPanels.getTooltipWebContentsId,
89
95
  getHostToolbarWebContentsId: hostToolbar.getHostToolbarWebContentsId,
90
96
  getHostToolbarHeight: hostToolbar.getHostToolbarHeight,
91
97
  resize: () => overlayPanels.applySettingsBoundsIfPresent(),